//! AFP_AfpInfo Stream (Apple SMB Extensions) //! //! Reference: Apple SMB Extensions protocol (MS-SMB2 ยง2.2.13.2) //! and Samba MacExtensions.h implementation. pub const AFP_INFO_SIZE: usize = 60; pub const AFP_SIGNATURE: u32 = 0x41465000; // "AFP\0" pub const AFP_VERSION: u32 = 0x00010000; pub const AFP_OFF_FINDER_INFO: usize = 16; pub const AFP_FINDER_SIZE: usize = 32; pub const AFPINFO_STREAM_NAME: &[u8] = b"AFP_AfpInfo"; #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct FinderInfo { pub file_type: [u8; 4], pub creator_type: [u8; 4], pub finder_flags: u16, pub location: (u16, u16), pub reserved: u16, pub extended_flags: u16, pub extended_location: (u16, u16), pub reserved2: [u8; 10], } impl FinderInfo { pub fn default_file() -> Self { Self { file_type: [0, 0, 0, 0], creator_type: [0, 0, 0, 0], finder_flags: 0, location: (0, 0), reserved: 0, extended_flags: 0, extended_location: (0, 0), reserved2: [0; 10], } } pub fn default_directory() -> Self { Self { file_type: [0, 0, 0, 0], creator_type: [0, 0, 0, 0], finder_flags: 0, location: (0, 0), reserved: 0, extended_flags: 0, extended_location: (0, 0), reserved2: [0; 10], } } pub fn from_bytes(data: &[u8; 32]) -> Self { let file_type = [data[0], data[1], data[2], data[3]]; let creator_type = [data[4], data[5], data[6], data[7]]; let finder_flags = u16::from_be_bytes([data[8], data[9]]); let location = ( u16::from_be_bytes([data[10], data[11]]), u16::from_be_bytes([data[12], data[13]]), ); let reserved = u16::from_be_bytes([data[14], data[15]]); let extended_flags = u16::from_be_bytes([data[16], data[17]]); let extended_location = ( u16::from_be_bytes([data[18], data[19]]), u16::from_be_bytes([data[20], data[21]]), ); let reserved2 = [ data[22], data[23], data[24], data[25], data[26], data[27], data[28], data[29], data[30], data[31], ]; Self { file_type, creator_type, finder_flags, location, reserved, extended_flags, extended_location, reserved2, } } pub fn to_bytes(&self) -> [u8; 32] { let mut data = [0u8; 32]; data[0..4].copy_from_slice(&self.file_type); data[4..8].copy_from_slice(&self.creator_type); data[8..10].copy_from_slice(&self.finder_flags.to_be_bytes()); data[10..12].copy_from_slice(&self.location.0.to_be_bytes()); data[12..14].copy_from_slice(&self.location.1.to_be_bytes()); data[14..16].copy_from_slice(&self.reserved.to_be_bytes()); data[16..18].copy_from_slice(&self.extended_flags.to_be_bytes()); data[18..20].copy_from_slice(&self.extended_location.0.to_be_bytes()); data[20..22].copy_from_slice(&self.extended_location.1.to_be_bytes()); data[22..32].copy_from_slice(&self.reserved2); data } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct AfpInfo { pub signature: u32, pub version: u32, pub reserved1: u32, pub backup_time: u32, pub finder_info: FinderInfo, pub prodos_info: [u8; 6], pub reserved2: [u8; 6], } impl AfpInfo { pub fn new() -> Self { Self { signature: AFP_SIGNATURE, version: AFP_VERSION, reserved1: 0, backup_time: 0, finder_info: FinderInfo::default_file(), prodos_info: [0; 6], reserved2: [0; 6], } } pub fn from_bytes(data: &[u8]) -> Option { if data.len() < AFP_INFO_SIZE { return None; } let signature = u32::from_le_bytes([data[0], data[1], data[2], data[3]]); if signature != AFP_SIGNATURE { return None; } let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]); if version != AFP_VERSION { return None; } let reserved1 = u32::from_le_bytes([data[8], data[9], data[10], data[11]]); let backup_time = u32::from_le_bytes([data[12], data[13], data[14], data[15]]); let finder_bytes: [u8; 32] = data[16..48].try_into().ok()?; let finder_info = FinderInfo::from_bytes(&finder_bytes); let prodos_info: [u8; 6] = data[48..54].try_into().ok()?; let reserved2: [u8; 6] = data[54..60].try_into().ok()?; Some(Self { signature, version, reserved1, backup_time, finder_info, prodos_info, reserved2, }) } pub fn to_bytes(&self) -> Vec { let mut data = Vec::with_capacity(AFP_INFO_SIZE); data.extend_from_slice(&self.signature.to_le_bytes()); data.extend_from_slice(&self.version.to_le_bytes()); data.extend_from_slice(&self.reserved1.to_le_bytes()); data.extend_from_slice(&self.backup_time.to_le_bytes()); data.extend_from_slice(&self.finder_info.to_bytes()); data.extend_from_slice(&self.prodos_info); data.extend_from_slice(&self.reserved2); data } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct SambaAfpInfo { pub afp: AfpInfo, pub create_time: u32, } impl SambaAfpInfo { pub fn new() -> Self { Self { afp: AfpInfo::new(), create_time: 0, } } pub fn from_bytes(data: &[u8]) -> Option { if data.len() < AFP_INFO_SIZE + 4 { return None; } let afp = AfpInfo::from_bytes(&data[..AFP_INFO_SIZE])?; let create_time = u32::from_le_bytes([ data[60], data[61], data[62], data[63], ]); Some(Self { afp, create_time, }) } pub fn to_bytes(&self) -> Vec { let mut data = self.afp.to_bytes(); data.extend_from_slice(&self.create_time.to_le_bytes()); data } pub fn get_create_time(&self) -> u32 { self.create_time } pub fn set_create_time(&mut self, time: u32) { self.create_time = time; } pub fn get_finder_info(&self) -> &FinderInfo { &self.afp.finder_info } pub fn set_finder_info(&mut self, finder: FinderInfo) { self.afp.finder_info = finder; } } #[cfg(test)] mod tests { use super::*; #[test] fn test_afp_info_roundtrip() { let afp = AfpInfo::new(); let bytes = afp.to_bytes(); assert_eq!(bytes.len(), AFP_INFO_SIZE); let decoded = AfpInfo::from_bytes(&bytes).unwrap(); assert_eq!(decoded.signature, AFP_SIGNATURE); assert_eq!(decoded.version, AFP_VERSION); } #[test] fn test_samba_afp_info_roundtrip() { let mut samba = SambaAfpInfo::new(); samba.set_create_time(12345678); let bytes = samba.to_bytes(); assert_eq!(bytes.len(), AFP_INFO_SIZE + 4); let decoded = SambaAfpInfo::from_bytes(&bytes).unwrap(); assert_eq!(decoded.get_create_time(), 12345678); } #[test] fn test_finder_info_roundtrip() { let finder = FinderInfo::default_file(); let bytes = finder.to_bytes(); assert_eq!(bytes.len(), 32); let decoded = FinderInfo::from_bytes(&bytes); assert_eq!(decoded.file_type, finder.file_type); assert_eq!(decoded.creator_type, finder.creator_type); } #[test] fn test_invalid_signature() { let data = [0u8; 60]; assert!(AfpInfo::from_bytes(&data).is_none()); } }