Add SMB AAPL Extensions Phase 1-6 + VFS xattr support
Phase 1: AAPL Create Context negotiation Phase 2: AFP_AfpInfo Stream structure (Finder info + creation time) Phase 2.5: SMB Named Stream Backend (NamedStreamPath) Phase 2.6: Backend Named Stream Support in handlers Phase 2.7: VFS Extended Attributes (get/set/remove/list_xattr) Phase 4: Time Machine share config (time_machine field) Phase 5: Server/Volume Capabilities Phase 6: macOS Unicode mapping (private range ↔ ASCII) Tests: 174 smb-server tests pass, 52 VFS tests pass
This commit is contained in:
+257
@@ -0,0 +1,257 @@
|
||||
//! 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<Self> {
|
||||
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<u8> {
|
||||
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<Self> {
|
||||
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<u8> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user