use crate::error::Result;
use crate::pack::{Pack, ReadCursor, Unpack, WriteCursor};
use crate::types::FileId;
use crate::Error;
pub const SMB2_WATCH_TREE: u16 = 0x0001;
pub const FILE_NOTIFY_CHANGE_FILE_NAME: u32 = 0x0000_0001;
pub const FILE_NOTIFY_CHANGE_DIR_NAME: u32 = 0x0000_0002;
pub const FILE_NOTIFY_CHANGE_ATTRIBUTES: u32 = 0x0000_0004;
pub const FILE_NOTIFY_CHANGE_SIZE: u32 = 0x0000_0008;
pub const FILE_NOTIFY_CHANGE_LAST_WRITE: u32 = 0x0000_0010;
pub const FILE_NOTIFY_CHANGE_LAST_ACCESS: u32 = 0x0000_0020;
pub const FILE_NOTIFY_CHANGE_CREATION: u32 = 0x0000_0040;
pub const FILE_NOTIFY_CHANGE_EA: u32 = 0x0000_0080;
pub const FILE_NOTIFY_CHANGE_SECURITY: u32 = 0x0000_0100;
pub const FILE_NOTIFY_CHANGE_STREAM_NAME: u32 = 0x0000_0200;
pub const FILE_NOTIFY_CHANGE_STREAM_SIZE: u32 = 0x0000_0400;
pub const FILE_NOTIFY_CHANGE_STREAM_WRITE: u32 = 0x0000_0800;
#[derive(Debug, Clone)]
pub struct ChangeNotifyRequest {
pub flags: u16,
pub output_buffer_length: u32,
pub file_id: FileId,
pub completion_filter: u32,
}
impl ChangeNotifyRequest {
pub const STRUCTURE_SIZE: u16 = 32;
}
impl Pack for ChangeNotifyRequest {
fn pack(&self, cursor: &mut WriteCursor) {
cursor.write_u16_le(Self::STRUCTURE_SIZE);
cursor.write_u16_le(self.flags);
cursor.write_u32_le(self.output_buffer_length);
cursor.write_u64_le(self.file_id.persistent);
cursor.write_u64_le(self.file_id.volatile);
cursor.write_u32_le(self.completion_filter);
cursor.write_u32_le(0);
}
}
impl Unpack for ChangeNotifyRequest {
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 ChangeNotifyRequest structure size: expected {}, got {}",
Self::STRUCTURE_SIZE,
structure_size
)));
}
let flags = cursor.read_u16_le()?;
let output_buffer_length = cursor.read_u32_le()?;
let persistent = cursor.read_u64_le()?;
let volatile = cursor.read_u64_le()?;
let completion_filter = cursor.read_u32_le()?;
let _reserved = cursor.read_u32_le()?;
Ok(ChangeNotifyRequest {
flags,
output_buffer_length,
file_id: FileId {
persistent,
volatile,
},
completion_filter,
})
}
}
#[derive(Debug, Clone)]
pub struct ChangeNotifyResponse {
pub output_data: Vec<u8>,
}
impl ChangeNotifyResponse {
pub const STRUCTURE_SIZE: u16 = 9;
const FIXED_SIZE: u32 = 8;
}
impl Pack for ChangeNotifyResponse {
fn pack(&self, cursor: &mut WriteCursor) {
let start = cursor.position();
cursor.write_u16_le(Self::STRUCTURE_SIZE);
let output_len = self.output_data.len() as u32;
let output_offset = if output_len > 0 {
(start as u32) + Self::FIXED_SIZE
} else {
0
};
cursor.write_u16_le(output_offset as u16);
cursor.write_u32_le(output_len);
cursor.write_bytes(&self.output_data);
}
}
impl Unpack for ChangeNotifyResponse {
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 ChangeNotifyResponse structure size: expected {}, got {}",
Self::STRUCTURE_SIZE,
structure_size
)));
}
let _output_buffer_offset = cursor.read_u16_le()?;
let output_buffer_length = cursor.read_u32_le()?;
let output_data = if output_buffer_length > 0 {
cursor
.read_bytes_bounded(output_buffer_length as usize)?
.to_vec()
} else {
Vec::new()
};
Ok(ChangeNotifyResponse { output_data })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn change_notify_request_roundtrip_recursive() {
let original = ChangeNotifyRequest {
flags: SMB2_WATCH_TREE,
output_buffer_length: 65536,
file_id: FileId {
persistent: 0x1122_3344_5566_7788,
volatile: 0xAABB_CCDD_EEFF_0011,
},
completion_filter: FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_LAST_WRITE,
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(bytes.len(), 32);
let mut r = ReadCursor::new(&bytes);
let decoded = ChangeNotifyRequest::unpack(&mut r).unwrap();
assert_eq!(decoded.flags, SMB2_WATCH_TREE);
assert_eq!(decoded.output_buffer_length, 65536);
assert_eq!(decoded.file_id, original.file_id);
assert_eq!(
decoded.completion_filter,
FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_LAST_WRITE
);
}
#[test]
fn change_notify_request_wrong_structure_size() {
let mut buf = [0u8; 32];
buf[0..2].copy_from_slice(&99u16.to_le_bytes());
let mut cursor = ReadCursor::new(&buf);
let result = ChangeNotifyRequest::unpack(&mut cursor);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("structure size"), "error was: {err}");
}
#[test]
fn change_notify_response_roundtrip_with_data() {
let notify_data = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
let original = ChangeNotifyResponse {
output_data: notify_data.clone(),
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(bytes.len(), 16);
let mut r = ReadCursor::new(&bytes);
let decoded = ChangeNotifyResponse::unpack(&mut r).unwrap();
assert_eq!(decoded.output_data, notify_data);
}
#[test]
fn change_notify_response_roundtrip_empty() {
let original = ChangeNotifyResponse {
output_data: Vec::new(),
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(bytes.len(), 8);
let mut r = ReadCursor::new(&bytes);
let decoded = ChangeNotifyResponse::unpack(&mut r).unwrap();
assert!(decoded.output_data.is_empty());
}
#[test]
fn change_notify_response_wrong_structure_size() {
let mut buf = [0u8; 8];
buf[0..2].copy_from_slice(&42u16.to_le_bytes());
let mut cursor = ReadCursor::new(&buf);
let result = ChangeNotifyResponse::unpack(&mut cursor);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("structure size"), "error was: {err}");
}
}