1use 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}