1use std::fmt::{Debug, Display};
4use std::io::{Cursor, SeekFrom};
5
6use super::header::Status;
7use super::*;
8use binrw::io::TakeSeekExt;
9use binrw::prelude::*;
10use modular_bitfield::prelude::*;
11use smb_dtyp::SecurityDescriptor;
12use smb_dtyp::{Guid, binrw_util::prelude::*};
13use smb_fscc::*;
14
15#[binrw::binrw]
17#[derive(PartialEq, Eq, Clone, Copy, Default)]
18pub struct FileId {
19 pub persistent: u64,
20 pub volatile: u64,
21}
22
23impl FileId {
24 pub const EMPTY: FileId = FileId {
25 persistent: 0,
26 volatile: 0,
27 };
28 pub const FULL: FileId = FileId {
31 persistent: u64::MAX,
32 volatile: u64::MAX,
33 };
34}
35
36impl From<[u8; 16]> for FileId {
37 fn from(data: [u8; 16]) -> Self {
38 let mut cursor = Cursor::new(data);
39 Self::read_le(&mut cursor).unwrap()
40 }
41}
42
43impl From<Guid> for FileId {
44 fn from(guid: Guid) -> Self {
45 let mut cursor = Cursor::new(Vec::new());
46 guid.write_le(&mut cursor).unwrap();
47 <Self as From<[u8; 16]>>::from(cursor.into_inner().try_into().unwrap())
48 }
49}
50
51impl Display for FileId {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 write!(f, "{{{:x}|{:x}}}", self.persistent, self.volatile)
54 }
55}
56
57impl Debug for FileId {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 write!(f, "FileId({})", self)
60 }
61}
62
63#[binrw::binrw]
64#[derive(Debug, PartialEq, Eq)]
65pub struct CreateRequest {
66 #[bw(calc = 57)]
67 #[br(assert(_structure_size == 57))]
68 _structure_size: u16,
69 #[bw(calc = 0)] #[br(assert(_security_flags == 0))]
71 _security_flags: u8,
72 pub requested_oplock_level: OplockLevel,
73 pub impersonation_level: ImpersonationLevel,
74 #[bw(calc = 0)]
75 #[br(assert(_smb_create_flags == 0))]
76 _smb_create_flags: u64,
77 #[bw(calc = 0)]
78 _reserved: u64,
79 pub desired_access: FileAccessMask,
80 pub file_attributes: FileAttributes,
81 pub share_access: ShareAccessFlags,
82 pub create_disposition: CreateDisposition,
83 pub create_options: CreateOptions,
84 #[bw(calc = PosMarker::default())]
85 _name_offset: PosMarker<u16>,
86 #[bw(try_calc = name.size().try_into())]
87 name_length: u16, #[bw(calc = PosMarker::default())]
89 _create_contexts_offset: PosMarker<u32>,
90 #[bw(calc = PosMarker::default())]
91 _create_contexts_length: PosMarker<u32>,
92
93 #[brw(align_before = 8)]
94 #[bw(write_with = PosMarker::write_aoff, args(&_name_offset))]
95 #[br(args { size: SizedStringSize::bytes16(name_length) })]
96 pub name: SizedWideString,
97
98 #[brw(align_before = 8)]
100 #[br(map_stream = |s| s.take_seek(_create_contexts_length.value.into()))]
101 #[bw(write_with = PosMarker::write_roff_size, args(&_create_contexts_offset, &_create_contexts_length))]
102 pub contexts: ChainedItemList<RequestCreateContext, 8>,
103}
104
105#[binrw::binrw]
106#[derive(Debug, PartialEq, Eq, Copy, Clone)]
107#[brw(repr(u32))]
108pub enum ImpersonationLevel {
109 Anonymous = 0x0,
110 Identification = 0x1,
111 Impersonation = 0x2,
112 Delegate = 0x3,
113}
114
115#[binrw::binrw]
116#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)]
117#[brw(repr(u32))]
118pub enum CreateDisposition {
119 Superseded = 0x0,
120 #[default]
121 Open = 0x1,
122 Create = 0x2,
123 OpenIf = 0x3,
124 Overwrite = 0x4,
125 OverwriteIf = 0x5,
126}
127
128#[bitfield]
129#[derive(BinWrite, BinRead, Default, Debug, Clone, Copy, PartialEq, Eq)]
130#[bw(map = |&x| Self::into_bytes(x))]
131#[br(map = Self::from_bytes)]
132pub struct CreateOptions {
133 pub directory_file: bool,
134 pub write_through: bool,
135 pub sequential_only: bool,
136 pub no_intermediate_buffering: bool,
137
138 pub synchronous_io_alert: bool,
139 pub synchronous_io_nonalert: bool,
140 pub non_directory_file: bool,
141 #[skip]
142 __: bool,
143
144 pub complete_if_oplocked: bool,
145 pub no_ea_knowledge: bool,
146 pub open_remote_instance: bool,
147 pub random_access: bool,
148
149 pub delete_on_close: bool,
150 pub open_by_file_id: bool,
151 pub open_for_backup_intent: bool,
152 pub no_compression: bool,
153
154 pub open_requiring_oplock: bool,
155 pub disallow_exclusive: bool,
156 #[skip]
157 __: B2,
158
159 pub reserve_opfilter: bool,
160 pub open_reparse_point: bool,
161 pub open_no_recall: bool,
162 pub open_for_free_space_query: bool,
163
164 #[skip]
165 __: B8,
166}
167
168#[bitfield]
170#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
171#[bw(map = |&x| Self::into_bytes(x))]
172#[br(map = Self::from_bytes)]
173pub struct ShareAccessFlags {
174 pub read: bool,
175 pub write: bool,
176 pub delete: bool,
177 #[skip]
178 __: B29,
179}
180
181#[binrw::binrw]
182#[derive(Debug, PartialEq, Eq)]
183pub struct CreateResponse {
184 #[bw(calc = 89)]
185 #[br(assert(_structure_size == 89))]
186 _structure_size: u16,
187 pub oplock_level: OplockLevel,
188 pub flags: CreateResponseFlags,
189 pub create_action: CreateAction,
190 pub creation_time: FileTime,
191 pub last_access_time: FileTime,
192 pub last_write_time: FileTime,
193 pub change_time: FileTime,
194 pub allocation_size: u64,
195 pub endof_file: u64,
196 pub file_attributes: FileAttributes,
197 #[bw(calc = 0)]
198 _reserved2: u32,
199 pub file_id: FileId,
200 #[br(assert(create_contexts_offset.value & 0x7 == 0))]
202 #[bw(calc = PosMarker::default())]
203 create_contexts_offset: PosMarker<u32>, #[bw(calc = PosMarker::default())]
205 create_contexts_length: PosMarker<u32>, #[br(seek_before = SeekFrom::Start(create_contexts_offset.value as u64))]
209 #[br(map_stream = |s| s.take_seek(create_contexts_length.value.into()))]
210 #[bw(write_with = PosMarker::write_roff_size, args(&create_contexts_offset, &create_contexts_length))]
211 pub create_contexts: ChainedItemList<ResponseCreateContext, 8>,
212}
213
214#[bitfield]
215#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
216#[bw(map = |&x| Self::into_bytes(x))]
217#[br(map = Self::from_bytes)]
218pub struct CreateResponseFlags {
219 pub reparsepoint: bool,
220 #[skip]
221 __: B7,
222}
223
224#[binrw::binrw]
226#[derive(Debug, PartialEq, Eq)]
227#[brw(repr(u32))]
228pub enum CreateAction {
229 Superseded = 0x0,
230 Opened = 0x1,
231 Created = 0x2,
232 Overwritten = 0x3,
233}
234
235#[binrw::binrw]
239#[derive(Debug, PartialEq, Eq)]
240#[bw(import(is_last: bool))]
241#[allow(clippy::manual_non_exhaustive)]
242pub struct CreateContext<T>
243where
244 for<'a> T: BinRead<Args<'a> = (&'a Vec<u8>,)> + BinWrite<Args<'static> = ()>,
245{
246 #[bw(calc = PosMarker::default())]
247 _name_offset: PosMarker<u16>, #[bw(calc = u16::try_from(name.len()).unwrap())]
249 name_length: u16,
250 #[bw(calc = 0)]
251 _reserved: u16,
252 #[bw(calc = PosMarker::default())]
253 _data_offset: PosMarker<u16>,
254 #[bw(calc = PosMarker::default())]
255 _data_length: PosMarker<u32>,
256
257 #[brw(align_before = 8)]
258 #[br(count = name_length)]
259 #[br(seek_before = _name_offset.seek_from(_name_offset.value as u64 - CHAINED_ITEM_PREFIX_SIZE as u64))]
260 #[bw(write_with = PosMarker::write_roff_plus, args(&_name_offset, CHAINED_ITEM_PREFIX_SIZE as u64))]
261 pub name: Vec<u8>,
262
263 #[bw(align_before = 8)]
264 #[br(assert(_data_offset.value % 8 == 0))]
265 #[bw(write_with = PosMarker::write_roff_size_b_plus, args(&_data_offset, &_data_length, &_name_offset, CHAINED_ITEM_PREFIX_SIZE as u64))]
266 #[br(seek_before = _name_offset.seek_from_if(_data_offset.value as u64 - CHAINED_ITEM_PREFIX_SIZE as u64, _data_length.value > 0))]
267 #[br(map_stream = |s| s.take_seek(_data_length.value.into()), args(&name))]
268 pub data: T,
269}
270
271macro_rules! create_context_half {
272 (
273 $struct_name:ident {
274 $(
275 $context_type:ident : $req_type:ty,
276 )+
277 }
278 ) => {
279 pastey::paste! {
280
281#[doc = concat!("[`Create", stringify!($struct_name), "`]")]
283pub trait [<CreateContextData $struct_name Value>] : Into<CreateContext<[<CreateContext $struct_name Data>]>> {
285 const CONTEXT_NAME: &'static [u8];
286}
287
288#[doc = concat!("The [`Create", stringify!($struct_name), "`] Context data enum. ")]
289#[binrw::binrw]
290#[derive(Debug, PartialEq, Eq)]
291#[br(import(name: &Vec<u8>))]
292pub enum [<CreateContext $struct_name Data>] {
293 $(
294 #[br(pre_assert(name.as_slice() == CreateContextType::[<$context_type:upper>].name()))]
295 [<$context_type:camel $struct_name>]($req_type),
296 )+
297}
298
299impl [<CreateContext $struct_name Data>] {
300 pub fn name(&self) -> &'static [u8] {
301 match self {
302 $(
303 Self::[<$context_type:camel $struct_name>](_) => CreateContextType::[<$context_type:upper _NAME>],
304 )+
305 }
306 }
307
308 $(
309 pub fn [<as_ $context_type:snake>](&self) -> Option<&$req_type> {
310 match self {
311 Self::[<$context_type:camel $struct_name>](a) => Some(a),
312 _ => None,
313 }
314 }
315
316 pub fn [<first_ $context_type:snake>](val: &Vec<CreateContext<Self>>) -> Option<&$req_type> {
317 for ctx in val {
318 if let Self::[<$context_type:camel $struct_name>](a) = &ctx.data {
319 return Some(a);
320 }
321 }
322 None
323 }
324 )+
325}
326
327$(
328 impl [<CreateContextData $struct_name Value>] for $req_type {
329 const CONTEXT_NAME: &'static [u8] = CreateContextType::[<$context_type:upper _NAME>];
330 }
331
332 impl From<$req_type> for CreateContext<[<CreateContext $struct_name Data>]> {
333 fn from(req: $req_type) -> Self {
334 CreateContext::<[<CreateContext $struct_name Data>]> {
335 name: <$req_type as [<CreateContextData $struct_name Value>]>::CONTEXT_NAME.to_vec(),
336 data: [<CreateContext $struct_name Data>]::[<$context_type:camel $struct_name>](req),
337 }
338 }
339 }
340
341 impl TryInto<$req_type> for CreateContext<[<CreateContext $struct_name Data>]> {
342 type Error = crate::SmbMsgError;
343 fn try_into(self) -> crate::Result<$req_type> {
344 match self.data {
345 [<CreateContext $struct_name Data>]::[<$context_type:camel $struct_name>](a) => Ok(a),
346 _ => Err(crate::SmbMsgError::UnexpectedContent {
347 expected: stringify!($req_type),
348 actual: "", }),
350 }
351 }
352 }
353)+
354
355pub type [<$struct_name CreateContext>] = CreateContext<[<CreateContext $struct_name Data>]>;
356 }
357 }
358}
359
360macro_rules! make_create_context {
362 (
363 $(
364 $(#[doc = $docstring:literal])*
365 $context_type:ident : $class_name:literal, $req_type:ty $(, $res_type:ty)?;
366 )+
367 ) => {
368 pastey::paste!{
369
370pub enum CreateContextType {
372 $(
373 $(#[doc = $docstring])*
374 [<$context_type:upper>],
375 )+
376}
377
378impl CreateContextType {
379 $(
380 #[doc = concat!("The name for the `", stringify!($context_type), "` create context.")]
381 pub const [<$context_type:upper _NAME>]: &[u8] = $class_name;
382 )+
383
384 pub fn from_name(name: &[u8]) -> Option<CreateContextType> {
385 match name {
386 $(
387 Self::[<$context_type:upper _NAME>] => Some(Self::[<$context_type:upper>]),
388 )+
389 _ => None,
390 }
391 }
392
393 pub fn name(&self) -> &[u8] {
394 match self {
395 $(
396 Self::[<$context_type:upper>] => Self::[<$context_type:upper _NAME>],
397 )+
398 }
399 }
400}
401 }
402
403 create_context_half! {
404 Request {
405 $($context_type: $req_type,)+
406 }
407 }
408
409 create_context_half! {
410 Response {
411 $($($context_type: $res_type,)?)+
412 }
413 }
414 }
415}
416
417make_create_context!(
418 exta: b"ExtA", ChainedItemList<FileFullEaInformation>;
420 secd: b"SecD", SecurityDescriptor;
422 dhnq: b"DHnQ", DurableHandleRequest, DurableHandleResponse;
424 dhnc: b"DHNc", DurableHandleReconnect;
426 alsi: b"AlSi", AllocationSize;
428 mxac: b"MxAc", QueryMaximalAccessRequest, QueryMaximalAccessResponse;
430 twrp: b"TWrp", TimewarpToken;
432 qfid: b"QFid", QueryOnDiskIdReq, QueryOnDiskIdResp;
434 rqls: b"RqLs", RequestLease, RequestLease; dh2q: b"DH2Q", DurableHandleRequestV2, DH2QResp;
438 dh2c: b"DH2C", DurableHandleReconnectV2;
440 appinstid: b"\x45\xBC\xA6\x6A\xEF\xA7\xF7\x4A\x90\x08\xFA\x46\x2E\x14\x4D\x74", AppInstanceId, AppInstanceId;
442 appinstver: b"\xB9\x82\xD0\xB7\x3B\x56\x07\x4F\xA0\x7B\x52\x4A\x81\x16\xA0\x10", AppInstanceVersion, AppInstanceVersion;
444 svhdxopendev: b"\x9C\xCB\xCF\x9E\x04\xC1\xE6\x43\x98\x0E\x15\x8D\xA1\xF6\xEC\x83", SvhdxOpenDeviceContext, SvhdxOpenDeviceContext;
447);
448
449macro_rules! empty_req {
450 ($name:ident) => {
451 #[binrw::binrw]
452 #[derive(Debug, PartialEq, Eq)]
453 pub struct $name;
454 };
455}
456
457#[binrw::binrw]
458#[derive(Debug, PartialEq, Eq, Default)]
459pub struct DurableHandleRequest {
460 #[bw(calc = 0)]
461 #[br(assert(durable_request == 0))]
462 durable_request: u128,
463}
464
465#[binrw::binrw]
466#[derive(Debug, PartialEq, Eq, Default)]
467pub struct DurableHandleResponse {
468 #[bw(calc = 0)]
469 _reserved: u64,
470}
471
472#[binrw::binrw]
473#[derive(Debug, PartialEq, Eq)]
474pub struct DurableHandleReconnect {
475 pub durable_request: FileId,
476}
477#[binrw::binrw]
478#[derive(Debug, PartialEq, Eq, Default)]
479pub struct QueryMaximalAccessRequest {
480 #[br(parse_with = binread_if_has_data)]
481 pub timestamp: Option<FileTime>,
482}
483
484#[binrw::binrw]
485#[derive(Debug, PartialEq, Eq)]
486pub struct AllocationSize {
487 pub allocation_size: u64,
488}
489
490#[binrw::binrw]
491#[derive(Debug, PartialEq, Eq)]
492pub struct TimewarpToken {
493 pub timestamp: FileTime,
494}
495
496#[binrw::binrw]
497#[derive(Debug, PartialEq, Eq)]
498pub enum RequestLease {
499 RqLsReqv1(RequestLeaseV1),
500 RqLsReqv2(RequestLeaseV2),
501}
502
503#[binrw::binrw]
504#[derive(Debug, PartialEq, Eq)]
505pub struct RequestLeaseV1 {
506 pub lease_key: u128,
507 pub lease_state: LeaseState,
508 #[bw(calc = 0)]
509 #[br(assert(lease_flags == 0))]
510 lease_flags: u32,
511 #[bw(calc = 0)]
512 #[br(assert(lease_duration == 0))]
513 lease_duration: u64,
514}
515#[binrw::binrw]
516#[derive(Debug, PartialEq, Eq)]
517pub struct RequestLeaseV2 {
518 pub lease_key: u128,
519 pub lease_state: LeaseState,
520 pub lease_flags: LeaseFlags,
521 #[bw(calc = 0)]
522 #[br(assert(lease_duration == 0))]
523 lease_duration: u64,
524 pub parent_lease_key: u128,
525 pub epoch: u16,
526 #[bw(calc = 0)]
527 reserved: u16,
528}
529
530#[bitfield]
531#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
532#[bw(map = |&x| Self::into_bytes(x))]
533#[br(map = Self::from_bytes)]
534pub struct LeaseFlags {
535 #[skip]
536 __: B2,
537 pub parent_lease_key_set: bool,
538 #[skip]
539 __: B29,
540}
541
542empty_req!(QueryOnDiskIdReq);
543
544#[binrw::binrw]
545#[derive(Debug, PartialEq, Eq)]
546pub struct DurableHandleRequestV2 {
547 pub timeout: u32,
548 pub flags: DurableHandleV2Flags,
549 #[bw(calc = 0)]
550 _reserved: u64,
551 pub create_guid: Guid,
552}
553
554#[bitfield]
555#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
556#[bw(map = |&x| Self::into_bytes(x))]
557#[br(map = Self::from_bytes)]
558pub struct DurableHandleV2Flags {
559 #[skip]
560 __: bool,
561 pub persistent: bool, #[skip]
563 __: B30,
564}
565
566#[binrw::binrw]
567#[derive(Debug, PartialEq, Eq)]
568pub struct DurableHandleReconnectV2 {
569 file_id: FileId,
570 create_guid: Guid,
571 flags: DurableHandleV2Flags,
572}
573
574#[binrw::binrw]
575#[derive(Debug, PartialEq, Eq)]
576pub struct AppInstanceId {
577 #[bw(calc = 20)]
578 #[br(assert(structure_size == 20))]
579 structure_size: u16,
580 #[bw(calc = 0)]
581 _reserved: u16,
582 pub app_instance_id: Guid,
583}
584
585#[binrw::binrw]
586#[derive(Debug, PartialEq, Eq)]
587pub struct AppInstanceVersion {
588 #[bw(calc = 24)]
589 #[br(assert(structure_size == 24))]
590 structure_size: u16,
591 #[bw(calc = 0)]
592 _reserved: u16,
593 #[bw(calc = 0)]
594 _reserved2: u32,
595 pub app_instance_version_high: u64,
596 pub app_instance_version_low: u64,
597}
598
599#[binrw::binrw]
600#[derive(Debug, PartialEq, Eq)]
601pub enum SvhdxOpenDeviceContext {
602 V1(SvhdxOpenDeviceContextV1),
603 V2(SvhdxOpenDeviceContextV2),
604}
605
606#[binrw::binrw]
608#[derive(Debug, PartialEq, Eq)]
609pub struct SvhdxOpenDeviceContextV1 {
610 pub version: u32,
611 pub has_initiator_id: Boolean,
612 #[bw(calc = 0)]
613 reserved1: u8,
614 #[bw(calc = 0)]
615 reserved2: u16,
616 pub initiator_id: Guid,
617 pub flags: u32,
618 pub originator_flags: u32,
619 pub open_request_id: u64,
620 pub initiator_host_name_length: u16,
621 pub initiator_host_name: [u16; 126 / 2],
622}
623
624#[binrw::binrw]
625#[derive(Debug, PartialEq, Eq)]
626pub struct SvhdxOpenDeviceContextV2 {
627 pub version: u32,
628 pub has_initiator_id: Boolean,
629 #[bw(calc = 0)]
630 reserved1: u8,
631 #[bw(calc = 0)]
632 reserved2: u16,
633 pub initiator_id: Guid,
634 pub flags: u32,
635 pub originator_flags: u32,
636 pub open_request_id: u64,
637 pub initiator_host_name_length: u16,
638 pub initiator_host_name: [u16; 126 / 2],
639 pub virtual_disk_properties_initialized: u32,
640 pub server_service_version: u32,
641 pub virtual_sector_size: u32,
642 pub physical_sector_size: u32,
643 pub virtual_size: u64,
644}
645
646#[binrw::binrw]
647#[derive(Debug, PartialEq, Eq)]
648pub struct QueryMaximalAccessResponse {
649 pub query_status: Status,
653
654 pub maximal_access: FileAccessMask,
658}
659
660impl QueryMaximalAccessResponse {
661 pub fn is_success(&self) -> bool {
663 self.query_status == Status::Success
664 }
665
666 pub fn maximal_access(&self) -> Option<FileAccessMask> {
668 if self.is_success() {
669 Some(self.maximal_access)
670 } else {
671 None
672 }
673 }
674}
675
676#[binrw::binrw]
677#[derive(Debug, PartialEq, Eq)]
678pub struct QueryOnDiskIdResp {
679 pub file_id: u64,
680 pub volume_id: u64,
681 #[bw(calc = 0)]
682 _reserved: u128,
683}
684
685#[binrw::binrw]
686#[derive(Debug, PartialEq, Eq)]
687pub struct DH2QResp {
688 pub timeout: u32,
689 pub flags: DurableHandleV2Flags,
690}
691
692#[binrw::binrw]
693#[derive(Debug)]
694pub struct CloseRequest {
695 #[bw(calc = 24)]
696 #[br(assert(_structure_size == 24))]
697 _structure_size: u16,
698 #[bw(calc = CloseFlags::new().with_postquery_attrib(true))]
699 #[br(assert(_flags == CloseFlags::new().with_postquery_attrib(true)))]
700 _flags: CloseFlags,
701 #[bw(calc = 0)]
702 _reserved: u32,
703 pub file_id: FileId,
704}
705
706#[binrw::binrw]
707#[derive(Debug)]
708pub struct CloseResponse {
709 #[bw(calc = 60)]
710 #[br(assert(_structure_size == 60))]
711 _structure_size: u16,
712 pub flags: CloseFlags,
713 #[bw(calc = 0)]
714 _reserved: u32,
715 pub creation_time: FileTime,
716 pub last_access_time: FileTime,
717 pub last_write_time: FileTime,
718 pub change_time: FileTime,
719 pub allocation_size: u64,
720 pub endof_file: u64,
721 pub file_attributes: FileAttributes,
722}
723
724#[bitfield]
725#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
726#[bw(map = |&x| Self::into_bytes(x))]
727#[br(map = Self::from_bytes)]
728pub struct CloseFlags {
729 pub postquery_attrib: bool,
730 #[skip]
731 __: B15,
732}
733
734#[cfg(test)]
735mod tests {
736 use crate::*;
737
738 use super::*;
739
740 test_request! {
741 Create {
742 requested_oplock_level: OplockLevel::None,
743 impersonation_level: ImpersonationLevel::Impersonation,
744 desired_access: FileAccessMask::from_bytes(0x00100081u32.to_le_bytes()),
745 file_attributes: FileAttributes::new(),
746 share_access: ShareAccessFlags::new()
747 .with_read(true)
748 .with_write(true)
749 .with_delete(true),
750 create_disposition: CreateDisposition::Open,
751 create_options: CreateOptions::new()
752 .with_synchronous_io_nonalert(true)
753 .with_disallow_exclusive(true),
754 name: "hello".into(),
755 contexts: vec![
756 DurableHandleRequestV2 {
757 timeout: 0,
758 flags: DurableHandleV2Flags::new(),
759 create_guid: 0x821680290c007b8b11efc0a0c679a320u128.to_le_bytes().into(),
760 }
761 .into(),
762 QueryMaximalAccessRequest::default().into(),
763 QueryOnDiskIdReq.into(),
764 ]
765 .into(),
766 } => "390000000200000000000000000000000000000000000000810010000000000007000000010000002000020078000a008800000068
767 000000680065006c006c006f0000000000000038000000100004000000180020000000444832510000000000000000000000000000000000
768 00000020a379c6a0c0ef118b7b000c29801682180000001000040000001800000000004d7841630000000000000000100004000000180000
769 0000005146696400000000"
770 }
771
772 test_response! {
773 Create {
774 oplock_level: OplockLevel::None,
775 flags: CreateResponseFlags::new(),
776 create_action: CreateAction::Opened,
777 creation_time: 133783827154208828.into(),
778 last_access_time: 133797832406291912.into(),
779 last_write_time: 133783939554544738.into(),
780 change_time: 133783939554544738.into(),
781 allocation_size: 0,
782 endof_file: 0,
783 file_attributes: FileAttributes::new().with_directory(true),
784 file_id: 950737950337192747837452976457u128.to_le_bytes().into(),
785 create_contexts: vec![
786 QueryMaximalAccessResponse {
787 query_status: Status::Success,
788 maximal_access: FileAccessMask::from_bytes(0x001f01ffu32.to_le_bytes()),
789 }
790 .into(),
791 QueryOnDiskIdResp {
792 file_id: 0x400000001e72a,
793 volume_id: 0xb017cfd9,
794 }
795 .into(),
796 ]
797 .into()
798 } => "59000000010000003c083896ae4bdb01c8554b706b58db01620ccdc1c84bdb01620ccdc1c84bdb0100000000000000000000
799 0000000000001000000000000000490100000c000000090000000c0000009800000058000000200000001000040000001800080000
800 004d7841630000000000000000ff011f000000000010000400000018002000000051466964000000002ae7010000000400d9cf17b0
801 0000000000000000000000000000000000000000"
802 }
803
804 use smb_dtyp::make_guid;
805
806 test_response_read! {
807 server2016: Create {
808 oplock_level: OplockLevel::None,
809 flags: CreateResponseFlags::new(),
810 create_action: CreateAction::Opened,
811 creation_time: FileTime::ZERO,
812 last_access_time: FileTime::ZERO,
813 last_write_time: FileTime::ZERO,
814 change_time: FileTime::ZERO,
815 allocation_size: 4096,
816 endof_file: 0,
817 file_attributes: FileAttributes::new().with_normal(true),
818 file_id: make_guid!("00000001-0001-0000-0100-000001000000").into(),
819 create_contexts: vec![
820 QueryMaximalAccessResponse {
821 query_status: Status::NotMapped, maximal_access: FileAccessMask::default(),
823 }
824 .into(),
825 QueryOnDiskIdResp {
826 file_id: 0xffff870415d75290,
827 volume_id: 0xffffe682cb589c90,
828 }
829 .into(),
830 ].into(),
831 } => "59000000010000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000
832 000000008000000076007300010000000100000001000000010000009800000058000000200000001000040000001800080000004d7841
833 6300000000730000c0000000000000000010000400000018002000000051466964000000009052d7150487ffff909c58cb82e6ffff0000
834 0000000000000000000000000000"
835 }
836
837 use smb_dtyp::guid;
847 use smb_tests::*;
848 use time::macros::datetime;
849
850 test_binrw! {
855 struct DurableHandleRequest {} => "00000000000000000000000000000000"
856 }
857
858 test_binrw! {
859 struct DurableHandleResponse {} => "0000000000000000"
860 }
861
862 test_binrw! {
863 struct QueryMaximalAccessRequest {
864 timestamp: None,
865 } => ""
866 }
867
868 test_binrw! {
869 struct QueryMaximalAccessResponse {
870 query_status: Status::Success,
871 maximal_access: FileAccessMask::from_bytes(0x001f01ffu32.to_le_bytes()),
872 } => "00000000ff011f00"
873 }
874
875 test_binrw! {
876 struct QueryOnDiskIdReq {} => ""
877 }
878
879 test_binrw! {
880 struct QueryOnDiskIdResp {
881 file_id: 0x2ae7010000000400,
882 volume_id: 0xd9cf17b000000000,
883 } => "000400000001e72a 00000000b017cfd9 00000000000000000000000000000000"
884 }
885
886 test_binrw! {
888 RequestLease => rqlsv2: RequestLease::RqLsReqv2(RequestLeaseV2 {
889 lease_key: guid!("b69d8fd8-184b-7c4d-a359-40c8a53cd2b7").as_u128(),
890 lease_state: LeaseState::new().with_read_caching(true).with_handle_caching(true),
891 lease_flags: LeaseFlags::new().with_parent_lease_key_set(true),
892 parent_lease_key: guid!("2d158ea3-55db-f749-9cd1-095496a06627").as_u128(),
893 epoch: 0
894 }) => "d88f9db64b184d7ca35940c8a53cd2b703000000040000000000000000000000a38e152ddb5549f79cd1095496a0662700000000"
895 }
896
897 test_binrw! {
898 struct AllocationSize {
899 allocation_size: 0xebfef0d4c000,
900 } => "00c0d4f0feeb0000"
901 }
902
903 test_binrw! {
904 struct DurableHandleRequestV2 {
905 create_guid: guid!("5a08e844-45c3-234d-87c6-596d2bc8bca5"),
906 flags: DurableHandleV2Flags::new(),
907 timeout: 0,
908 } => "0000000000000000000000000000000044e8085ac3454d2387c6596d2bc8bca5"
909 }
910
911 test_binrw! {
912 struct DH2QResp {
913 timeout: 180000,
914 flags: DurableHandleV2Flags::new(),
915 } => "20bf020000000000"
916 }
917
918 test_binrw! {
919 struct TimewarpToken {
920 timestamp: datetime!(2025-01-20 15:36:20.277632400).into(),
921 } => "048fa10d516bdb01"
922 }
923
924 test_binrw! {
925 struct DurableHandleReconnectV2 {
926 file_id: guid!("000000b3-0008-0000-dd00-000008000000").into(),
927 create_guid: guid!("a23e428c-1bac-7e43-8451-91f9f2277a95"),
928 flags: DurableHandleV2Flags::new(),
929 } => "b300000008000000dd000000080000008c423ea2ac1b437e845191f9f2277a9500000000"
930 }
931}