smb_msg/
create.rs

1//! Create & Close (files) requests and responses.
2
3use std::fmt::{Debug, Display};
4use std::io::Cursor;
5
6#[cfg(feature = "client")]
7use std::io::SeekFrom;
8
9use super::header::Status;
10use super::*;
11use binrw::io::TakeSeekExt;
12use binrw::prelude::*;
13use modular_bitfield::prelude::*;
14use smb_dtyp::SecurityDescriptor;
15use smb_dtyp::{Guid, binrw_util::prelude::*};
16use smb_fscc::*;
17use smb_msg_derive::*;
18
19/// 2.2.14.1: SMB2_FILEID
20#[binrw::binrw]
21#[derive(PartialEq, Eq, Clone, Copy, Default)]
22pub struct FileId {
23    pub persistent: u64,
24    pub volatile: u64,
25}
26
27impl FileId {
28    pub const EMPTY: FileId = FileId {
29        persistent: 0,
30        volatile: 0,
31    };
32    /// A file ID that is used to indicate that the file ID is not valid,
33    /// with setting all bits to 1 - {0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}.
34    pub const FULL: FileId = FileId {
35        persistent: u64::MAX,
36        volatile: u64::MAX,
37    };
38}
39
40impl From<[u8; 16]> for FileId {
41    fn from(data: [u8; 16]) -> Self {
42        let mut cursor = Cursor::new(data);
43        Self::read_le(&mut cursor).unwrap()
44    }
45}
46
47impl From<Guid> for FileId {
48    fn from(guid: Guid) -> Self {
49        let mut cursor = Cursor::new(Vec::new());
50        guid.write_le(&mut cursor).unwrap();
51        <Self as From<[u8; 16]>>::from(cursor.into_inner().try_into().unwrap())
52    }
53}
54
55impl Display for FileId {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        write!(f, "{{{:x}|{:x}}}", self.persistent, self.volatile)
58    }
59}
60
61impl Debug for FileId {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        write!(f, "FileId({})", self)
64    }
65}
66
67/// The SMB2 CREATE Request packet is sent by a client to request either creation of
68/// or access to a file. In case of a named pipe or printer, the server creates a new file.
69///
70/// Reference: MS-SMB2 2.2.13
71#[smb_request(size = 57)]
72pub struct CreateRequest {
73    /// SecurityFlags
74    reserved: u8,
75    /// The requested oplock level for this file open
76    pub requested_oplock_level: OplockLevel,
77    /// The impersonation level requested by the application issuing the create request
78    pub impersonation_level: ImpersonationLevel,
79    /// SmbCreateFlags
80    reserved: u64,
81    reserved: u64,
82    /// The level of access required for the file or pipe
83    pub desired_access: FileAccessMask,
84    /// File attributes to be applied when creating or opening the file
85    pub file_attributes: FileAttributes,
86    /// Specifies the sharing mode for the open
87    pub share_access: ShareAccessFlags,
88    /// Defines the action the server must take if the file already exists
89    pub create_disposition: CreateDisposition,
90    /// Options to be applied when creating or opening the file
91    pub create_options: CreateOptions,
92    #[bw(calc = PosMarker::default())]
93    #[br(temp)]
94    _name_offset: PosMarker<u16>,
95    #[bw(try_calc = name.size().try_into())]
96    #[br(temp)]
97    name_length: u16, // bytes
98    #[bw(calc = PosMarker::default())]
99    #[br(temp)]
100    _create_contexts_offset: PosMarker<u32>,
101    #[bw(calc = PosMarker::default())]
102    #[br(temp)]
103    _create_contexts_length: PosMarker<u32>,
104
105    /// The Unicode file name to be created or opened
106    #[brw(align_before = 8)]
107    #[bw(write_with = PosMarker::write_aoff, args(&_name_offset))]
108    #[br(args { size: SizedStringSize::bytes16(name_length) })]
109    pub name: SizedWideString,
110
111    /// The list of create contexts sent in this request.
112    /// Use the [`CreateContextRequestData`]`::first_...` function family to get the first context of a specific type.
113    #[brw(align_before = 8)]
114    #[br(map_stream = |s| s.take_seek(_create_contexts_length.value.into()))]
115    #[bw(write_with = PosMarker::write_roff_size, args(&_create_contexts_offset, &_create_contexts_length))]
116    pub contexts: ChainedItemList<CreateContextRequest, 8>,
117}
118
119/// The impersonation level requested by the application issuing the create request.
120///
121/// Reference: MS-SMB2 2.2.13
122#[smb_request_binrw]
123#[derive(Copy, Clone)]
124#[brw(repr(u32))]
125pub enum ImpersonationLevel {
126    /// The application-requested impersonation level is Anonymous
127    Anonymous = 0x0,
128    /// The application-requested impersonation level is Identification
129    Identification = 0x1,
130    /// The application-requested impersonation level is Impersonation
131    Impersonation = 0x2,
132    /// The application-requested impersonation level is Delegate
133    Delegate = 0x3,
134}
135
136/// Defines the action the server must take if the file already exists.
137/// For opening named pipes, this field can be set to any value and is ignored by the server.
138///
139/// Reference: MS-SMB2 2.2.13
140#[smb_request_binrw]
141#[derive(Copy, Clone, Default)]
142#[brw(repr(u32))]
143pub enum CreateDisposition {
144    /// If the file already exists, supersede it. Otherwise, create the file
145    Superseded = 0x0,
146    /// If the file already exists, return success; otherwise, fail the operation
147    #[default]
148    Open = 0x1,
149    /// If the file already exists, fail the operation; otherwise, create the file
150    Create = 0x2,
151    /// Open the file if it already exists; otherwise, create the file
152    OpenIf = 0x3,
153    /// Overwrite the file if it already exists; otherwise, fail the operation
154    Overwrite = 0x4,
155    /// Overwrite the file if it already exists; otherwise, create the file
156    OverwriteIf = 0x5,
157}
158
159/// Options to be applied when creating or opening the file.
160///
161/// Reference: MS-SMB2 2.2.13
162#[smb_dtyp::mbitfield]
163pub struct CreateOptions {
164    /// The file being created or opened is a directory file
165    pub directory_file: bool,
166    /// The server performs file write-through
167    pub write_through: bool,
168    /// Application intends to read or write at sequential offsets
169    pub sequential_only: bool,
170    /// File buffering is not performed on this open
171    pub no_intermediate_buffering: bool,
172
173    /// Should be set to 0 and is ignored by the server
174    pub synchronous_io_alert: bool,
175    /// Should be set to 0 and is ignored by the server
176    pub synchronous_io_nonalert: bool,
177    /// If the name matches an existing directory file, the server must fail the request
178    pub non_directory_file: bool,
179    #[skip]
180    __: bool,
181
182    /// Should be set to 0 and is ignored by the server
183    pub complete_if_oplocked: bool,
184    /// The caller does not understand how to handle extended attributes
185    pub no_ea_knowledge: bool,
186    /// Should be set to 0 and is ignored by the server
187    pub open_remote_instance: bool,
188    /// Application intends to read or write at random offsets
189    pub random_access: bool,
190
191    /// The file must be automatically deleted when the last open request is closed
192    pub delete_on_close: bool,
193    /// Should be set to 0 and the server must fail the request if set
194    pub open_by_file_id: bool,
195    /// The file is being opened for backup intent
196    pub open_for_backup_intent: bool,
197    /// The file cannot be compressed
198    pub no_compression: bool,
199
200    /// Should be set to 0 and is ignored by the server
201    pub open_requiring_oplock: bool,
202    /// Should be set to 0 and is ignored by the server
203    pub disallow_exclusive: bool,
204    #[skip]
205    __: B2,
206
207    /// Should be set to 0 and the server must fail the request if set
208    pub reserve_opfilter: bool,
209    /// If the file is a reparse point, open the reparse point itself
210    pub open_reparse_point: bool,
211    /// In HSM environment, the file should not be recalled from tertiary storage
212    pub open_no_recall: bool,
213    /// Open file to query for free space
214    pub open_for_free_space_query: bool,
215
216    #[skip]
217    __: B8,
218}
219
220/// Specifies the sharing mode for the open.
221///
222/// Reference: MS-SMB2 2.2.13
223#[smb_dtyp::mbitfield]
224pub struct ShareAccessFlags {
225    /// Other opens are allowed to read this file while this open is present
226    pub read: bool,
227    /// Other opens are allowed to write this file while this open is present
228    pub write: bool,
229    /// Other opens are allowed to delete or rename this file while this open is present
230    pub delete: bool,
231    #[skip]
232    __: B29,
233}
234
235/// The SMB2 CREATE Response packet is sent by the server to notify the client of
236/// the status of its SMB2 CREATE Request.
237///
238/// Reference: MS-SMB2 2.2.14
239#[smb_response(size = 89)]
240pub struct CreateResponse {
241    /// The oplock level that is granted to the client for this open
242    pub oplock_level: OplockLevel,
243    /// Response flags indicating properties of the opened file
244    pub flags: CreateResponseFlags,
245    /// The action taken in establishing the open
246    pub create_action: CreateAction,
247    /// The time when the file was created
248    pub creation_time: FileTime,
249    /// The time the file was last accessed
250    pub last_access_time: FileTime,
251    /// The time when data was last written to the file
252    pub last_write_time: FileTime,
253    /// The time when the file was last modified
254    pub change_time: FileTime,
255    /// The size, in bytes, of the data that is allocated to the file
256    pub allocation_size: u64,
257    /// The size, in bytes, of the file
258    pub endof_file: u64,
259    /// The attributes of the file
260    pub file_attributes: FileAttributes,
261    reserved: u32,
262    /// The identifier of the open to a file or pipe that was established
263    pub file_id: FileId,
264    // assert it's 8-aligned
265    #[br(assert(create_contexts_offset.value & 0x7 == 0))]
266    #[bw(calc = PosMarker::default())]
267    #[br(temp)]
268    create_contexts_offset: PosMarker<u32>, // from smb header start
269    #[bw(calc = PosMarker::default())]
270    #[br(temp)]
271    create_contexts_length: PosMarker<u32>, // bytes
272
273    /// The list of create contexts returned in this response.
274    /// Use the [`CreateContextResponseData`]`::first_...` function family to get the first context of a specific type.
275    #[br(seek_before = SeekFrom::Start(create_contexts_offset.value as u64))]
276    #[br(map_stream = |s| s.take_seek(create_contexts_length.value.into()))]
277    #[bw(write_with = PosMarker::write_roff_size, args(&create_contexts_offset, &create_contexts_length))]
278    pub create_contexts: ChainedItemList<CreateContextResponse, 8>,
279}
280
281/// Response flags indicating properties of the opened file.
282/// Only valid for SMB 3.x dialect family.
283///
284/// Reference: MS-SMB2 2.2.14
285#[smb_dtyp::mbitfield]
286pub struct CreateResponseFlags {
287    /// When set, indicates the last portion of the file path is a reparse point
288    pub reparsepoint: bool,
289    #[skip]
290    __: B7,
291}
292
293/// The action taken in establishing the open.
294///
295/// Reference: MS-SMB2 2.2.14
296#[smb_response_binrw]
297#[brw(repr(u32))]
298pub enum CreateAction {
299    /// An existing file was deleted and a new file was created in its place
300    Superseded = 0x0,
301    /// An existing file was opened
302    Opened = 0x1,
303    /// A new file was created
304    Created = 0x2,
305    /// An existing file was overwritten
306    Overwritten = 0x3,
307}
308
309macro_rules! create_context_half {
310    (
311        $struct_name:ident {
312            $(
313                $context_type:ident : $req_type:ty,
314            )+
315        }
316    ) => {
317    pastey::paste! {
318
319
320/// The common definition that wrap around all create contexts, for both request and response.
321/// Create contexts are used to pass additional information to the server or receive additional
322/// information from the server in the CREATE request and response.
323///
324/// This is meant to be used within a [`ChainedItemList<T>`][smb_fscc::ChainedItemList<T>]!
325///
326/// Reference: MS-SMB2 2.2.13, 2.2.14
327#[[<smb_ $struct_name:lower _binrw>]]
328#[bw(import(is_last: bool))]
329#[allow(clippy::manual_non_exhaustive)]
330pub struct [<CreateContext $struct_name:camel>]
331{
332    #[bw(calc = PosMarker::default())]
333    #[br(temp)]
334    _name_offset: PosMarker<u16>, // relative to ChainedItem (any access must consider +CHAINED_ITEM_PREFIX_SIZE from start of item)
335    #[bw(calc = u16::try_from(name.len()).unwrap())]
336    #[br(temp)]
337    name_length: u16,
338    reserved: u16,
339    #[bw(calc = PosMarker::default())]
340    #[br(temp)]
341    _data_offset: PosMarker<u16>,
342    #[bw(calc = PosMarker::default())]
343    #[br(temp)]
344    _data_length: PosMarker<u32>,
345
346    /// The name of the create context
347    #[brw(align_before = 8)]
348    #[br(count = name_length)]
349    #[br(seek_before = _name_offset.seek_from(_name_offset.value as u64 - CHAINED_ITEM_PREFIX_SIZE as u64))]
350    #[bw(write_with = PosMarker::write_roff_plus, args(&_name_offset, CHAINED_ITEM_PREFIX_SIZE as u64))]
351    pub name: Vec<u8>,
352
353    /// The data payload of the create context
354    #[bw(align_before = 8)]
355    #[br(assert(_data_offset.value % 8 == 0))]
356    #[bw(write_with = PosMarker::write_roff_size_b_plus, args(&_data_offset, &_data_length, &_name_offset, CHAINED_ITEM_PREFIX_SIZE as u64))]
357    #[br(seek_before = _name_offset.seek_from_if(_data_offset.value as u64 - CHAINED_ITEM_PREFIX_SIZE as u64, _data_length.value > 0))]
358    #[br(map_stream = |s| s.take_seek(_data_length.value.into()), args(&name))]
359    pub data: [<CreateContext $struct_name Data>],
360}
361
362/// This trait is automatically implemented for all
363#[doc = concat!("[`Create", stringify!($struct_name), "`]")]
364/// create context values.
365pub trait [<CreateContextData $struct_name Value>] : Into<[<CreateContext $struct_name:camel>]> {
366    const CONTEXT_NAME: &'static [u8];
367}
368
369#[doc = concat!("The [`Create", stringify!($struct_name), "`] Context data enum. ")]
370#[[<smb_ $struct_name:lower _binrw>]]
371#[br(import(name: &Vec<u8>))]
372pub enum [<CreateContext $struct_name Data>] {
373    $(
374        #[br(pre_assert(name.as_slice() == CreateContextType::[<$context_type:upper>].name()))]
375        [<$context_type:camel $struct_name>]($req_type),
376    )+
377}
378
379impl [<CreateContext $struct_name Data>] {
380    pub fn name(&self) -> &'static [u8] {
381        match self {
382            $(
383                Self::[<$context_type:camel $struct_name>](_) => CreateContextType::[<$context_type:upper _NAME>],
384            )+
385        }
386    }
387
388    $(
389        pub fn [<as_ $context_type:snake>](&self) -> Option<&$req_type> {
390            match self {
391                Self::[<$context_type:camel $struct_name>](a) => Some(a),
392                _ => None,
393            }
394        }
395
396        #[doc = concat!("Get the first `", stringify!($context_type), "` create context from the list, if any.")]
397        ///
398        /// _Note: this function is auto-generated by the `make_create_context!` macro._
399        pub fn [<first_ $context_type:snake>](val: &[[<CreateContext $struct_name:camel>]]) -> Option<&$req_type> {
400            for ctx in val {
401                if let Self::[<$context_type:camel $struct_name>](a) = &ctx.data {
402                    return Some(a);
403                }
404            }
405            None
406        }
407    )+
408}
409
410$(
411    impl [<CreateContextData $struct_name Value>] for $req_type {
412        const CONTEXT_NAME: &'static [u8] = CreateContextType::[<$context_type:upper _NAME>];
413    }
414
415    impl From<$req_type> for [<CreateContext $struct_name:camel>] {
416        fn from(req: $req_type) -> Self {
417            [<CreateContext $struct_name:camel>] {
418                name: <$req_type as [<CreateContextData $struct_name Value>]>::CONTEXT_NAME.to_vec(),
419                data: [<CreateContext $struct_name Data>]::[<$context_type:camel $struct_name>](req),
420            }
421        }
422    }
423
424    impl TryInto<$req_type> for [<CreateContext $struct_name:camel>] {
425        type Error = crate::SmbMsgError;
426        fn try_into(self) -> crate::Result<$req_type> {
427            match self.data {
428                [<CreateContext $struct_name Data>]::[<$context_type:camel $struct_name>](a) => Ok(a),
429                _ => Err(crate::SmbMsgError::UnexpectedContent {
430                    expected: stringify!($req_type),
431                    actual: "", // self.data.name(), TODO: Fix this by making name() a string.
432                }),
433            }
434        }
435    }
436)+
437        }
438    }
439}
440
441/// Internal macro to generate request/response context enums for create.
442macro_rules! make_create_context {
443    (
444        $(
445            $(#[doc = $docstring:literal])*
446            $context_type:ident : $class_name:literal, $req_type:ty $(, $res_type:ty)?;
447        )+
448    ) => {
449        pastey::paste!{
450
451/// This enum contains all the types of create contexts.
452pub enum CreateContextType {
453    $(
454        $(#[doc = $docstring])*
455        [<$context_type:upper>],
456    )+
457}
458
459impl CreateContextType {
460    $(
461        #[doc = concat!("The name for the `", stringify!($context_type), "` create context.")]
462        pub const [<$context_type:upper _NAME>]: &[u8] = $class_name;
463    )+
464
465    pub fn from_name(name: &[u8]) -> Option<CreateContextType> {
466        match name {
467            $(
468                Self::[<$context_type:upper _NAME>] => Some(Self::[<$context_type:upper>]),
469            )+
470            _ => None,
471        }
472    }
473
474    pub fn name(&self) -> &[u8] {
475        match self {
476            $(
477                Self::[<$context_type:upper>] => Self::[<$context_type:upper _NAME>],
478            )+
479        }
480    }
481}
482        }
483
484        create_context_half! {
485            Request {
486                $($context_type: $req_type,)+
487            }
488        }
489
490        create_context_half! {
491            Response {
492                $($($context_type: $res_type,)?)+
493            }
494        }
495    }
496}
497
498make_create_context!(
499    /// The data contains the extended attributes that MUST be stored on the created file.
500    exta: b"ExtA", ChainedItemList<FileFullEaInformation>;
501    /// The data contains a security descriptor that MUST be stored on the created file.
502    secd: b"SecD", SecurityDescriptor;
503    /// The client is requesting the open to be durable
504    dhnq: b"DHnQ", DurableHandleRequest, DurableHandleResponse;
505    /// The client is requesting to reconnect to a durable open after being disconnected
506    dhnc: b"DHNc", DurableHandleReconnect;
507    /// The data contains the required allocation size of the newly created file.
508    alsi: b"AlSi", AllocationSize;
509    /// The client is requesting that the server return maximal access information.
510    mxac: b"MxAc", QueryMaximalAccessRequest, QueryMaximalAccessResponse;
511    /// The client is requesting that the server open an earlier version of the file identified by the provided time stamp.
512    twrp: b"TWrp", TimewarpToken;
513    /// The client is requesting that the server return a 32-byte opaque BLOB that uniquely identifies the file being opened on disk.
514    qfid: b"QFid", QueryOnDiskIdReq, QueryOnDiskIdResp;
515    /// 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.
516    rqls: b"RqLs", RequestLease, RequestLease; // v1+2, request & response are the same
517    /// The client is requesting the open to be durable. This value is only supported for the SMB 3.x dialect family.
518    dh2q: b"DH2Q", DurableHandleRequestV2, DH2QResp;
519    /// 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.
520    dh2c: b"DH2C", DurableHandleReconnectV2;
521    /// 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.
522    appinstid: b"\x45\xBC\xA6\x6A\xEF\xA7\xF7\x4A\x90\x08\xFA\x46\x2E\x14\x4D\x74", AppInstanceId, AppInstanceId;
523    /// The client is supplying a version to correspond to the application instance identifier.  This value is only supported for SMB 3.1.1 dialect.
524    appinstver: b"\xB9\x82\xD0\xB7\x3B\x56\x07\x4F\xA0\x7B\x52\x4A\x81\x16\xA0\x10", AppInstanceVersion, AppInstanceVersion;
525    /// Provided by an application while opening a shared virtual disk file.
526    /// This Create Context value is not valid for the SMB 2.002, SMB 2.1, and SMB 3.0 dialects
527    svhdxopendev: b"\x9C\xCB\xCF\x9E\x04\xC1\xE6\x43\x98\x0E\x15\x8D\xA1\xF6\xEC\x83", SvhdxOpenDeviceContext, SvhdxOpenDeviceContext;
528);
529
530/// Request for a durable handle that can survive brief network disconnections.
531///
532/// Reference: MS-SMB2 2.2.13.2.3
533#[smb_request_binrw]
534pub struct DurableHandleRequest {
535    reserved: u128,
536}
537
538/// Response indicating the server has marked the open as durable.
539///
540/// Reference: MS-SMB2 2.2.14.2.3
541#[smb_response_binrw]
542pub struct DurableHandleResponse {
543    reserved: u64,
544}
545
546/// Request to reestablish a durable open after being disconnected.
547///
548/// Reference: MS-SMB2 2.2.13.2.4
549#[smb_request_binrw]
550pub struct DurableHandleReconnect {
551    /// The file ID for the open that is being reestablished
552    pub durable_request: FileId,
553}
554/// Request for the server to retrieve maximal access information.
555///
556/// Reference: MS-SMB2 2.2.13.2.5
557#[smb_request_binrw]
558#[derive(Default)]
559pub struct QueryMaximalAccessRequest {
560    /// Optional timestamp for the query
561    #[br(parse_with = binread_if_has_data)]
562    pub timestamp: Option<FileTime>,
563}
564
565/// Specifies the allocation size for a newly created or overwritten file.
566///
567/// Reference: MS-SMB2 2.2.13.2.6
568#[smb_request_binrw]
569pub struct AllocationSize {
570    /// The size, in bytes, that the newly created file must have reserved on disk
571    pub allocation_size: u64,
572}
573
574/// Request to open a version of the file at a previous point in time.
575///
576/// Reference: MS-SMB2 2.2.13.2.7
577#[smb_request_binrw]
578pub struct TimewarpToken {
579    /// The timestamp of the version of the file to be opened
580    pub timestamp: FileTime,
581}
582
583/// Request for the server to return a lease on a file or directory.
584/// Also used by the server to respond with a granted lease.
585///
586/// Reference: MS-SMB2 2.2.13.2.8, 2.2.13.2.10, 2.2.14.2.10, 2.2.14.2.11
587#[smb_message_binrw]
588pub enum RequestLease {
589    RqLsReqv1(RequestLeaseV1),
590    RqLsReqv2(RequestLeaseV2),
591}
592
593/// Version 1 lease request and response (SMB 2.1 and 3.x dialect family).
594/// Contains the lease key, state, flags, and duration.
595///
596/// Reference: MS-SMB2 2.2.13.2.8, 2.2.14.2.10
597#[smb_message_binrw]
598pub struct RequestLeaseV1 {
599    /// Client-generated key that identifies the owner of the lease
600    pub lease_key: u128,
601    /// The requested lease state
602    pub lease_state: LeaseState,
603    #[bw(calc = 0)]
604    #[br(assert(lease_flags == 0))]
605    lease_flags: u32,
606    #[bw(calc = 0)]
607    #[br(assert(lease_duration == 0))]
608    lease_duration: u64,
609}
610
611/// Version 2 lease request and response (SMB 3.x dialect family only).
612/// Includes parent lease key and epoch tracking for lease state changes.
613///
614/// Reference: MS-SMB2 2.2.13.2.10, 2.2.14.2.11
615#[smb_message_binrw]
616pub struct RequestLeaseV2 {
617    /// Client-generated key that identifies the owner of the lease
618    pub lease_key: u128,
619    /// The requested lease state
620    pub lease_state: LeaseState,
621    /// Lease flags
622    pub lease_flags: LeaseFlags,
623    #[bw(calc = 0)]
624    #[br(assert(lease_duration == 0))]
625    lease_duration: u64,
626    /// Key that identifies the owner of the lease for the parent directory
627    pub parent_lease_key: u128,
628    /// Epoch value used to track lease state changes
629    pub epoch: u16,
630    reserved: u16,
631}
632
633/// Flags for lease requests and responses.
634///
635/// Reference: MS-SMB2 2.2.13.2.10, 2.2.14.2.10, 2.2.14.2.11
636#[smb_dtyp::mbitfield]
637pub struct LeaseFlags {
638    #[skip]
639    __: B2,
640    /// When set, indicates that the ParentLeaseKey is set
641    pub parent_lease_key_set: bool,
642    #[skip]
643    __: B29,
644}
645
646/// Request for the server to return an identifier for the open file.
647///
648/// Reference: MS-SMB2 2.2.13.2.9
649#[smb_request_binrw]
650pub struct QueryOnDiskIdReq;
651
652/// Request for a durable or persistent handle (SMB 3.x dialect family only).
653///
654/// Reference: MS-SMB2 2.2.13.2.11
655#[smb_request_binrw]
656pub struct DurableHandleRequestV2 {
657    /// Time in milliseconds for which the server reserves the handle after failover
658    pub timeout: u32,
659    /// Flags indicating whether a persistent handle is requested
660    pub flags: DurableHandleV2Flags,
661    reserved: u64,
662    /// GUID that identifies the create request
663    pub create_guid: Guid,
664}
665
666/// Flags for durable handle v2 requests.
667///
668/// Reference: MS-SMB2 2.2.13.2.11
669#[smb_dtyp::mbitfield]
670pub struct DurableHandleV2Flags {
671    #[skip]
672    __: bool,
673    /// When set, a persistent handle is requested
674    pub persistent: bool,
675    #[skip]
676    __: B30,
677}
678
679/// Request to reestablish a durable open (SMB 3.x dialect family only).
680///
681/// Reference: MS-SMB2 2.2.13.2.12
682#[smb_request_binrw]
683pub struct DurableHandleReconnectV2 {
684    /// The file ID for the open that is being reestablished
685    file_id: FileId,
686    /// Unique ID that identifies the create request
687    create_guid: Guid,
688    /// Flags indicating whether a persistent handle is requested
689    flags: DurableHandleV2Flags,
690}
691
692/// Application instance identifier (SMB 3.x dialect family only).
693///
694/// Reference: MS-SMB2 2.2.13.2.13
695#[smb_request_response(size = 20)]
696pub struct AppInstanceId {
697    reserved: u16,
698    /// Unique ID that identifies an application instance
699    pub app_instance_id: Guid,
700}
701
702/// Application instance version (SMB 3.1.1 dialect only).
703///
704/// Reference: MS-SMB2 2.2.13.2.15
705#[smb_request_response(size = 24)]
706pub struct AppInstanceVersion {
707    reserved: u16,
708    reserved: u16,
709    reserved: u32,
710    /// Most significant value of the version
711    pub app_instance_version_high: u64,
712    /// Least significant value of the version
713    pub app_instance_version_low: u64,
714}
715
716/// Context for opening a shared virtual disk file.
717///
718/// Reference: MS-SMB2 2.2.13.2.14, MS-RSVD 2.2.4.12, 2.2.4.32
719#[smb_message_binrw]
720pub enum SvhdxOpenDeviceContext {
721    V1(SvhdxOpenDeviceContextV1),
722    V2(SvhdxOpenDeviceContextV2),
723}
724
725/// Version 1 context for opening a shared virtual disk file.
726///
727/// Reference: MS-RSVD 2.2.4.12
728#[smb_message_binrw]
729pub struct SvhdxOpenDeviceContextV1 {
730    pub version: u32,
731    pub has_initiator_id: Boolean,
732    reserved: u8,
733    reserved: u16,
734    pub initiator_id: Guid,
735    pub flags: u32,
736    pub originator_flags: u32,
737    pub open_request_id: u64,
738    pub initiator_host_name_length: u16,
739    pub initiator_host_name: [u16; 126 / 2],
740}
741
742/// Version 2 context for opening a shared virtual disk file.
743///
744/// Reference: MS-RSVD 2.2.4.32
745#[smb_message_binrw]
746pub struct SvhdxOpenDeviceContextV2 {
747    pub version: u32,
748    pub has_initiator_id: Boolean,
749    reserved: u8,
750    reserved: u16,
751    pub initiator_id: Guid,
752    pub flags: u32,
753    pub originator_flags: u32,
754    pub open_request_id: u64,
755    pub initiator_host_name_length: u16,
756    pub initiator_host_name: [u16; 126 / 2],
757    pub virtual_disk_properties_initialized: u32,
758    pub server_service_version: u32,
759    pub virtual_sector_size: u32,
760    pub physical_sector_size: u32,
761    pub virtual_size: u64,
762}
763
764#[smb_response_binrw]
765pub struct QueryMaximalAccessResponse {
766    // MS-SMB2, 2.2.14.2.5: "MaximalAccess field is valid only if QueryStatus is STATUS_SUCCESS.
767    // he status code MUST be one of those defined in [MS-ERREF] section 2.3"
768    /// Use [`is_success()`][Self::is_success] to check if the query was successful.
769    pub query_status: Status,
770
771    /// The maximal access mask for the opened file.
772    ///
773    /// Use [`maximal_access()`][Self::maximal_access] to get the access mask if the query was successful.
774    pub maximal_access: FileAccessMask,
775}
776
777impl QueryMaximalAccessResponse {
778    /// Returns true if the query was successful.
779    pub fn is_success(&self) -> bool {
780        self.query_status == Status::Success
781    }
782
783    /// Returns the maximal access mask if the query was successful.
784    pub fn maximal_access(&self) -> Option<FileAccessMask> {
785        if self.is_success() {
786            Some(self.maximal_access)
787        } else {
788            None
789        }
790    }
791}
792
793/// Response containing disk file and volume identifiers for the opened file.
794///
795/// Reference: MS-SMB2 2.2.14.2.9
796#[smb_response_binrw]
797pub struct QueryOnDiskIdResp {
798    /// 64-bit file identifier for the open on disk
799    pub file_id: u64,
800    /// 64-bit volume identifier
801    pub volume_id: u64,
802    reserved: u128,
803}
804
805/// Response for SMB 3.x durable or persistent handle request.
806/// Indicates successful creation of a durable/persistent handle.
807///
808/// Reference: MS-SMB2 2.2.14.2.12
809#[smb_response_binrw]
810pub struct DH2QResp {
811    /// Time in milliseconds the server waits for client reconnect after failover
812    pub timeout: u32,
813    /// Flags indicating whether a persistent handle was granted
814    pub flags: DurableHandleV2Flags,
815}
816
817/// The SMB2 CLOSE Request packet is used by the client to close an instance of a file
818/// that was opened previously with a successful SMB2 CREATE Request.
819///
820/// Reference: MS-SMB2 2.2.15
821#[smb_request(size = 24)]
822pub struct CloseRequest {
823    #[bw(calc = CloseFlags::new().with_postquery_attrib(true))]
824    #[br(assert(_flags == CloseFlags::new().with_postquery_attrib(true)))]
825    _flags: CloseFlags,
826    reserved: u32,
827    /// The identifier of the open to a file or named pipe that is being closed
828    pub file_id: FileId,
829}
830
831/// The SMB2 CLOSE Response packet is sent by the server to indicate that an SMB2 CLOSE Request
832/// was processed successfully.
833///
834/// Reference: MS-SMB2 2.2.16
835#[smb_response(size = 60)]
836pub struct CloseResponse {
837    pub flags: CloseFlags,
838    reserved: u32,
839    /// The time when the file was created
840    pub creation_time: FileTime,
841    /// The time when the file was last accessed
842    pub last_access_time: FileTime,
843    /// The time when data was last written to the file
844    pub last_write_time: FileTime,
845    /// The time when the file was last modified
846    pub change_time: FileTime,
847    /// The size, in bytes, of the data that is allocated to the file
848    pub allocation_size: u64,
849    /// The size, in bytes, of the file
850    pub endof_file: u64,
851    /// The attributes of the file
852    pub file_attributes: FileAttributes,
853}
854
855/// Flags indicating how to process the CLOSE operation.
856///
857/// Reference: MS-SMB2 2.2.15, 2.2.16
858#[smb_dtyp::mbitfield]
859pub struct CloseFlags {
860    /// If set in request, the server MUST set the attribute fields in the response to valid values.
861    /// If set in response, the client MUST use the attribute fields that are returned.
862    pub postquery_attrib: bool,
863    #[skip]
864    __: B15,
865}
866
867#[cfg(test)]
868mod tests {
869    use crate::*;
870
871    use super::*;
872
873    test_request! {
874        Create {
875            requested_oplock_level: OplockLevel::None,
876            impersonation_level: ImpersonationLevel::Impersonation,
877            desired_access: FileAccessMask::from_bytes(0x00100081u32.to_le_bytes()),
878            file_attributes: FileAttributes::new(),
879            share_access: ShareAccessFlags::new()
880                .with_read(true)
881                .with_write(true)
882                .with_delete(true),
883            create_disposition: CreateDisposition::Open,
884            create_options: CreateOptions::new()
885                .with_synchronous_io_nonalert(true)
886                .with_disallow_exclusive(true),
887            name: "hello".into(),
888            contexts: vec![
889                DurableHandleRequestV2 {
890                    timeout: 0,
891                    flags: DurableHandleV2Flags::new(),
892                    create_guid: 0x821680290c007b8b11efc0a0c679a320u128.to_le_bytes().into(),
893                }
894                .into(),
895                QueryMaximalAccessRequest::default().into(),
896                QueryOnDiskIdReq.into(),
897            ]
898            .into(),
899        } => "390000000200000000000000000000000000000000000000810010000000000007000000010000002000020078000a008800000068
900        000000680065006c006c006f0000000000000038000000100004000000180020000000444832510000000000000000000000000000000000
901        00000020a379c6a0c0ef118b7b000c29801682180000001000040000001800000000004d7841630000000000000000100004000000180000
902        0000005146696400000000"
903    }
904
905    test_response! {
906        Create {
907                oplock_level: OplockLevel::None,
908                flags: CreateResponseFlags::new(),
909                create_action: CreateAction::Opened,
910                creation_time: 133783827154208828.into(),
911                last_access_time: 133797832406291912.into(),
912                last_write_time: 133783939554544738.into(),
913                change_time: 133783939554544738.into(),
914                allocation_size: 0,
915                endof_file: 0,
916                file_attributes: FileAttributes::new().with_directory(true),
917                file_id: 950737950337192747837452976457u128.to_le_bytes().into(),
918                create_contexts: vec![
919                    QueryMaximalAccessResponse {
920                        query_status: Status::Success,
921                        maximal_access: FileAccessMask::from_bytes(0x001f01ffu32.to_le_bytes()),
922                    }
923                    .into(),
924                    QueryOnDiskIdResp {
925                        file_id: 0x400000001e72a,
926                        volume_id: 0xb017cfd9,
927                    }
928                    .into(),
929                ]
930                .into()
931            } => "59000000010000003c083896ae4bdb01c8554b706b58db01620ccdc1c84bdb01620ccdc1c84bdb0100000000000000000000
932            0000000000001000000000000000490100000c000000090000000c0000009800000058000000200000001000040000001800080000
933            004d7841630000000000000000ff011f000000000010000400000018002000000051466964000000002ae7010000000400d9cf17b0
934            0000000000000000000000000000000000000000"
935    }
936
937    #[cfg(feature = "client")]
938    use smb_dtyp::make_guid;
939
940    test_response_read! {
941        server2016: Create {
942            oplock_level: OplockLevel::None,
943            flags: CreateResponseFlags::new(),
944            create_action: CreateAction::Opened,
945            creation_time: FileTime::ZERO,
946            last_access_time: FileTime::ZERO,
947            last_write_time: FileTime::ZERO,
948            change_time: FileTime::ZERO,
949            allocation_size: 4096,
950            endof_file: 0,
951            file_attributes: FileAttributes::new().with_normal(true),
952            file_id: make_guid!("00000001-0001-0000-0100-000001000000").into(),
953            create_contexts: vec![
954                QueryMaximalAccessResponse {
955                    query_status: Status::NotMapped, // Server 2016 IPC$ bug
956                    maximal_access: FileAccessMask::default(),
957                }
958                .into(),
959                QueryOnDiskIdResp {
960                    file_id: 0xffff870415d75290,
961                    volume_id: 0xffffe682cb589c90,
962                }
963                .into(),
964            ].into(),
965        } => "59000000010000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000
966        000000008000000076007300010000000100000001000000010000009800000058000000200000001000040000001800080000004d7841
967        6300000000730000c0000000000000000010000400000018002000000051466964000000009052d7150487ffff909c58cb82e6ffff0000
968        0000000000000000000000000000"
969    }
970
971    /*
972    Tests to add for contexts:
973    dhnc: b"DHNc", DurableHandleReconnect, DurableHandleReconnect,
974    dh2c: b"DH2C", DurableHandleReconnectV2, DurableHandleReconnectV2,
975    appinstid: b"\x45\xBC\xA6\x6A\xEF\xA7\xF7\x4A\x90\x08\xFA\x46\x2E\x14\x4D\x74", AppInstanceId, AppInstanceId,
976    appinstver: b"\xB9\x82\xD0\xB7\x3B\x56\x07\x4F\xA0\x7B\x52\x4A\x81\x16\xA0\x10", AppInstanceVersion, AppInstanceVersion,
977    svhdxopendev: b"\x9C\xCB\xCF\x9E\x04\xC1\xE6\x43\x98\x0E\x15\x8D\xA1\xF6\xEC\x83", SvhdxOpenDeviceContext, SvhdxOpenDeviceContext,
978     */
979
980    use smb_dtyp::guid;
981    use time::macros::datetime;
982
983    // Tests for the following contexts are not implemented here:
984    // - ExtA - already tested in smb-fscc & query info/ea tests
985    // - SecD - already tested in smb-dtyp tests
986
987    test_binrw_request! {
988        struct DurableHandleRequest {} => "00000000000000000000000000000000"
989    }
990
991    test_binrw_response! {
992        struct DurableHandleResponse {} => "0000000000000000"
993    }
994
995    test_binrw_request! {
996        struct QueryMaximalAccessRequest {
997            timestamp: None,
998        } => ""
999    }
1000
1001    test_binrw_response! {
1002        struct QueryMaximalAccessResponse {
1003            query_status: Status::Success,
1004            maximal_access: FileAccessMask::from_bytes(0x001f01ffu32.to_le_bytes()),
1005        } => "00000000ff011f00"
1006    }
1007
1008    test_binrw_request! {
1009        struct QueryOnDiskIdReq {} => ""
1010    }
1011
1012    test_binrw_response! {
1013        struct QueryOnDiskIdResp {
1014            file_id: 0x2ae7010000000400,
1015            volume_id: 0xd9cf17b000000000,
1016        } => "000400000001e72a 00000000b017cfd9 00000000000000000000000000000000"
1017    }
1018
1019    // TODO(TEST): RqLsV1
1020    test_binrw_request! {
1021        RequestLease => rqlsv2: RequestLease::RqLsReqv2(RequestLeaseV2 {
1022            lease_key: guid!("b69d8fd8-184b-7c4d-a359-40c8a53cd2b7").as_u128(),
1023            lease_state: LeaseState::new().with_read_caching(true).with_handle_caching(true),
1024            lease_flags: LeaseFlags::new().with_parent_lease_key_set(true),
1025            parent_lease_key: guid!("2d158ea3-55db-f749-9cd1-095496a06627").as_u128(),
1026            epoch: 0
1027        }) => "d88f9db64b184d7ca35940c8a53cd2b703000000040000000000000000000000a38e152ddb5549f79cd1095496a0662700000000"
1028    }
1029
1030    test_binrw_request! {
1031        struct AllocationSize {
1032            allocation_size: 0xebfef0d4c000,
1033        } => "00c0d4f0feeb0000"
1034    }
1035
1036    test_binrw_request! {
1037        struct DurableHandleRequestV2 {
1038            create_guid: guid!("5a08e844-45c3-234d-87c6-596d2bc8bca5"),
1039            flags: DurableHandleV2Flags::new(),
1040            timeout: 0,
1041        } => "0000000000000000000000000000000044e8085ac3454d2387c6596d2bc8bca5"
1042    }
1043
1044    test_binrw_response! {
1045        struct DH2QResp {
1046            timeout: 180000,
1047            flags: DurableHandleV2Flags::new(),
1048        } => "20bf020000000000"
1049    }
1050
1051    test_binrw_request! {
1052        struct TimewarpToken {
1053            timestamp: datetime!(2025-01-20 15:36:20.277632400).into(),
1054        } => "048fa10d516bdb01"
1055    }
1056
1057    test_binrw_request! {
1058        struct DurableHandleReconnectV2 {
1059            file_id: guid!("000000b3-0008-0000-dd00-000008000000").into(),
1060            create_guid: guid!("a23e428c-1bac-7e43-8451-91f9f2277a95"),
1061            flags: DurableHandleV2Flags::new(),
1062        } => "b300000008000000dd000000080000008c423ea2ac1b437e845191f9f2277a9500000000"
1063    }
1064}