1use binrw::{NullWideString, io::TakeSeekExt, prelude::*};
2use modular_bitfield::prelude::*;
3use smb_dtyp::binrw_util::prelude::*;
4
5#[binrw::binrw]
8#[derive(Debug, PartialEq, Eq)]
9pub struct ReqGetDfsReferral {
10 pub max_referral_level: ReferralLevel,
12 pub request_file_name: NullWideString,
14}
15
16#[binrw::binrw]
19#[derive(Debug, PartialEq, Eq)]
20#[brw(repr(u16))]
21pub enum ReferralLevel {
22 V1 = 1,
24 V2 = 2,
26 V3 = 3,
28 V4 = 4,
30}
31
32#[binrw::binrw]
33#[derive(Debug, PartialEq, Eq)]
34pub struct ReqGetDfsReferralEx {
35 pub max_referral_level: u16,
37 pub request_flags: DfsRequestFlags,
38 request_data_length: PosMarker<u32>,
39 #[bw(write_with = PosMarker::write_size, args(request_data_length))]
40 #[br(map_stream = |s| s.take_seek(request_data_length.value as u64))]
41 pub request_data: DfsRequestData,
42}
43
44#[bitfield]
45#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
46#[bw(map = |&x| Self::into_bytes(x))]
47#[br(map = Self::from_bytes)]
48pub struct DfsRequestFlags {
49 pub site_name: bool,
51 #[skip]
52 __: B15,
53}
54
55#[binrw::binrw]
57#[derive(Debug, PartialEq, Eq)]
58pub struct DfsRequestData {
59 #[bw(try_calc = request_file_name.len().try_into())]
60 request_file_name_length: u16,
61 #[br(args(request_file_name_length as u64))]
63 request_file_name: SizedWideString,
64 #[bw(try_calc = site_name.len().try_into())]
65 site_name_length: u16,
66 #[br(args(site_name_length as u64))]
68 site_name: SizedWideString,
69}
70
71impl DfsRequestData {
72 pub fn get_bin_size(&self) -> usize {
73 size_of::<u16>() * 2 + self.request_file_name.len() * size_of::<u16>() + self.site_name.len() * size_of::<u16>() }
77}
78
79#[binrw::binrw]
80#[derive(Debug, PartialEq, Eq)]
81pub struct RespGetDfsReferral {
82 pub path_consumed: u16,
83 #[bw(try_calc = referral_entries.len().try_into())]
84 number_of_referrals: u16,
85 pub referral_header_flags: ReferralHeaderFlags,
86 #[br(count = number_of_referrals)]
87 pub referral_entries: Vec<ReferralEntry>,
88 }
90
91#[bitfield]
92#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
93#[bw(map = |&x| Self::into_bytes(x))]
94#[br(map = Self::from_bytes)]
95pub struct ReferralHeaderFlags {
96 pub referral_servers: bool,
98 pub storage_servers: bool,
100 pub target_failbacl: bool,
102 #[skip]
103 __: B29,
104}
105
106#[binrw::binrw]
107#[derive(Debug, PartialEq, Eq)]
108pub struct ReferralEntry {
109 #[bw(calc = value.get_version())]
110 pub version: u16,
111 #[bw(calc = PosMarker::default())]
112 _size: PosMarker<u16>,
113 #[br(args(version))]
114 #[bw(write_with = PosMarker::write_size, args(&_size))]
115 pub value: ReferralEntryValue,
116}
117
118impl ReferralEntry {
119 pub const COMMON_PART_SIZE: usize = std::mem::size_of::<u16>() * 2;
121}
122
123macro_rules! gen_ref_entry_val {
124 (
125 $($ver:literal,)+
126 ) => {
127 paste::paste! {
128 #[binrw::binrw]
129 #[derive(Debug, PartialEq, Eq)]
130 #[br(import(version: u16))]
131 pub enum ReferralEntryValue {
132 $(
133 #[doc = concat!("A DFS referral version", stringify!($ver), "Entry")]
134 #[br(pre_assert(version == $ver))]
135 [<V $ver>]([<ReferralEntryValueV $ver>]),
136 )+
137 }
138
139 impl ReferralEntryValue {
140 fn get_version(&self) -> u16 {
141 match self {
142 $(
143 Self::[<V $ver>](_) => $ver,
144 )+
145 }
146 }
147 }
148 }
149 };
150}
151
152gen_ref_entry_val!(1, 2, 3, 4,);
153
154#[binrw::binrw]
155#[derive(Debug, PartialEq, Eq)]
156pub struct ReferralEntryValueV1 {
157 pub server_type: DfsServerType,
159 #[bw(calc = 0)]
160 _referral_entry_flags: u16,
161 pub share_name: NullWideString,
163}
164
165#[binrw::binrw]
167#[derive(Debug, PartialEq, Eq)]
168#[brw(repr(u16))]
169pub enum DfsServerType {
170 NonRoot = 0x0,
172 Root = 0x1,
174}
175
176#[binrw::binrw]
179#[derive(Debug, PartialEq, Eq)]
180pub struct ReferralEntryValueV2 {
181 #[bw(calc = PosMarker::default())]
182 _start: PosMarker<()>,
183 pub server_type: DfsServerType,
185 #[bw(calc = 0)]
186 _referral_entry_flags: u16,
187 #[bw(calc = 0)]
188 _proximity: u32,
189
190 pub time_to_live: u32,
192 #[br(assert(dfs_path_offset.value >= ReferralEntry::COMMON_PART_SIZE as u16))]
193 #[bw(calc = PosMarker::default())]
194 dfs_path_offset: PosMarker<u16>,
195 #[br(assert(dfs_alternate_path_offset.value >= ReferralEntry::COMMON_PART_SIZE as u16))]
196 #[bw(calc = PosMarker::default())]
197 dfs_alternate_path_offset: PosMarker<u16>,
198 #[br(assert(network_address_offset.value >= ReferralEntry::COMMON_PART_SIZE as u16))]
199 #[bw(calc = PosMarker::default())]
200 network_address_offset: PosMarker<u16>,
201
202 #[bw(calc = PosMarker::default())]
203 _restore_position: PosMarker<()>,
204
205 #[br(seek_before = _start.seek_from((dfs_path_offset.value as usize - ReferralEntry::COMMON_PART_SIZE).try_into().unwrap()))]
207 pub dfs_path: NullWideString,
208 #[br(seek_before = _start.seek_from((dfs_alternate_path_offset.value as usize - ReferralEntry::COMMON_PART_SIZE).try_into().unwrap()))]
210 pub dfs_alternate_path: NullWideString,
211 #[br(seek_before = _start.seek_from((network_address_offset.value as usize - ReferralEntry::COMMON_PART_SIZE).try_into().unwrap()))]
213 pub network_address: NullWideString,
214
215 #[br(seek_before = _restore_position.seek_from(0))]
216 #[bw(calc = ())]
217 __: (),
218}
219
220#[binrw::binrw]
221#[derive(Debug, PartialEq, Eq)]
222pub struct ReferralEntryValueV3 {
223 pub server_type: DfsServerType,
225 pub referral_entry_flags: ReferralEntryFlags,
226 pub time_to_live: u32,
228 #[br(args(referral_entry_flags))]
229 pub value: EntryV3Value,
230}
231
232impl ReferralEntryValueV3 {
233 pub const COMMON_PART_SIZE: usize = std::mem::size_of::<u16>() * 2 + std::mem::size_of::<u32>();
235}
236
237#[bitfield]
238#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
239#[bw(map = |&x| Self::into_bytes(x))]
240#[br(map = Self::from_bytes)]
241pub struct ReferralEntryFlags {
242 #[skip]
243 __: bool,
244 pub name_list_referral: bool,
245 #[skip]
246 __: B14,
247}
248
249#[binrw::binrw]
250#[derive(Debug, PartialEq, Eq)]
251#[br(import(flags: ReferralEntryFlags))]
252pub enum EntryV3Value {
253 #[br(pre_assert(flags.name_list_referral()))]
255 DfsPath(EntryV3V4DfsPaths),
256 #[br(pre_assert(!flags.name_list_referral()))]
258 NetworkAddress(EntryV3DCRefs),
259}
260
261impl EntryV3Value {
262 const OFFSET_FROM_ENTRY_START: u16 =
267 (ReferralEntry::COMMON_PART_SIZE + ReferralEntryValueV3::COMMON_PART_SIZE) as u16;
268}
269
270#[binrw::binrw]
272#[derive(Debug, PartialEq, Eq)]
273pub struct EntryV3V4DfsPaths {
274 #[bw(calc = PosMarker::default())]
275 _start: PosMarker<()>,
276 #[br(assert(dfs_path_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
277 #[bw(calc = PosMarker::default())]
278 dfs_path_offset: PosMarker<u16>,
279 #[br(assert(dfs_alternate_path_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
280 #[bw(calc = PosMarker::default())]
281 dfs_alternate_path_offset: PosMarker<u16>,
282 #[br(assert(network_address_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
283 #[bw(calc = PosMarker::default())]
284 network_address_offset: PosMarker<u16>,
285 #[bw(calc = 0)]
286 _service_site_guid: u128,
287
288 #[bw(calc = PosMarker::default())]
289 _restore_position: PosMarker<()>,
290
291 #[br(seek_before = _start.seek_from((dfs_path_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
293 pub dfs_path: NullWideString,
294 #[br(seek_before = _start.seek_from((dfs_alternate_path_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
296 pub dfs_alternate_path: NullWideString,
297 #[br(seek_before = _start.seek_from((network_address_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
299 pub network_address: NullWideString,
300
301 #[br(seek_before = _restore_position.seek_from(0))]
302 #[bw(calc = ())]
303 __: (),
304}
305
306#[binrw::binrw]
308#[derive(Debug, PartialEq, Eq)]
309pub struct EntryV3DCRefs {
310 #[bw(calc = PosMarker::default())]
311 _start: PosMarker<()>,
312 #[br(assert(special_name_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
313 #[bw(calc = PosMarker::default())]
314 special_name_offset: PosMarker<u16>,
315 number_of_expanded_names: u16,
316 #[br(assert(expanded_name_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
317 #[bw(calc = PosMarker::default())]
318 expanded_name_offset: PosMarker<u16>,
319
320 #[bw(calc = PosMarker::default())]
321 _restore_position: PosMarker<()>,
322
323 #[br(seek_before = _start.seek_from((special_name_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
324 pub special_name: NullWideString,
325 #[br(seek_before = _start.seek_from((expanded_name_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
326 #[br(count = number_of_expanded_names)]
327 pub expanded_names: Vec<NullWideString>,
328
329 #[br(seek_before = _restore_position.seek_from(0))]
330 #[bw(calc = ())]
331 __: (),
332}
333
334#[binrw::binrw]
335#[derive(Debug, PartialEq, Eq)]
336pub struct ReferralEntryValueV4 {
337 pub server_type: DfsServerType,
339 #[br(assert((referral_entry_flags & !u16::from_le_bytes(ReferralEntryFlagsV4::new().with_target_set_boundary(true).into_bytes())) == 0))]
341 pub referral_entry_flags: u16,
342 pub time_to_live: u32,
344 pub refs: EntryV3V4DfsPaths,
346}
347
348#[bitfield]
350#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
351#[bw(map = |&x| Self::into_bytes(x))]
352#[br(map = Self::from_bytes)]
353struct ReferralEntryFlagsV4 {
354 #[skip]
355 __: B2,
356 #[skip(getters)]
357 target_set_boundary: bool,
358 #[skip]
359 __: B13,
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365 use crate::IoctlRequestContent;
366 use std::io::Cursor;
367
368 #[test]
369 pub fn test_write_req() {
370 let req = ReqGetDfsReferral {
371 max_referral_level: ReferralLevel::V4,
372 request_file_name: r"\ADC.aviv.local\dfs\Docs".into(),
373 };
374 let mut buf = Vec::new();
375 req.write_le(&mut Cursor::new(&mut buf)).unwrap();
376 assert_eq!(buf.len() as u32, req.get_bin_size());
377 assert_eq!(
378 buf,
379 &[
380 0x4, 0x0, 0x5c, 0x0, 0x41, 0x0, 0x44, 0x0, 0x43, 0x0, 0x2e, 0x0, 0x61, 0x0, 0x76,
381 0x0, 0x69, 0x0, 0x76, 0x0, 0x2e, 0x0, 0x6c, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x61, 0x0,
382 0x6c, 0x0, 0x5c, 0x0, 0x64, 0x0, 0x66, 0x0, 0x73, 0x0, 0x5c, 0x0, 0x44, 0x0, 0x6f,
383 0x0, 0x63, 0x0, 0x73, 0x0, 0x0, 0x0
384 ]
385 );
386 }
387
388 #[test]
389 pub fn test_v4_parses_properly() {
390 let bytes = [
391 0x30, 0x0, 0x2, 0x0, 0x2, 0x0, 0x0, 0x0, 0x4, 0x0, 0x22, 0x0, 0x0, 0x0, 0x4, 0x0, 0x8,
392 0x7, 0x0, 0x0, 0x44, 0x0, 0x76, 0x0, 0xa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
393 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8,
394 0x7, 0x0, 0x0, 0x22, 0x0, 0x54, 0x0, 0xa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
395 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5c, 0x0, 0x41, 0x0, 0x44, 0x0, 0x43, 0x0,
396 0x2e, 0x0, 0x61, 0x0, 0x76, 0x0, 0x69, 0x0, 0x76, 0x0, 0x2e, 0x0, 0x6c, 0x0, 0x6f, 0x0,
397 0x63, 0x0, 0x61, 0x0, 0x6c, 0x0, 0x5c, 0x0, 0x64, 0x0, 0x66, 0x0, 0x73, 0x0, 0x5c, 0x0,
398 0x44, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x73, 0x0, 0x0, 0x0, 0x5c, 0x0, 0x41, 0x0, 0x44, 0x0,
399 0x43, 0x0, 0x2e, 0x0, 0x61, 0x0, 0x76, 0x0, 0x69, 0x0, 0x76, 0x0, 0x2e, 0x0, 0x6c, 0x0,
400 0x6f, 0x0, 0x63, 0x0, 0x61, 0x0, 0x6c, 0x0, 0x5c, 0x0, 0x64, 0x0, 0x66, 0x0, 0x73, 0x0,
401 0x5c, 0x0, 0x44, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x73, 0x0, 0x0, 0x0, 0x5c, 0x0, 0x41, 0x0,
402 0x44, 0x0, 0x43, 0x0, 0x5c, 0x0, 0x53, 0x0, 0x68, 0x0, 0x61, 0x0, 0x72, 0x0, 0x65, 0x0,
403 0x73, 0x0, 0x5c, 0x0, 0x44, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x73, 0x0, 0x0, 0x0, 0x5c, 0x0,
404 0x46, 0x0, 0x53, 0x0, 0x52, 0x0, 0x56, 0x0, 0x5c, 0x0, 0x53, 0x0, 0x68, 0x0, 0x61, 0x0,
405 0x72, 0x0, 0x65, 0x0, 0x73, 0x0, 0x5c, 0x0, 0x4d, 0x0, 0x79, 0x0, 0x53, 0x0, 0x68, 0x0,
406 0x61, 0x0, 0x72, 0x0, 0x65, 0x0, 0x0, 0x0,
407 ];
408
409 let r = RespGetDfsReferral::read_le(&mut Cursor::new(&bytes)).unwrap();
410 assert_eq!(
411 r,
412 RespGetDfsReferral {
413 path_consumed: 48,
414 referral_header_flags: ReferralHeaderFlags::new().with_storage_servers(true),
415 referral_entries: vec![
416 ReferralEntry {
417 value: ReferralEntryValue::V4(ReferralEntryValueV4 {
418 server_type: DfsServerType::NonRoot,
419 referral_entry_flags: u16::from_le_bytes(
420 ReferralEntryFlagsV4::new()
421 .with_target_set_boundary(true)
422 .into_bytes()
423 ),
424 time_to_live: 1800,
425 refs: EntryV3V4DfsPaths {
426 dfs_path: r"\ADC.aviv.local\dfs\Docs".into(),
427 dfs_alternate_path: r"\ADC.aviv.local\dfs\Docs".into(),
428 network_address: r"\ADC\Shares\Docs".into()
429 }
430 })
431 },
432 ReferralEntry {
433 value: ReferralEntryValue::V4(ReferralEntryValueV4 {
434 server_type: DfsServerType::NonRoot,
435 referral_entry_flags: 0,
436 time_to_live: 1800,
437 refs: EntryV3V4DfsPaths {
438 dfs_path: r"\ADC.aviv.local\dfs\Docs".into(),
439 dfs_alternate_path: r"\ADC.aviv.local\dfs\Docs".into(),
440 network_address: r"\FSRV\Shares\MyShare".into()
441 }
442 })
443 }
444 ]
445 }
446 )
447 }
448}