smb_msg/
create.rs

1//! Create & Close (files) requests and responses.
2
3use 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/// 2.2.14.1: SMB2_FILEID
16#[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    /// A file ID that is used to indicate that the file ID is not valid,
29    /// with setting all bits to 1 - {0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}.
30    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)] // reserved
70    #[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, // bytes
88    #[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    /// Use the [`CreateContextRequestData`]`::first_...` function family to get the first context of a specific type.
99    #[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// share_access 4 byte flags:
169#[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    // assert it's 8-aligned
201    #[br(assert(create_contexts_offset.value & 0x7 == 0))]
202    #[bw(calc = PosMarker::default())]
203    create_contexts_offset: PosMarker<u32>, // from smb header start
204    #[bw(calc = PosMarker::default())]
205    create_contexts_length: PosMarker<u32>, // bytes
206
207    /// Use the [`CreateContextResponseData`]`::first_...` function family to get the first context of a specific type.
208    #[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// CreateAction
225#[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/// The common definition that wrap around all create contexts, for both request and response.
236///
237/// This is meant to be used within a [`ChainedItemList<T>`][smb_fscc::ChainedItemList<T>]!
238#[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>, // relative to ChainedItem (any access must consider +CHAINED_ITEM_PREFIX_SIZE from start of item)
248    #[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/// This trait is automatically implemented for all
282#[doc = concat!("[`Create", stringify!($struct_name), "`]")]
283/// create context values.
284pub 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: "", // self.data.name(), TODO: Fix this by making name() a string.
349                }),
350            }
351        }
352    }
353)+
354
355pub type [<$struct_name CreateContext>] = CreateContext<[<CreateContext $struct_name Data>]>;
356        }
357    }
358}
359
360/// Internal macro to generate request/response context enums for create.
361macro_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
370/// This enum contains all the types of create contexts.
371pub 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    /// The data contains the extended attributes that MUST be stored on the created file.
419    exta: b"ExtA", ChainedItemList<FileFullEaInformation>;
420    /// The data contains a security descriptor that MUST be stored on the created file.
421    secd: b"SecD", SecurityDescriptor;
422    /// The client is requesting the open to be durable
423    dhnq: b"DHnQ", DurableHandleRequest, DurableHandleResponse;
424    /// The client is requesting to reconnect to a durable open after being disconnected
425    dhnc: b"DHNc", DurableHandleReconnect;
426    /// The data contains the required allocation size of the newly created file.
427    alsi: b"AlSi", AllocationSize;
428    /// The client is requesting that the server return maximal access information.
429    mxac: b"MxAc", QueryMaximalAccessRequest, QueryMaximalAccessResponse;
430    /// The client is requesting that the server open an earlier version of the file identified by the provided time stamp.
431    twrp: b"TWrp", TimewarpToken;
432    /// The client is requesting that the server return a 32-byte opaque BLOB that uniquely identifies the file being opened on disk.
433    qfid: b"QFid", QueryOnDiskIdReq, QueryOnDiskIdResp;
434    /// The client is requesting that the server return a lease. This value is only supported for the SMB 2.1 and 3.x dialect family.
435    rqls: b"RqLs", RequestLease, RequestLease; // v1+2, request & response are the same
436    /// The client is requesting the open to be durable. This value is only supported for the SMB 3.x dialect family.
437    dh2q: b"DH2Q", DurableHandleRequestV2, DH2QResp;
438    /// The client is requesting to reconnect to a durable open after being disconnected. This value is only supported for the SMB 3.x dialect family.
439    dh2c: b"DH2C", DurableHandleReconnectV2;
440    /// The client is supplying an identifier provided by an application instance while opening a file. This value is only supported for the SMB 3.x dialect family.
441    appinstid: b"\x45\xBC\xA6\x6A\xEF\xA7\xF7\x4A\x90\x08\xFA\x46\x2E\x14\x4D\x74", AppInstanceId, AppInstanceId;
442    /// The client is supplying a version to correspond to the application instance identifier.  This value is only supported for SMB 3.1.1 dialect.
443    appinstver: b"\xB9\x82\xD0\xB7\x3B\x56\x07\x4F\xA0\x7B\x52\x4A\x81\x16\xA0\x10", AppInstanceVersion, AppInstanceVersion;
444    /// Provided by an application while opening a shared virtual disk file.
445    /// This Create Context value is not valid for the SMB 2.002, SMB 2.1, and SMB 3.0 dialects
446    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, // 0x2
562    #[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/// [MS-RSVD sections 2.2.4.12 and 2.2.4.32.](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rsvd/6ec20c83-a6a7-49d5-ae60-72070f91d5e0)
607#[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    // MS-SMB2, 2.2.14.2.5: "MaximalAccess field is valid only if QueryStatus is STATUS_SUCCESS.
650    // he status code MUST be one of those defined in [MS-ERREF] section 2.3"
651    /// Use [`is_success()`][QueryMaximalAccessResponse::is_success] to check if the query was successful.
652    pub query_status: Status,
653
654    /// The maximal access mask for the opened file.
655    ///
656    /// Use [`access_mask()`][QueryMaximalAccessResponse::access_mask] to get the access mask if the query was successful.
657    pub maximal_access: FileAccessMask,
658}
659
660impl QueryMaximalAccessResponse {
661    /// Returns true if the query was successful.
662    pub fn is_success(&self) -> bool {
663        self.query_status == Status::Success
664    }
665
666    /// Returns the maximal access mask if the query was successful.
667    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, // Server 2016 IPC$ bug
822                    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    /*
838    Tests to add for contexts:
839    dhnc: b"DHNc", DurableHandleReconnect, DurableHandleReconnect,
840    dh2c: b"DH2C", DurableHandleReconnectV2, DurableHandleReconnectV2,
841    appinstid: b"\x45\xBC\xA6\x6A\xEF\xA7\xF7\x4A\x90\x08\xFA\x46\x2E\x14\x4D\x74", AppInstanceId, AppInstanceId,
842    appinstver: b"\xB9\x82\xD0\xB7\x3B\x56\x07\x4F\xA0\x7B\x52\x4A\x81\x16\xA0\x10", AppInstanceVersion, AppInstanceVersion,
843    svhdxopendev: b"\x9C\xCB\xCF\x9E\x04\xC1\xE6\x43\x98\x0E\x15\x8D\xA1\xF6\xEC\x83", SvhdxOpenDeviceContext, SvhdxOpenDeviceContext,
844     */
845
846    use smb_dtyp::guid;
847    use smb_tests::*;
848    use time::macros::datetime;
849
850    // Tests for the following contexts are not implemented here:
851    // - ExtA - already tested in smb-fscc & query info/ea tests
852    // - SecD - already tested in smb-dtyp tests
853
854    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    // TODO(TEST): RqLsV1
887    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}