use crate::error::Result;
use crate::pack::{FileTime, Pack, ReadCursor, Unpack, WriteCursor};
use crate::types::FileId;
use crate::Error;
pub const SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB: u16 = 0x0001;
#[derive(Debug, Clone)]
pub struct CloseRequest {
pub flags: u16,
pub file_id: FileId,
}
impl CloseRequest {
pub const STRUCTURE_SIZE: u16 = 24;
}
impl Pack for CloseRequest {
fn pack(&self, cursor: &mut WriteCursor) {
cursor.write_u16_le(Self::STRUCTURE_SIZE);
cursor.write_u16_le(self.flags);
cursor.write_u32_le(0);
cursor.write_u64_le(self.file_id.persistent);
cursor.write_u64_le(self.file_id.volatile);
}
}
impl Unpack for CloseRequest {
fn unpack(cursor: &mut ReadCursor<'_>) -> Result<Self> {
let structure_size = cursor.read_u16_le()?;
if structure_size != Self::STRUCTURE_SIZE {
return Err(Error::invalid_data(format!(
"invalid CloseRequest structure size: expected {}, got {}",
Self::STRUCTURE_SIZE,
structure_size
)));
}
let flags = cursor.read_u16_le()?;
let _reserved = cursor.read_u32_le()?;
let persistent = cursor.read_u64_le()?;
let volatile = cursor.read_u64_le()?;
Ok(CloseRequest {
flags,
file_id: FileId {
persistent,
volatile,
},
})
}
}
#[derive(Debug, Clone)]
pub struct CloseResponse {
pub flags: u16,
pub creation_time: FileTime,
pub last_access_time: FileTime,
pub last_write_time: FileTime,
pub change_time: FileTime,
pub allocation_size: u64,
pub end_of_file: u64,
pub file_attributes: u32,
}
impl CloseResponse {
pub const STRUCTURE_SIZE: u16 = 60;
}
impl Pack for CloseResponse {
fn pack(&self, cursor: &mut WriteCursor) {
cursor.write_u16_le(Self::STRUCTURE_SIZE);
cursor.write_u16_le(self.flags);
cursor.write_u32_le(0); self.creation_time.pack(cursor);
self.last_access_time.pack(cursor);
self.last_write_time.pack(cursor);
self.change_time.pack(cursor);
cursor.write_u64_le(self.allocation_size);
cursor.write_u64_le(self.end_of_file);
cursor.write_u32_le(self.file_attributes);
}
}
impl Unpack for CloseResponse {
fn unpack(cursor: &mut ReadCursor<'_>) -> Result<Self> {
let structure_size = cursor.read_u16_le()?;
if structure_size != Self::STRUCTURE_SIZE {
return Err(Error::invalid_data(format!(
"invalid CloseResponse structure size: expected {}, got {}",
Self::STRUCTURE_SIZE,
structure_size
)));
}
let flags = cursor.read_u16_le()?;
let _reserved = cursor.read_u32_le()?;
let creation_time = FileTime::unpack(cursor)?;
let last_access_time = FileTime::unpack(cursor)?;
let last_write_time = FileTime::unpack(cursor)?;
let change_time = FileTime::unpack(cursor)?;
let allocation_size = cursor.read_u64_le()?;
let end_of_file = cursor.read_u64_le()?;
let file_attributes = cursor.read_u32_le()?;
Ok(CloseResponse {
flags,
creation_time,
last_access_time,
last_write_time,
change_time,
allocation_size,
end_of_file,
file_attributes,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn close_request_roundtrip() {
let original = CloseRequest {
flags: SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB,
file_id: FileId {
persistent: 0x1122_3344_5566_7788,
volatile: 0xAABB_CCDD_EEFF_0011,
},
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(bytes.len(), 24);
let mut r = ReadCursor::new(&bytes);
let decoded = CloseRequest::unpack(&mut r).unwrap();
assert_eq!(decoded.flags, original.flags);
assert_eq!(decoded.file_id, original.file_id);
}
#[test]
fn close_request_known_bytes() {
let mut buf = [0u8; 24];
buf[0..2].copy_from_slice(&24u16.to_le_bytes());
buf[2..4].copy_from_slice(&1u16.to_le_bytes());
buf[4..8].copy_from_slice(&0u32.to_le_bytes());
buf[8..16].copy_from_slice(&0x42u64.to_le_bytes());
buf[16..24].copy_from_slice(&0x99u64.to_le_bytes());
let mut cursor = ReadCursor::new(&buf);
let req = CloseRequest::unpack(&mut cursor).unwrap();
assert_eq!(req.flags, SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB);
assert_eq!(req.file_id.persistent, 0x42);
assert_eq!(req.file_id.volatile, 0x99);
}
#[test]
fn close_request_wrong_structure_size() {
let mut buf = [0u8; 24];
buf[0..2].copy_from_slice(&99u16.to_le_bytes());
let mut cursor = ReadCursor::new(&buf);
let result = CloseRequest::unpack(&mut cursor);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("structure size"), "error was: {err}");
}
#[test]
fn close_response_roundtrip() {
let original = CloseResponse {
flags: SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB,
creation_time: FileTime(0x01D8_AAAA_BBBB_CCCC),
last_access_time: FileTime(0x01D8_DDDD_EEEE_FFFF),
last_write_time: FileTime(0x01D8_1111_2222_3333),
change_time: FileTime(0x01D8_4444_5555_6666),
allocation_size: 4096,
end_of_file: 2048,
file_attributes: 0x20, };
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(bytes.len(), 60);
let mut r = ReadCursor::new(&bytes);
let decoded = CloseResponse::unpack(&mut r).unwrap();
assert_eq!(decoded.flags, original.flags);
assert_eq!(decoded.creation_time, original.creation_time);
assert_eq!(decoded.last_access_time, original.last_access_time);
assert_eq!(decoded.last_write_time, original.last_write_time);
assert_eq!(decoded.change_time, original.change_time);
assert_eq!(decoded.allocation_size, original.allocation_size);
assert_eq!(decoded.end_of_file, original.end_of_file);
assert_eq!(decoded.file_attributes, original.file_attributes);
}
#[test]
fn close_response_known_bytes() {
let mut buf = [0u8; 60];
buf[0..2].copy_from_slice(&60u16.to_le_bytes());
buf[2..4].copy_from_slice(&1u16.to_le_bytes());
buf[4..8].copy_from_slice(&0u32.to_le_bytes());
buf[8..16].copy_from_slice(&100u64.to_le_bytes());
buf[16..24].copy_from_slice(&200u64.to_le_bytes());
buf[24..32].copy_from_slice(&300u64.to_le_bytes());
buf[32..40].copy_from_slice(&400u64.to_le_bytes());
buf[40..48].copy_from_slice(&8192u64.to_le_bytes());
buf[48..56].copy_from_slice(&1024u64.to_le_bytes());
buf[56..60].copy_from_slice(&0x10u32.to_le_bytes());
let mut cursor = ReadCursor::new(&buf);
let resp = CloseResponse::unpack(&mut cursor).unwrap();
assert_eq!(resp.flags, SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB);
assert_eq!(resp.creation_time, FileTime(100));
assert_eq!(resp.last_access_time, FileTime(200));
assert_eq!(resp.last_write_time, FileTime(300));
assert_eq!(resp.change_time, FileTime(400));
assert_eq!(resp.allocation_size, 8192);
assert_eq!(resp.end_of_file, 1024);
assert_eq!(resp.file_attributes, 0x10);
}
#[test]
fn close_response_wrong_structure_size() {
let mut buf = [0u8; 60];
buf[0..2].copy_from_slice(&42u16.to_le_bytes());
let mut cursor = ReadCursor::new(&buf);
let result = CloseResponse::unpack(&mut cursor);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("structure size"), "error was: {err}");
}
#[test]
fn close_response_zero_flags_has_zeroed_attributes() {
let original = CloseResponse {
flags: 0,
creation_time: FileTime::ZERO,
last_access_time: FileTime::ZERO,
last_write_time: FileTime::ZERO,
change_time: FileTime::ZERO,
allocation_size: 0,
end_of_file: 0,
file_attributes: 0,
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
let mut r = ReadCursor::new(&bytes);
let decoded = CloseResponse::unpack(&mut r).unwrap();
assert_eq!(decoded.flags, 0);
assert_eq!(decoded.creation_time, FileTime::ZERO);
assert_eq!(decoded.file_attributes, 0);
}
}