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.size() 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 {size: SizedStringSize::bytes(name_length)})]
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    #[bw(write_with = PosMarker::write_size, args(&snap_shot_array_size))]
523    pub snap_shots: MultiWSz,
524}
525
526impl_fsctl_response!(SrvEnumerateSnapshots, SrvEnumerateSnapshotsResponse);
527
528/// [MS-FSCC 2.3.14](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/b949a580-d8db-439b-a791-17ddc7565c4b)
529#[binrw::binrw]
530#[derive(Debug, PartialEq, Eq)]
531pub struct FileLevelTrimResponse {
532    /// The number of input ranges that were processed.
533    pub num_ranges_processed: u32,
534}
535
536impl_fsctl_response!(FileLevelTrim, FileLevelTrimResponse);
537
538/// [MS-FSCC 2.3.41](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/5d41cf62-9ebc-4f62-b7d7-0d085552b6dd)
539#[binrw::binrw]
540#[derive(Debug, PartialEq, Eq)]
541pub struct OffloadReadRequest {
542    #[bw(calc = 0x20)]
543    #[br(assert(_size == 0x20))]
544    _size: u32,
545    /// The flags to be set for this operation. Currently, no flags are defined.
546    pub flags: u32,
547    /// Time to Live (TTL) value in milliseconds for the generated Token. A value of 0 indicates a default TTL interval.
548    pub token_time_to_live: u32,
549    #[bw(calc = 0)]
550    _reserved: u32,
551    /// the file offset, in bytes, of the start of a range of bytes in a file from which to generate the Token.
552    /// MUST be aligned to a logical sector boundary on the volume.
553    pub file_offset: u64,
554    /// the requested range of the file from which to generate the Token.
555    /// MUST be aligned to a logical sector boundary on the volume
556    pub copy_length: u64,
557}
558
559impl IoctlRequestContent for OffloadReadRequest {
560    fn get_bin_size(&self) -> u32 {
561        (size_of::<u32>() * 4 + size_of::<u64>() * 2) as u32
562    }
563}
564
565/// [MS-FSCC 2.3.42](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/b98a8325-e6ec-464a-bc1b-8216b74f5828)
566#[binrw::binrw]
567#[derive(Debug, PartialEq, Eq)]
568pub struct OffloadReadResponse {
569    #[bw(calc = 528)]
570    #[br(assert(_size == 528))]
571    _size: u32,
572
573    // Note: this is a reduction of the flags field.
574    /// The data beyond the current range is logically equivalent to zero.
575    pub all_zero_beyond_current_range: Boolean,
576    _padding: u8,
577    _padding2: u16,
578
579    /// contains the amount, in bytes, of data that the Token logically represents.
580    /// This value indicates a contiguous region of the file from the beginning of the requested offset in the input.
581    /// This value can be smaller than the CopyLength field specified in the request data element,
582    /// which indicates that less data was logically represented (logically read) with the Token than was requested.
583    pub transfer_length: u64,
584
585    /// The generated Token to be used as a representation of the data contained within the portion of the file specified in the input request.
586    /// The contents of this field MUST NOT be modified during subsequent operations.
587    pub token: [u8; 512], // TODO: Parse as STORAGE_OFFLOAD_TOKEN
588}
589
590impl_fsctl_response!(OffloadRead, OffloadReadResponse);
591
592/// This macro wraps an existing type into a newtype that implements the `IoctlRequestContent` trait.
593/// It also provides a constructor and implements `From` and `Deref` traits for the new type.
594///
595/// It's made so we can easily create new types for ioctl requests without repeating boilerplate code,
596/// and prevents collisions with existing types in the `IoctlReqData` enum.
597macro_rules! make_newtype {
598    ($vis:vis $name:ident($inner:ty)) => {
599        #[binrw::binrw]
600        #[derive(Debug, PartialEq, Eq)]
601        pub struct $name(pub $inner);
602
603        impl $name {
604            pub fn new(inner: $inner) -> Self {
605                Self(inner)
606            }
607        }
608
609        impl From<$inner> for $name {
610            fn from(inner: $inner) -> Self {
611                Self(inner)
612            }
613        }
614
615        impl Deref for $name {
616            type Target = $inner;
617
618            fn deref(&self) -> &Self::Target {
619                &self.0
620            }
621        }
622
623        impl DerefMut for $name {
624            fn deref_mut(&mut self) -> &mut Self::Target {
625                &mut self.0
626            }
627        }
628    };
629}
630
631macro_rules! make_req_newtype {
632    ($vis:vis $name:ident($inner:ty)) => {
633        make_newtype!($vis $name($inner));
634        impl IoctlRequestContent for $name {
635            fn get_bin_size(&self) -> u32 {
636                self.0.get_bin_size()
637            }
638        }
639    }
640}
641
642macro_rules! make_res_newtype {
643    ($fsctl:ident: $vis:vis $name:ident($inner:ty)) => {
644        make_newtype!($vis $name($inner));
645        impl FsctlResponseContent for $name {
646            const FSCTL_CODES: &'static [FsctlCodes] = &[FsctlCodes::$fsctl];
647        }
648    }
649}
650
651make_req_newtype!(pub PipePeekRequest(()));
652make_req_newtype!(pub SrvEnumerateSnapshotsRequest(()));
653make_req_newtype!(pub SrvRequestResumeKeyRequest(()));
654make_req_newtype!(pub QueryNetworkInterfaceInfoRequest(()));
655make_req_newtype!(pub PipeTransceiveRequest(IoctlBuffer));
656make_req_newtype!(pub SrvCopyChunkCopyWrite(SrvCopychunkCopy));
657
658make_res_newtype!(
659    PipeWait: pub PipeWaitResponse(())
660);
661make_res_newtype!(
662    PipeTransceive: pub PipeTransceiveResponse(IoctlBuffer)
663);
664make_res_newtype!(
665    SetReparsePoint: pub SetReparsePointResponse(())
666);
667
668make_res_newtype!(
669    LmrRequestResiliency: pub LmrRequestResiliencyResponse(())
670);
671
672#[cfg(test)]
673mod tests {
674    use super::*;
675    use smb_tests::*;
676
677    test_binrw! {
678        struct OffloadReadRequest {
679            flags: 0,
680            token_time_to_live: 0,
681            file_offset: 0,
682            copy_length: 10485760,
683        } => "2000000000000000000000000000000000000000000000000000a00000000000"
684    }
685
686    test_binrw! {
687        struct SrvRequestResumeKey {
688            resume_key: [
689                0x2d, 0x3, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x27, 0x11, 0x6a, 0x26, 0x30, 0xd2,
690                0xdb, 0x1, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
691            ],
692            context: vec![],
693        } => "2d0300001c00000027116a2630d2db01fffe00000000000000000000"
694    }
695
696    const CHUNK_SIZE: u32 = 1 << 20; // 1 MiB
697    const TOTAL_SIZE: u32 = 10417096;
698    const BLOCK_NUM: u32 = (TOTAL_SIZE + CHUNK_SIZE - 1) / CHUNK_SIZE;
699
700    test_binrw! {
701        struct SrvCopychunkCopy {
702            source_key: [
703                0x2d, 0x3, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x27, 0x11, 0x6a, 0x26, 0x30, 0xd2, 0xdb,
704                0x1, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
705            ],
706            chunks: (0..BLOCK_NUM).map(|i| SrvCopychunkItem {
707                source_offset: (i * CHUNK_SIZE) as u64,
708                target_offset: (i * CHUNK_SIZE) as u64,
709                length: if i == BLOCK_NUM - 1 {
710                    TOTAL_SIZE % CHUNK_SIZE
711                } else {
712                    CHUNK_SIZE
713                },
714            }).collect(),
715        } => "2d0300001c00000027116a2630d2db01fffe0000000000000a000000000000000
716        00000000000000000000000000000000000100000000000000010000000000000001000
717        00000000000010000000000000002000000000000000200000000000000010000000000
718        00000300000000000000030000000000000001000000000000000400000000000000040
719        00000000000000100000000000000050000000000000005000000000000000100000000
720        00000006000000000000000600000000000000010000000000000007000000000000000
721        70000000000000001000000000000000800000000000000080000000000000001000000
722        0000000009000000000000000900000000000c8f30e0000000000"
723    }
724
725    test_binrw! {
726        struct SrvCopychunkResponse {
727            chunks_written: 10,
728            chunk_bytes_written: 0,
729            total_bytes_written: 10417096,
730        } => "0a00000000000000c8f39e00"
731    }
732
733    test_binrw! {
734        struct QueryAllocRangesResult {
735            values: vec![
736                QueryAllocRangesItem {
737                    offset: 0,
738                    len: 4096,
739                },
740                QueryAllocRangesItem {
741                    offset: 8192,
742                    len: 46801,
743                },
744            ],
745        } => "000000000000000000100000000000000020000000000000d1b6000000000000"
746    }
747
748    test_binrw! {
749        NetworkInterfacesInfo: NetworkInterfacesInfo::from(vec![
750                NetworkInterfaceInfo {
751                    if_index: 2,
752                    capability: NetworkInterfaceCapability::new().with_rdma(true),
753                    link_speed: 1000000000,
754                    sockaddr: SocketAddrStorage::V4(SocketAddrStorageV4 {
755                        port: 0,
756                        address: 0xac10cc84u32.to_be(),
757                    })
758                },
759                NetworkInterfaceInfo {
760                    if_index: 2,
761                    capability: NetworkInterfaceCapability::new().with_rdma(true),
762                    link_speed: 1000000000,
763                    sockaddr: SocketAddrStorage::V6(SocketAddrStorageV6 {
764                        port: 0,
765                        flow_info: 0,
766                        address: 0xfe80000000000000020c29fffe9f8bf3u128.to_be(),
767                        scope_id: 0,
768                    })
769                },
770            ]) => "9800000002000000020000000000000000ca9a3b0000000002000000ac10cc8400000000000000
771            0000000000000000000000000000000000000000000000000000000000000000000000000000000000000
772            0000000000000000000000000000000000000000000000000000000000000000000000000000000000000
773            0000000000000000000000000000000000000000000000000000000000000000020000000200000000000
774            00000ca9a3b000000001700000000000000fe80000000000000020c29fffe9f8bf3000000000000000000
775            0000000000000000000000000000000000000000000000000000000000000000000000000000000000000
776            0000000000000000000000000000000000000000000000000000000000000000000000000000000000000
777            00000000000000000000"
778    }
779
780    // TODO(TEST): Add missing tests. Consider testing size calc as well.
781}