smb_msg/ioctl/
fsctl.rs

1//! FSCTL codes and structs.
2#[cfg(feature = "client")]
3use binrw::io::TakeSeekExt;
4use binrw::{NullWideString, prelude::*};
5use modular_bitfield::prelude::*;
6use smb_dtyp::binrw_util::prelude::*;
7use smb_msg_derive::{smb_message_binrw, smb_request_binrw, smb_response_binrw};
8
9use crate::{Dialect, NegotiateSecurityMode};
10
11use crate::dfsc::{ReqGetDfsReferral, ReqGetDfsReferralEx, RespGetDfsReferral};
12use smb_dtyp::*;
13use smb_fscc::*;
14
15use super::common::IoctlRequestContent;
16use crate::IoctlBuffer;
17use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
18use std::ops::{Deref, DerefMut};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[repr(u32)]
22pub enum FsctlCodes {
23    DfsGetReferrals = 0x00060194,
24    OffloadRead = 0x00094264,
25    PipePeek = 0x0011400C,
26    PipeWait = 0x00110018,
27    PipeTransceive = 0x0011C017,
28    SrvCopychunk = 0x001440F2,
29    SrvEnumerateSnapshots = 0x00144064,
30    SrvRequestResumeKey = 0x00140078,
31    SrvReadHash = 0x001441bb,
32    SrvCopychunkWrite = 0x001480F2,
33    LmrRequestResiliency = 0x001401D4,
34    QueryNetworkInterfaceInfo = 0x001401FC,
35    SetReparsePoint = 0x000900A4,
36    DfsGetReferralsEx = 0x000601B0,
37    FileLevelTrim = 0x00098208,
38    ValidateNegotiateInfo = 0x00140204,
39    QueryAllocatedRanges = 0x000940CF,
40}
41
42/// Request packet for initiating a server-side copy of data.
43/// Sent in an SMB2 IOCTL Request using FSCTL_SRV_COPYCHUNK or FSCTL_SRV_COPYCHUNK_WRITE.
44///
45/// Reference: MS-SMB2 2.2.31.1
46#[smb_message_binrw]
47pub struct SrvCopychunkCopy {
48    /// A key representing the source file for the copy operation.
49    /// Obtained from the server in a SRV_REQUEST_RESUME_KEY Response.
50    pub source_key: [u8; SrvCopychunkCopy::SRV_KEY_LENGTH],
51    /// The number of chunks of data that are to be copied.
52    #[bw(try_calc = chunks.len().try_into())]
53    chunk_count: u32,
54    reserved: u32,
55    /// An array of SRV_COPYCHUNK packets describing the ranges to be copied.
56    /// The array length must equal chunk_count * size of SRV_COPYCHUNK.
57    #[br(count = chunk_count)]
58    pub chunks: Vec<SrvCopychunkItem>,
59}
60
61impl SrvCopychunkCopy {
62    pub const SRV_KEY_LENGTH: usize = 24;
63    pub const SIZE: usize = Self::SRV_KEY_LENGTH + 4 + 4;
64}
65
66/// Individual data range descriptor for server-side copy operations.
67/// Sent in the chunks array of a SRV_COPYCHUNK_COPY packet to describe an individual data range to copy.
68///
69/// Reference: MS-SMB2 2.2.31.1.1
70#[smb_message_binrw]
71pub struct SrvCopychunkItem {
72    /// The offset, in bytes, from the beginning of the source file to the location
73    /// from which the data will be copied.
74    pub source_offset: u64,
75    /// The offset, in bytes, from the beginning of the destination file to where
76    /// the data will be copied.
77    pub target_offset: u64,
78    /// The number of bytes of data to copy.
79    pub length: u32,
80    reserved: u32,
81}
82
83impl SrvCopychunkItem {
84    pub const SIZE: usize = size_of::<u64>() * 2 + size_of::<u32>() * 2;
85}
86
87impl IoctlRequestContent for SrvCopychunkCopy {
88    fn get_bin_size(&self) -> u32 {
89        (Self::SIZE + self.chunks.len() * SrvCopychunkItem::SIZE) as u32
90    }
91}
92
93/// Request packet for retrieving data from the Content Information File associated with a specified file.
94/// Sent in an SMB2 IOCTL Request using FSCTL_SRV_READ_HASH.
95/// The request is not valid for the SMB 2.0.2 dialect.
96///
97/// Reference: MS-SMB2 2.2.31.2
98#[smb_request_binrw]
99pub struct SrvReadHashReq {
100    /// The hash type of the request indicating what the hash is used for.
101    /// Must be set to SRV_HASH_TYPE_PEER_DIST for branch caching.
102    #[bw(calc = 1)]
103    #[br(assert(hash_type == 1))]
104    pub hash_type: u32,
105    /// The version number of the algorithm used to create the Content Information.
106    /// Must be set to version 1 (branch cache version 1) or version 2 (branch cache version 2).
107    /// Version 2 is only applicable for the SMB 3.x dialect family.
108    #[br(assert((1..=2).contains(&hash_version)))]
109    #[bw(assert((1..=2).contains(hash_version)))]
110    pub hash_version: u32,
111    /// Indicates the nature of the offset field and how it should be interpreted.
112    pub hash_retrieval_type: SrvHashRetrievalType,
113}
114
115impl IoctlRequestContent for SrvReadHashReq {
116    fn get_bin_size(&self) -> u32 {
117        size_of::<u32>() as u32 * 3
118    }
119}
120
121/// Enum specifying the nature of the offset field in SRV_READ_HASH requests.
122/// Determines how the offset field should be interpreted for hash retrieval.
123///
124/// Reference: MS-SMB2 2.2.31.2
125#[smb_request_binrw]
126#[brw(repr(u32))]
127pub enum SrvHashRetrievalType {
128    /// The offset field in the SRV_READ_HASH request is relative to the beginning
129    /// of the Content Information File.
130    HashBased = 1,
131    /// The offset field in the SRV_READ_HASH request is relative to the beginning
132    /// of the file indicated by the FileId field in the IOCTL request.
133    /// This value is only applicable for the SMB 3.x dialect family.
134    FileBased = 2,
135}
136
137/// Request packet for requesting resiliency for a specified open file.
138/// Sent in an SMB2 IOCTL Request using FSCTL_LMR_REQUEST_RESILIENCY.
139/// This request is not valid for the SMB 2.0.2 dialect.
140///
141/// Reference: MS-SMB2 2.2.31.3
142#[smb_request_binrw]
143pub struct NetworkResiliencyRequest {
144    /// The requested time the server holds the file open after a disconnect before releasing it.
145    /// This time is in milliseconds.
146    pub timeout: u32,
147    reserved: u32,
148}
149
150impl IoctlRequestContent for NetworkResiliencyRequest {
151    fn get_bin_size(&self) -> u32 {
152        size_of::<u32>() as u32 * 2
153    }
154}
155
156/// Request packet for validating a previous SMB 2 NEGOTIATE.
157/// Used in FSCTL_VALIDATE_NEGOTIATE_INFO to ensure the negotiation was not tampered with.
158/// Valid for clients and servers implementing SMB 3.0 and SMB 3.0.2 dialects.
159///
160/// Reference: MS-SMB2 2.2.31.4
161#[smb_request_binrw]
162pub struct ValidateNegotiateInfoRequest {
163    /// The capabilities of the client.
164    pub capabilities: u32,
165    /// The ClientGuid of the client.
166    pub guid: Guid,
167    /// The security mode of the client.
168    pub security_mode: NegotiateSecurityMode,
169    /// The number of entries in the dialects field.
170    #[bw(try_calc = dialects.len().try_into())]
171    dialect_count: u16,
172    /// The list of SMB2 dialects supported by the client.
173    /// These entries should contain only the dialect values defined in the negotiate request.
174    #[br(count = dialect_count)]
175    pub dialects: Vec<Dialect>,
176}
177
178impl IoctlRequestContent for ValidateNegotiateInfoRequest {
179    fn get_bin_size(&self) -> u32 {
180        (size_of::<u32>()
181            + Guid::GUID_SIZE
182            + 2
183            + size_of::<u16>()
184            + self.dialects.len() * size_of::<u16>()) as u32
185    }
186}
187
188/// Response packet containing snapshots associated with a share.
189/// Returned by the server in an SMB2 IOCTL Response for FSCTL_SRV_ENUMERATE_SNAPSHOTS request.
190/// Contains all revision timestamps associated with the Tree Connect share.
191///
192/// Reference: MS-SMB2 2.2.32.2
193#[smb_response_binrw]
194pub struct SrvSnapshotArray {
195    /// The number of previous versions associated with the volume that backs this file.
196    pub number_of_snap_shots: u32,
197    /// The number of previous version timestamps returned in the snapshots array.
198    /// If the output buffer could not accommodate the entire array, this will be zero.
199    pub number_of_snap_shots_returned: u32,
200    /// Position marker for the size of the snapshots array in bytes.
201    /// If the output buffer is too small, this will be the amount of space the array would have occupied.
202    #[bw(calc = PosMarker::default())]
203    #[br(temp)]
204    pub snap_shot_array_size: PosMarker<u32>,
205    /// An array of timestamps in GMT format (@GMT token), separated by UNICODE null characters
206    /// and terminated by two UNICODE null characters. Empty if the output buffer could not
207    /// accommodate the entire array.
208    #[br(parse_with = binrw::helpers::until_eof, map_stream = |s| s.take_seek(snap_shot_array_size.value as u64))]
209    #[bw(write_with = PosMarker::write_size, args(&snap_shot_array_size))]
210    pub snap_shots: Vec<NullWideString>,
211}
212
213#[cfg(all(feature = "client", not(feature = "server")))]
214/// A trait that helps parsing FSCTL responses by matching the FSCTL code.
215pub trait FsctlResponseContent: for<'a> BinRead<Args<'a> = ()> + std::fmt::Debug {
216    const FSCTL_CODES: &'static [FsctlCodes];
217}
218
219#[cfg(all(feature = "server", not(feature = "client")))]
220/// A trait that helps parsing FSCTL responses by matching the FSCTL code.
221pub trait FsctlResponseContent: for<'a> BinWrite<Args<'a> = ()> + std::fmt::Debug {
222    const FSCTL_CODES: &'static [FsctlCodes];
223}
224
225#[cfg(all(feature = "client", feature = "server"))]
226/// A trait that helps parsing FSCTL responses by matching the FSCTL code.
227pub trait FsctlResponseContent:
228    for<'a> BinRead<Args<'a> = ()> + for<'b> BinWrite<Args<'b> = ()> + std::fmt::Debug
229{
230    const FSCTL_CODES: &'static [FsctlCodes];
231}
232
233macro_rules! impl_fsctl_response {
234    ($code:ident, $type:ty) => {
235        impl FsctlResponseContent for $type {
236            const FSCTL_CODES: &'static [FsctlCodes] = &[FsctlCodes::$code];
237        }
238    };
239}
240
241/// Response packet containing a resume key for server-side copy operations.
242/// Returned by the server in an SMB2 IOCTL Response for FSCTL_SRV_REQUEST_RESUME_KEY request.
243/// The resume key can be used to uniquely identify the source file in subsequent copy operations.
244///
245/// Reference: MS-SMB2 2.2.32.3
246#[smb_response_binrw]
247pub struct SrvRequestResumeKey {
248    /// A 24-byte resume key generated by the server that can be used by the client
249    /// to uniquely identify the source file in FSCTL_SRV_COPYCHUNK or FSCTL_SRV_COPYCHUNK_WRITE requests.
250    /// The resume key must be treated as an opaque structure.
251    pub resume_key: [u8; SrvCopychunkCopy::SRV_KEY_LENGTH],
252    /// The length, in bytes, of the context information. This field is unused.
253    /// The server must set this field to zero, and the client must ignore it on receipt.
254    /// TODO: What?!
255    #[bw(calc = 0)]
256    #[br(temp)]
257    context_length: u32,
258    /// The context extended information. This should always be set to empty according to the specification.
259    #[br(count = context_length)]
260    #[bw(assert(context.len() == context_length as usize))]
261    pub context: Vec<u8>,
262}
263
264impl_fsctl_response!(SrvRequestResumeKey, SrvRequestResumeKey);
265
266/// Response packet for server-side copy operations.
267/// Returned by the server in an SMB2 IOCTL Response for FSCTL_SRV_COPYCHUNK or
268/// FSCTL_SRV_COPYCHUNK_WRITE requests to provide the results of the copy operation.
269///
270/// Reference: MS-SMB2 2.2.32.1
271#[smb_response_binrw]
272pub struct SrvCopychunkResponse {
273    /// For successful operations: the number of chunks that were successfully written.
274    /// For STATUS_INVALID_PARAMETER: the maximum number of chunks the server will accept.
275    pub chunks_written: u32,
276    /// For successful operations: the number of bytes written in the last chunk that
277    /// did not successfully process (if a partial write occurred).
278    /// For STATUS_INVALID_PARAMETER: the maximum number of bytes the server will allow
279    /// to be written in a single chunk.
280    pub chunk_bytes_written: u32,
281    /// For successful operations: the total number of bytes written in the server-side copy operation.
282    /// For STATUS_INVALID_PARAMETER: the maximum number of bytes the server will accept
283    /// to copy in a single request.
284    pub total_bytes_written: u32,
285}
286
287impl_fsctl_response!(SrvCopychunk, SrvCopychunkResponse);
288
289/// Response packet for SRV_READ_HASH requests.
290/// Returned by the server in an SMB2 IOCTL Response for FSCTL_SRV_READ_HASH request.
291/// The response is not valid for the SMB 2.0.2 dialect.
292///
293/// Reference: MS-SMB2 2.2.32.4
294#[smb_response_binrw]
295pub struct SrvReadHashRes {
296    /// The hash type of the response. Must be set to SRV_HASH_TYPE_PEER_DIST for branch caching.
297    #[bw(calc = 1)]
298    #[br(assert(hash_type == 1))]
299    hash_type: u32,
300    /// The version number of the algorithm used to create the Content Information.
301    /// Must be version 1 (branch cache version 1) or version 2 (branch cache version 2).
302    #[br(assert((1..=2).contains(&hash_version)))]
303    #[bw(assert((1..=2).contains(hash_version)))]
304    hash_version: u32,
305    /// The last change time of the source file.
306    source_file_change_time: FileTime,
307    /// The size of the source file in bytes.
308    source_file_size: u64,
309    /// Position marker for the length of the hash blob.
310    hash_blob_length: PosMarker<u32>,
311    /// Position marker for the offset of the hash blob.
312    hash_blob_offset: PosMarker<u32>,
313    /// Indicates whether the file has been modified since the Content Information was generated.
314    dirty: u16,
315    /// The length of the source file name in bytes.
316    #[bw(try_calc = source_file_name.len().try_into())]
317    source_file_name_length: u16,
318    /// The name of the source file.
319    #[br(count = source_file_name_length)]
320    source_file_name: Vec<u8>,
321}
322
323impl_fsctl_response!(SrvReadHash, SrvReadHashRes);
324
325/// Hash-based response format for SRV_READ_HASH when HashRetrievalType is SRV_HASH_RETRIEVE_HASH_BASED.
326/// Contains a portion of the Content Information File retrieved from a specified offset.
327///
328/// Reference: MS-SMB2 2.2.32.4.2
329#[smb_response_binrw]
330pub struct SrvHashRetrieveHashBased {
331    /// The offset, in bytes, from the beginning of the Content Information File
332    /// to the portion retrieved. This equals the offset field in the SRV_READ_HASH request.
333    pub offset: u64,
334    /// The length, in bytes, of the retrieved portion of the Content Information File.
335    #[bw(try_calc = blob.len().try_into())]
336    buffer_length: u32,
337    reserved: u32,
338    /// A variable-length buffer that contains the retrieved portion of the Content Information File.
339    /// TODO: Parse as Content Information File as specified in MS-PCCRC section 2.3.
340    #[br(count = buffer_length)]
341    blob: Vec<u8>,
342}
343
344impl_fsctl_response!(SrvReadHash, SrvHashRetrieveHashBased);
345
346/// File-based response format for SRV_READ_HASH when HashRetrievalType is SRV_HASH_RETRIEVE_FILE_BASED.
347/// Valid for servers implementing the SMB 3.x dialect family.
348/// Contains hash information for a specified range of file data.
349///
350/// Reference: MS-SMB2 2.2.32.4.3
351#[smb_response_binrw]
352pub struct SrvHashRetrieveFileBased {
353    /// File data offset corresponding to the start of the hash data returned.
354    pub file_data_offset: u64,
355    /// The length, in bytes, starting from the file_data_offset that is covered
356    /// by the hash data returned.
357    pub file_data_length: u64,
358    /// The length, in bytes, of the retrieved portion of the Content Information File.
359    #[bw(try_calc = buffer.len().try_into())]
360    buffer_length: u32,
361    reserved: u32,
362    /// A variable-length buffer that contains the retrieved portion of the Content Information File.
363    /// TODO: Parse as Content Information File as specified in MS-PCCRC section 2.4.
364    #[br(count = buffer_length)]
365    pub buffer: Vec<u8>,
366}
367
368pub type NetworkInterfacesInfo = ChainedItemList<NetworkInterfaceInfo>;
369
370impl_fsctl_response!(QueryNetworkInterfaceInfo, NetworkInterfacesInfo);
371
372/// Network interface information structure returned by FSCTL_QUERY_NETWORK_INTERFACE_INFO.
373/// Contains details about a specific network interface on the server.
374///
375/// Reference: MS-SMB2 2.2.32.5
376#[smb_response_binrw]
377pub struct NetworkInterfaceInfo {
378    /// The network interface index that specifies the network interface.
379    pub if_index: u32,
380    /// The capabilities of the network interface, including RSS and RDMA capability flags.
381    pub capability: NetworkInterfaceCapability,
382    reserved: u32,
383    /// The speed of the network interface in bits per second.
384    pub link_speed: u64,
385    /// Socket address information describing the network interface address.
386    /// Inlined sockaddr_storage for convenience and performance.
387    pub sockaddr: SocketAddrStorage,
388}
389
390/// Capability flags for network interfaces indicating supported features.
391/// Used in the NetworkInterfaceInfo structure to specify interface capabilities.
392///
393/// Reference: MS-SMB2 2.2.32.5
394#[smb_dtyp::mbitfield]
395pub struct NetworkInterfaceCapability {
396    /// When set, specifies that the interface is RSS (Receive Side Scaling) capable.
397    pub rss: bool,
398    /// When set, specifies that the interface is RDMA (Remote Direct Memory Access) capable.
399    pub rdma: bool,
400    #[skip]
401    __: B30,
402}
403
404#[smb_response_binrw]
405pub enum SocketAddrStorage {
406    V4(SocketAddrStorageV4),
407    V6(SocketAddrStorageV6),
408}
409
410impl SocketAddrStorage {
411    pub fn socket_addr(&self) -> SocketAddr {
412        match self {
413            SocketAddrStorage::V4(v4) => SocketAddr::V4(v4.to_addr()),
414            SocketAddrStorage::V6(v6) => SocketAddr::V6(v6.to_addr()),
415        }
416    }
417}
418
419#[smb_response_binrw]
420#[brw(magic(b"\x02\x00"))] // InterNetwork
421pub struct SocketAddrStorageV4 {
422    pub port: u16,
423    pub address: u32,
424    reserved: [u8; 128 - (2 + 2 + 4)],
425}
426
427impl SocketAddrStorageV4 {
428    fn to_addr(&self) -> SocketAddrV4 {
429        SocketAddrV4::new(Ipv4Addr::from(self.address.to_be()), self.port)
430    }
431}
432
433#[smb_response_binrw]
434#[brw(magic(b"\x17\x00"))] // InterNetworkV6
435pub struct SocketAddrStorageV6 {
436    pub port: u16,
437    pub flow_info: u32,
438    pub address: u128,
439    pub scope_id: u32,
440    reserved: [u8; 128 - (2 + 2 + 4 + 16 + 4)],
441}
442
443impl SocketAddrStorageV6 {
444    fn to_addr(&self) -> SocketAddrV6 {
445        SocketAddrV6::new(
446            Ipv6Addr::from(self.address.to_be()),
447            self.port,
448            self.flow_info,
449            self.scope_id,
450        )
451    }
452}
453
454/// Response for validating a previous SMB 2 NEGOTIATE.
455/// Returned in an SMB2 IOCTL response for FSCTL_VALIDATE_NEGOTIATE_INFO request.
456/// Valid for servers implementing the SMB 3.x dialect family, optional for others.
457///
458/// Reference: MS-SMB2 2.2.32.6
459#[smb_response_binrw]
460pub struct ValidateNegotiateInfoResponse {
461    /// The capabilities of the server.
462    pub capabilities: u32,
463    /// The ServerGuid of the server.
464    pub guid: Guid,
465    /// The security mode of the server.
466    pub security_mode: NegotiateSecurityMode,
467    /// The SMB2 dialect in use by the server on the connection.
468    pub dialect: Dialect,
469}
470
471impl_fsctl_response!(ValidateNegotiateInfo, ValidateNegotiateInfoResponse);
472
473// DFS get referrals FSCTLs.
474impl FsctlResponseContent for RespGetDfsReferral {
475    const FSCTL_CODES: &'static [FsctlCodes] =
476        &[FsctlCodes::DfsGetReferrals, FsctlCodes::DfsGetReferralsEx];
477}
478
479impl IoctlRequestContent for ReqGetDfsReferral {
480    fn get_bin_size(&self) -> u32 {
481        (size_of::<u16>() + (self.request_file_name.len() + 1) * size_of::<u16>()) as u32
482    }
483}
484
485impl IoctlRequestContent for ReqGetDfsReferralEx {
486    fn get_bin_size(&self) -> u32 {
487        (size_of::<u16>() * 2 + size_of::<u32>() + self.request_data.get_bin_size()) as u32
488    }
489}
490
491#[smb_message_binrw] // used as request, but also used in the response
492#[derive(Default)]
493pub struct QueryAllocRangesItem {
494    pub offset: u64,
495    pub len: u64,
496}
497
498impl IoctlRequestContent for QueryAllocRangesItem {
499    fn get_bin_size(&self) -> u32 {
500        (size_of::<u64>() * 2) as u32
501    }
502}
503
504#[smb_response_binrw]
505#[derive(Default)]
506pub struct QueryAllocRangesResult {
507    #[br(parse_with = binrw::helpers::until_eof)]
508    values: Vec<QueryAllocRangesItem>,
509}
510
511impl Deref for QueryAllocRangesResult {
512    type Target = Vec<QueryAllocRangesItem>;
513    fn deref(&self) -> &Self::Target {
514        &self.values
515    }
516}
517
518impl From<Vec<QueryAllocRangesItem>> for QueryAllocRangesResult {
519    fn from(value: Vec<QueryAllocRangesItem>) -> Self {
520        Self { values: value }
521    }
522}
523
524impl_fsctl_response!(QueryAllocatedRanges, QueryAllocRangesResult);
525
526/// The FSCTL_PIPE_WAIT Request requests that the server wait until either a time-out interval elapses,
527/// or an instance of the specified named pipe is available for connection.
528///
529/// [MS-FSCC 2.3.49](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/f030a3b9-539c-4c7b-a893-86b795b9b711)
530#[smb_request_binrw]
531pub struct PipeWaitRequest {
532    /// specifies the maximum amount of time, in units of 100 milliseconds,
533    /// that the function can wait for an instance of the named pipe to be available.
534    pub timeout: u64,
535    #[bw(calc = name.size() as u32)]
536    #[br(temp)]
537    name_length: u32,
538    /// Whether the Timeout parameter will be ignored.
539    /// FALSE Indicates that the server MUST wait forever. Any value in `timeout` must be ignored.
540    pub timeout_specified: Boolean,
541    /// Reserved (padding)
542    reserved: u8,
543    /// A Unicode string that contains the name of the named pipe. Name MUST not include the "\pipe\",
544    /// so if the operation was on \\server\pipe\pipename, the name would be "pipename".
545    #[br(args {size: SizedStringSize::bytes(name_length)})]
546    pub name: SizedWideString,
547}
548
549impl IoctlRequestContent for PipeWaitRequest {
550    fn get_bin_size(&self) -> u32 {
551        (size_of::<u64>()
552            + size_of::<u32>()
553            + size_of::<Boolean>()
554            + size_of::<u8>()
555            + self.name.size() as usize) as u32
556    }
557}
558
559/// Stores data for a reparse point.
560///
561/// [MS-FSCC 2.3.81](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/4dc2b168-f177-4eec-a14b-25a51cbba2cf)
562#[smb_request_binrw]
563pub struct SetReparsePointRequest {
564    /// Contains the reparse point tag that uniquely identifies the owner of the reparse point.
565    #[bw(assert((reparse_tag & 0x80000000 == 0) == reparse_guid.is_some()))]
566    pub reparse_tag: u32,
567    #[bw(calc = reparse_data.len() as u32)]
568    reparse_data_length: u32,
569    /// Applicable only for reparse points that have a GUID.
570    /// See [MS-FSCC 2.1.2.3](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/a4d08374-0e92-43e2-8f88-88b94112f070)
571    // Internal note: (HighBit(arseTag) == 0)Has
572    #[br(if(reparse_tag & 0x80000000 == 0))]
573    pub reparse_guid: Option<Guid>,
574    /// Reparse-specific data for the reparse point
575    #[br(count = reparse_data_length)]
576    pub reparse_data: Vec<u8>,
577}
578
579impl IoctlRequestContent for SetReparsePointRequest {
580    fn get_bin_size(&self) -> u32 {
581        (size_of::<u32>()
582            + size_of::<u32>()
583            + self.reparse_guid.as_ref().map_or(0, |_| size_of::<Guid>())
584            + self.reparse_data.len()) as u32
585    }
586}
587
588#[smb_request_binrw]
589pub struct FileLevelTrimRequest {
590    /// Key - reserved
591    reserved: u32,
592    #[bw(calc = ranges.len() as u32)]
593    num_ranges: u32,
594    /// Array of ranges that describe the portions of the file that are to be trimmed.
595    #[br(count = num_ranges)]
596    pub ranges: Vec<FileLevelTrimRange>,
597}
598
599/// [MSDN](https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ns-winioctl-file_level_trim_range)
600///
601/// Supports [`std::mem::size_of`].
602#[smb_request_binrw]
603pub struct FileLevelTrimRange {
604    /// Offset, in bytes, from the start of the file for the range to be trimmed.
605    pub offset: u64,
606    /// Length, in bytes, for the range to be trimmed.
607    pub length: u64,
608}
609
610impl IoctlRequestContent for FileLevelTrimRequest {
611    fn get_bin_size(&self) -> u32 {
612        (size_of::<u32>() + size_of::<u32>() + self.ranges.len() * size_of::<FileLevelTrimRange>())
613            as u32
614    }
615}
616
617/// [MS-FSCC 2.3.46](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/6b6c8b8b-c5ac-4fa5-9182-619459fce7c7)
618#[smb_response_binrw]
619pub struct PipePeekResponse {
620    /// The current state of the pipe
621    pub named_pipe_state: NamedPipeState,
622    #[bw(calc = data.len() as u32)]
623    /// The size, in bytes, of the data available to read from the pipe.
624    read_data_available: u32,
625    /// Specifies the number of messages available in the pipe if the pipe has been created as a message-type pipe. Otherwise, this field is 0
626    pub number_of_messages: u32,
627    /// Specifies the length of the first message available in the pipe if the pipe has been created as a message-type pipe. Otherwise, this field is 0.
628    pub message_length: u32,
629    /// The data from the pipe.
630    #[br(count = read_data_available as u64)]
631    pub data: Vec<u8>,
632}
633
634impl_fsctl_response!(PipePeek, PipePeekResponse);
635
636/// [MS-SMB 2.2.7.2.2.1](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb/5a43eb29-50c8-46b6-8319-e793a11f6226)
637#[smb_response_binrw]
638pub struct SrvEnumerateSnapshotsResponse {
639    /// The number of snapshots that the underlying object store contains of this file.
640    pub number_of_snap_shots: u32,
641    /// This value MUST be the number of snapshots that are returned in this response.
642    /// If this value is less than NumberofSnapshots,
643    /// then there are more snapshots than were able to fit in this response.
644    pub number_of_snap_shots_returned: u32,
645    /// The length, in bytes, of the SnapShotMultiSZ field.
646    #[bw(calc = PosMarker::default())]
647    #[br(temp)]
648    snap_shot_array_size: PosMarker<u32>,
649    /// A list of snapshots, described as strings, that take on the following form: @GMT-YYYY.MM.DD-HH.MM.SS
650    #[br(map_stream = |s| s.take_seek(snap_shot_array_size.value as u64))]
651    #[bw(write_with = PosMarker::write_size, args(&snap_shot_array_size))]
652    pub snap_shots: MultiWSz,
653}
654
655impl_fsctl_response!(SrvEnumerateSnapshots, SrvEnumerateSnapshotsResponse);
656
657/// [MS-FSCC 2.3.14](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/b949a580-d8db-439b-a791-17ddc7565c4b)
658#[smb_response_binrw]
659pub struct FileLevelTrimResponse {
660    /// The number of input ranges that were processed.
661    pub num_ranges_processed: u32,
662}
663
664impl_fsctl_response!(FileLevelTrim, FileLevelTrimResponse);
665
666/// [MS-FSCC 2.3.41](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/5d41cf62-9ebc-4f62-b7d7-0d085552b6dd)
667#[smb_request_binrw]
668pub struct OffloadReadRequest {
669    #[bw(calc = 0x20)]
670    #[br(assert(_size == 0x20))]
671    #[br(temp)]
672    _size: u32,
673    /// The flags to be set for this operation. Currently, no flags are defined.
674    pub flags: u32,
675    /// Time to Live (TTL) value in milliseconds for the generated Token. A value of 0 indicates a default TTL interval.
676    pub token_time_to_live: u32,
677    reserved: u32,
678    /// the file offset, in bytes, of the start of a range of bytes in a file from which to generate the Token.
679    /// MUST be aligned to a logical sector boundary on the volume.
680    pub file_offset: u64,
681    /// the requested range of the file from which to generate the Token.
682    /// MUST be aligned to a logical sector boundary on the volume
683    pub copy_length: u64,
684}
685
686impl IoctlRequestContent for OffloadReadRequest {
687    fn get_bin_size(&self) -> u32 {
688        (size_of::<u32>() * 4 + size_of::<u64>() * 2) as u32
689    }
690}
691
692/// [MS-FSCC 2.3.42](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/b98a8325-e6ec-464a-bc1b-8216b74f5828)
693#[smb_response_binrw]
694pub struct OffloadReadResponse {
695    #[bw(calc = 528)]
696    #[br(assert(_size == 528))]
697    _size: u32,
698
699    // Note: this is a reduction of the flags field.
700    /// The data beyond the current range is logically equivalent to zero.
701    pub all_zero_beyond_current_range: Boolean,
702    _padding: u8,
703    _padding2: u16,
704
705    /// contains the amount, in bytes, of data that the Token logically represents.
706    /// This value indicates a contiguous region of the file from the beginning of the requested offset in the input.
707    /// This value can be smaller than the CopyLength field specified in the request data element,
708    /// which indicates that less data was logically represented (logically read) with the Token than was requested.
709    pub transfer_length: u64,
710
711    /// The generated Token to be used as a representation of the data contained within the portion of the file specified in the input request.
712    /// The contents of this field MUST NOT be modified during subsequent operations.
713    pub token: [u8; 512], // TODO: Parse as STORAGE_OFFLOAD_TOKEN
714}
715
716impl_fsctl_response!(OffloadRead, OffloadReadResponse);
717
718/// This macro wraps an existing type into a newtype that implements the `IoctlRequestContent` trait.
719/// It also provides a constructor and implements `From` and `Deref` traits for the new type.
720///
721/// It's made so we can easily create new types for ioctl requests without repeating boilerplate code,
722/// and prevents collisions with existing types in the `IoctlReqData` enum.
723macro_rules! make_newtype {
724    ($attr_type:ident $vis:vis $name:ident($inner:ty)) => {
725        #[$attr_type]
726        pub struct $name(pub $inner);
727
728        impl $name {
729            pub fn new(inner: $inner) -> Self {
730                Self(inner)
731            }
732        }
733
734        impl From<$inner> for $name {
735            fn from(inner: $inner) -> Self {
736                Self(inner)
737            }
738        }
739
740        impl Deref for $name {
741            type Target = $inner;
742
743            fn deref(&self) -> &Self::Target {
744                &self.0
745            }
746        }
747
748        impl DerefMut for $name {
749            fn deref_mut(&mut self) -> &mut Self::Target {
750                &mut self.0
751            }
752        }
753    };
754}
755
756macro_rules! make_req_newtype {
757    ($vis:vis $name:ident($inner:ty)) => {
758        make_newtype!(smb_request_binrw $vis $name($inner));
759        impl IoctlRequestContent for $name {
760            fn get_bin_size(&self) -> u32 {
761                self.0.get_bin_size()
762            }
763        }
764    }
765}
766
767macro_rules! make_res_newtype {
768    ($fsctl:ident: $vis:vis $name:ident($inner:ty)) => {
769        make_newtype!(smb_response_binrw $vis $name($inner));
770        impl FsctlResponseContent for $name {
771            const FSCTL_CODES: &'static [FsctlCodes] = &[FsctlCodes::$fsctl];
772        }
773    }
774}
775
776make_req_newtype!(pub PipePeekRequest(()));
777make_req_newtype!(pub SrvEnumerateSnapshotsRequest(()));
778make_req_newtype!(pub SrvRequestResumeKeyRequest(()));
779make_req_newtype!(pub QueryNetworkInterfaceInfoRequest(()));
780make_req_newtype!(pub PipeTransceiveRequest(IoctlBuffer));
781make_req_newtype!(pub SrvCopyChunkCopyWrite(SrvCopychunkCopy));
782
783make_res_newtype!(
784    PipeWait: pub PipeWaitResponse(())
785);
786make_res_newtype!(
787    PipeTransceive: pub PipeTransceiveResponse(IoctlBuffer)
788);
789make_res_newtype!(
790    SetReparsePoint: pub SetReparsePointResponse(())
791);
792
793make_res_newtype!(
794    LmrRequestResiliency: pub LmrRequestResiliencyResponse(())
795);
796
797#[cfg(test)]
798mod tests {
799    use super::*;
800    use crate::*;
801
802    test_binrw_request! {
803        struct OffloadReadRequest {
804            flags: 0,
805            token_time_to_live: 0,
806            file_offset: 0,
807            copy_length: 10485760,
808        } => "2000000000000000000000000000000000000000000000000000a00000000000"
809    }
810
811    test_binrw_response! {
812        struct SrvRequestResumeKey {
813            resume_key: [
814                0x2d, 0x3, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x27, 0x11, 0x6a, 0x26, 0x30, 0xd2,
815                0xdb, 0x1, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
816            ],
817            context: vec![],
818        } => "2d0300001c00000027116a2630d2db01fffe00000000000000000000"
819    }
820
821    const CHUNK_SIZE: u32 = 1 << 20; // 1 MiB
822    const TOTAL_SIZE: u32 = 10417096;
823    const BLOCK_NUM: u32 = (TOTAL_SIZE + CHUNK_SIZE - 1) / CHUNK_SIZE;
824
825    test_binrw_request! {
826        struct SrvCopychunkCopy {
827            source_key: [
828                0x2d, 0x3, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x27, 0x11, 0x6a, 0x26, 0x30, 0xd2, 0xdb,
829                0x1, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
830            ],
831            chunks: (0..BLOCK_NUM).map(|i| SrvCopychunkItem {
832                source_offset: (i * CHUNK_SIZE) as u64,
833                target_offset: (i * CHUNK_SIZE) as u64,
834                length: if i == BLOCK_NUM - 1 {
835                    TOTAL_SIZE % CHUNK_SIZE
836                } else {
837                    CHUNK_SIZE
838                },
839            }).collect(),
840        } => "2d0300001c00000027116a2630d2db01fffe0000000000000a000000000000000
841        00000000000000000000000000000000000100000000000000010000000000000001000
842        00000000000010000000000000002000000000000000200000000000000010000000000
843        00000300000000000000030000000000000001000000000000000400000000000000040
844        00000000000000100000000000000050000000000000005000000000000000100000000
845        00000006000000000000000600000000000000010000000000000007000000000000000
846        70000000000000001000000000000000800000000000000080000000000000001000000
847        0000000009000000000000000900000000000c8f30e0000000000"
848    }
849
850    test_binrw_response! {
851        struct SrvCopychunkResponse {
852            chunks_written: 10,
853            chunk_bytes_written: 0,
854            total_bytes_written: 10417096,
855        } => "0a00000000000000c8f39e00"
856    }
857
858    test_binrw_response! {
859        struct QueryAllocRangesResult {
860            values: vec![
861                QueryAllocRangesItem {
862                    offset: 0,
863                    len: 4096,
864                },
865                QueryAllocRangesItem {
866                    offset: 8192,
867                    len: 46801,
868                },
869            ],
870        } => "000000000000000000100000000000000020000000000000d1b6000000000000"
871    }
872
873    test_binrw_response! {
874        NetworkInterfacesInfo: NetworkInterfacesInfo::from(vec![
875                NetworkInterfaceInfo {
876                    if_index: 2,
877                    capability: NetworkInterfaceCapability::new().with_rdma(true),
878                    link_speed: 1000000000,
879                    sockaddr: SocketAddrStorage::V4(SocketAddrStorageV4 {
880                        port: 0,
881                        address: 0xac10cc84u32.to_be(),
882                    })
883                },
884                NetworkInterfaceInfo {
885                    if_index: 2,
886                    capability: NetworkInterfaceCapability::new().with_rdma(true),
887                    link_speed: 1000000000,
888                    sockaddr: SocketAddrStorage::V6(SocketAddrStorageV6 {
889                        port: 0,
890                        flow_info: 0,
891                        address: 0xfe80000000000000020c29fffe9f8bf3u128.to_be(),
892                        scope_id: 0,
893                    })
894                },
895            ]) => "9800000002000000020000000000000000ca9a3b0000000002000000ac10cc8400000000000000
896            0000000000000000000000000000000000000000000000000000000000000000000000000000000000000
897            0000000000000000000000000000000000000000000000000000000000000000000000000000000000000
898            0000000000000000000000000000000000000000000000000000000000000000020000000200000000000
899            00000ca9a3b000000001700000000000000fe80000000000000020c29fffe9f8bf3000000000000000000
900            0000000000000000000000000000000000000000000000000000000000000000000000000000000000000
901            0000000000000000000000000000000000000000000000000000000000000000000000000000000000000
902            00000000000000000000"
903    }
904
905    // TODO(TEST): Add missing tests. Consider testing size calc as well.
906}