smb_msg/ioctl/
fsctl.rs

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