smb_msg/
notify.rs

1//! SMB2 Change Notify Request and Response, and Server to Client Notification
2use std::io::SeekFrom;
3
4use binrw::io::TakeSeekExt;
5use binrw::prelude::*;
6use modular_bitfield::prelude::*;
7
8use super::FileId;
9use smb_dtyp::binrw_util::prelude::*;
10use smb_fscc::*;
11
12#[binrw::binrw]
13#[derive(Debug)]
14pub struct ChangeNotifyRequest {
15    #[bw(calc = 32)]
16    #[br(assert(_structure_size == 32))]
17    _structure_size: u16,
18    pub flags: NotifyFlags,
19    pub output_buffer_length: u32,
20    pub file_id: FileId,
21    pub completion_filter: NotifyFilter,
22    #[bw(calc = 0)]
23    _reserved: u32,
24}
25
26#[bitfield]
27#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
28#[bw(map = |&x| Self::into_bytes(x))]
29#[br(map = Self::from_bytes)]
30pub struct NotifyFlags {
31    pub watch_tree: bool,
32    #[skip]
33    __: B15,
34}
35
36#[bitfield]
37#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
38#[bw(map = |&x| Self::into_bytes(x))]
39#[br(map = Self::from_bytes)]
40pub struct NotifyFilter {
41    pub file_name: bool,
42    pub dir_name: bool,
43    pub attributes: bool,
44    pub size: bool,
45
46    pub last_write: bool,
47    pub last_access: bool,
48    pub creation: bool,
49    pub ea: bool,
50
51    pub security: bool,
52    pub stream_name: bool,
53    pub stream_size: bool,
54    pub stream_write: bool,
55
56    #[skip]
57    __: B20,
58}
59
60impl NotifyFilter {}
61
62#[binrw::binrw]
63#[derive(Debug, PartialEq, Eq)]
64pub struct ChangeNotifyResponse {
65    #[bw(calc = 9)]
66    #[br(assert(_structure_size == 9))]
67    _structure_size: u16,
68    #[bw(calc = PosMarker::default())]
69    _output_buffer_offset: PosMarker<u16>,
70    #[bw(calc = PosMarker::default())]
71    _output_buffer_length: PosMarker<u32>,
72    #[br(seek_before = SeekFrom::Start(_output_buffer_offset.value.into()))]
73    #[br(map_stream = |s| s.take_seek(_output_buffer_length.value.into()), parse_with = binrw::helpers::until_eof)]
74    pub buffer: Vec<FileNotifyInformation>,
75}
76
77#[binrw::binrw]
78#[derive(Debug, PartialEq, Eq)]
79pub struct ServerToClientNotification {
80    structure_size: u16,
81    #[bw(calc = 0)]
82    _reserved: u16,
83    #[bw(calc = notification.get_type())]
84    notification_type: NotificationType,
85    #[br(args(notification_type))]
86    pub notification: Notification,
87}
88
89#[binrw::binrw]
90#[derive(Debug, PartialEq, Eq)]
91#[brw(repr(u32))]
92pub enum NotificationType {
93    NotifySessionClosed = 0,
94}
95
96#[binrw::binrw]
97#[derive(Debug, PartialEq, Eq)]
98#[br(import(notification_type: NotificationType))]
99pub enum Notification {
100    #[br(pre_assert(notification_type == NotificationType::NotifySessionClosed))]
101    NotifySessionClosed(NotifySessionClosed),
102}
103
104impl Notification {
105    pub fn get_type(&self) -> NotificationType {
106        match self {
107            Notification::NotifySessionClosed(_) => NotificationType::NotifySessionClosed,
108        }
109    }
110}
111
112#[binrw::binrw]
113#[derive(Debug, PartialEq, Eq)]
114pub struct NotifySessionClosed {
115    #[bw(calc = 0)]
116    _reserved: u32,
117}
118
119#[cfg(test)]
120mod tests {
121    use std::io::Cursor;
122
123    use crate::*;
124    use smb_dtyp::guid::Guid;
125
126    use super::*;
127
128    #[test]
129    pub fn change_notify_request_write() {
130        let request = ChangeNotifyRequest {
131            flags: NotifyFlags::new(),
132            output_buffer_length: 2048,
133            file_id: "000005d1-000c-0000-1900-00000c000000"
134                .parse::<Guid>()
135                .unwrap()
136                .into(),
137            completion_filter: NotifyFilter::new()
138                .with_file_name(true)
139                .with_dir_name(true)
140                .with_attributes(true)
141                .with_last_write(true),
142        };
143
144        let mut cursor = Cursor::new(Vec::new());
145        request.write_le(&mut cursor).unwrap();
146        assert_eq!(
147            cursor.into_inner(),
148            [
149                0x20, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0xd1, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0,
150                0x19, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
151            ]
152        );
153    }
154
155    #[test]
156    pub fn test_change_notify_response_pending_parse() {
157        let data = [0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0];
158        let response = ChangeNotifyResponse::read_le(&mut Cursor::new(&data)).unwrap();
159        assert_eq!(response, ChangeNotifyResponse { buffer: vec![] });
160    }
161
162    #[test]
163    pub fn test_change_notify_response_with_data_parse() {
164        let data = [
165            0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0x0, 0x0, 0x0,
166            0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x56, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
167            0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x25, 0x0, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0,
168            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x48,
169            0x0, 0x34, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0,
170            0x4e, 0x0, 0x65, 0x0, 0x77, 0x0, 0x20, 0x0, 0x66, 0x0, 0x6f, 0x0, 0x6c, 0x0, 0x64, 0x0,
171            0x65, 0x0, 0x72, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x6a,
172            0x0, 0x64, 0x0, 0x73, 0x0, 0x61, 0x0,
173        ];
174
175        let parsed = decode_content(&data);
176        let notify_response = parsed.content.to_changenotify().unwrap();
177
178        assert_eq!(
179            notify_response,
180            ChangeNotifyResponse {
181                buffer: vec![
182                    FileNotifyInformationInner {
183                        action: NotifyAction::RenamedOldName,
184                        file_name: "New folder".into()
185                    }
186                    .into(),
187                    FileNotifyInformationInner {
188                        action: NotifyAction::RenamedNewName,
189                        file_name: "jdsa".into()
190                    }
191                    .into()
192                ]
193            }
194        );
195    }
196}