smb_msg/ioctl/
msg.rs

1use super::{
2    common::{IoctlBuffer, IoctlRequestContent},
3    fsctl::*,
4};
5use binrw::io::TakeSeekExt;
6use binrw::prelude::*;
7use modular_bitfield::prelude::*;
8use smb_dtyp::binrw_util::prelude::*;
9use std::io::SeekFrom;
10
11use crate::{
12    FileId,
13    dfsc::{ReqGetDfsReferral, ReqGetDfsReferralEx, RespGetDfsReferral},
14};
15
16#[binrw::binrw]
17#[derive(Debug, PartialEq, Eq)]
18pub struct IoctlRequest {
19    #[bw(calc = 57)]
20    #[br(assert(struct_size == 57))]
21    struct_size: u16,
22    #[bw(calc = 0)]
23    _reserved: u16,
24    pub ctl_code: u32,
25    pub file_id: FileId,
26    #[bw(calc = PosMarker::default())]
27    _input_offset: PosMarker<u32>,
28    #[bw(calc = PosMarker::default())]
29    _input_count: PosMarker<u32>,
30    pub max_input_response: u32,
31    #[bw(calc = 0)]
32    #[br(assert(output_offset == 0))]
33    output_offset: u32,
34    #[bw(calc = 0)]
35    #[br(assert(output_count == 0))]
36    output_count: u32,
37    pub max_output_response: u32,
38    pub flags: IoctlRequestFlags,
39    #[bw(calc = 0)]
40    reserved2: u32,
41
42    #[bw(write_with = PosMarker::write_aoff_size, args(&_input_offset, &_input_count))]
43    #[br(map_stream = |s| s.take_seek(_input_count.value as u64), args(ctl_code, flags))]
44    pub buffer: IoctlReqData,
45}
46
47/// This is a helper trait that defines, for a certain FSCTL request type,
48/// the response type and their matching FSCTL code.
49pub trait FsctlRequest: for<'a> BinWrite<Args<'a> = ()> + Into<IoctlReqData> {
50    type Response: FsctlResponseContent;
51    const FSCTL_CODE: FsctlCodes;
52}
53
54macro_rules! ioctl_req_data {
55    ($($fsctl:ident: $model:ty, $response:ty, )+) => {
56        pastey::paste! {
57
58#[binrw::binrw]
59#[derive(Debug, PartialEq, Eq)]
60#[br(import(ctl_code: u32, flags: IoctlRequestFlags))]
61pub enum IoctlReqData {
62    $(
63        #[br(pre_assert(ctl_code == FsctlCodes::$fsctl as u32 && flags.is_fsctl()))]
64        [<Fsctl $fsctl:camel>]($model),
65    )+
66
67    /// General Ioctl request, providing a buffer as an input.
68    Ioctl(IoctlBuffer),
69}
70
71impl IoctlReqData {
72    pub fn get_size(&self) -> u32 {
73        use IoctlReqData::*;
74        match self {
75            $(
76                [<Fsctl $fsctl:camel>](data) => data.get_bin_size(),
77            )+
78            Ioctl(data) => data.len() as u32,
79        }
80    }
81}
82
83$(
84    impl FsctlRequest for $model {
85        type Response = $response;
86        const FSCTL_CODE: FsctlCodes = FsctlCodes::$fsctl;
87    }
88
89    impl From<$model> for IoctlReqData {
90        fn from(model: $model) -> IoctlReqData {
91            IoctlReqData::[<Fsctl $fsctl:camel>](model)
92        }
93    }
94)+
95        }
96    }
97}
98
99// TODO: Enable non-fsctl ioctls. currently, we only support FSCTLs.
100ioctl_req_data! {
101    PipePeek: PipePeekRequest, PipePeekResponse,
102    SrvEnumerateSnapshots: SrvEnumerateSnapshotsRequest, SrvEnumerateSnapshotsResponse,
103    SrvRequestResumeKey: SrvRequestResumeKeyRequest, SrvRequestResumeKey,
104    QueryNetworkInterfaceInfo: QueryNetworkInterfaceInfoRequest, NetworkInterfacesInfo,
105    SrvCopychunk: SrvCopychunkCopy, SrvCopychunkResponse,
106    SrvCopychunkWrite: SrvCopyChunkCopyWrite, SrvCopychunkResponse,
107    SrvReadHash: SrvReadHashReq, SrvReadHashRes,
108    LmrRequestResiliency: NetworkResiliencyRequest, LmrRequestResiliencyResponse,
109    ValidateNegotiateInfo: ValidateNegotiateInfoRequest, ValidateNegotiateInfoResponse,
110    DfsGetReferrals: ReqGetDfsReferral, RespGetDfsReferral,
111    PipeWait: PipeWaitRequest, PipeWaitResponse,
112    PipeTransceive: PipeTransceiveRequest, PipeTransceiveResponse,
113    SetReparsePoint: SetReparsePointRequest, SetReparsePointResponse,
114    DfsGetReferralsEx: ReqGetDfsReferralEx, RespGetDfsReferral,
115    FileLevelTrim: FileLevelTrimRequest, FileLevelTrimResponse,
116    QueryAllocatedRanges: QueryAllocRangesItem, QueryAllocRangesResult,
117    OffloadRead: OffloadReadRequest, OffloadReadResponse,
118}
119
120#[bitfield]
121#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
122#[bw(map = |&x| Self::into_bytes(x))]
123#[br(map = Self::from_bytes)]
124pub struct IoctlRequestFlags {
125    pub is_fsctl: bool,
126    #[skip]
127    __: B31,
128}
129
130#[binrw::binrw]
131#[derive(Debug, PartialEq, Eq)]
132pub struct IoctlResponse {
133    #[bw(calc = 49)]
134    #[br(assert(struct_size == 49))]
135    struct_size: u16,
136    #[bw(calc = 0)]
137    _reserved: u16,
138    pub ctl_code: u32,
139    pub file_id: FileId,
140    #[bw(calc = PosMarker::default())]
141    input_offset: PosMarker<u32>,
142    #[bw(assert(in_buffer.is_empty()))] // there is an exception for pass-through operations.
143    #[bw(try_calc = in_buffer.len().try_into())]
144    #[br(assert(input_count == 0))]
145    input_count: u32,
146
147    // is either (0) or (input_offset + input_count)
148    #[br(assert(output_offset.value == 0 || output_offset.value == input_offset.value + input_count))]
149    #[bw(calc = PosMarker::default())]
150    output_offset: PosMarker<u32>,
151    #[bw(try_calc = out_buffer.len().try_into())]
152    output_count: u32,
153
154    #[bw(calc = 0)] // reserved.
155    #[br(assert(flags == 0))]
156    flags: u32,
157    #[bw(calc = 0)]
158    reserved2: u32,
159
160    #[br(seek_before = SeekFrom::Start(input_offset.value.into()))]
161    #[br(count = input_count)]
162    #[bw(write_with = PosMarker::write_aoff, args(&input_offset))]
163    pub in_buffer: Vec<u8>,
164
165    #[br(seek_before = SeekFrom::Start(output_offset.value.into()))]
166    #[br(count = output_count)]
167    #[bw(write_with = PosMarker::write_aoff, args(&output_offset))]
168    pub out_buffer: Vec<u8>,
169}
170
171impl IoctlResponse {
172    /// Parses the response content into the specified type.
173    pub fn parse_fsctl<T>(&self) -> crate::Result<T>
174    where
175        T: FsctlResponseContent,
176    {
177        if !T::FSCTL_CODES.iter().any(|&f| f as u32 == self.ctl_code) {
178            return Err(crate::SmbMsgError::MissingFsctlDefinition(self.ctl_code));
179        }
180        let mut cursor = std::io::Cursor::new(&self.out_buffer);
181        Ok(T::read_le(&mut cursor).unwrap())
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use smb_tests::*;
188
189    use crate::*;
190
191    use super::*;
192
193    const REQ_IOCTL_BUFFER_CONTENT: &'static str = "0500000310000000980000000300000080000000010039000000000013f8a58f166fb54482c28f2dae140df50000000001000000000000000000020000000000010000000000000000000200000000000500000000000000010500000000000515000000173da72e955653f915dff280e9030000000000000000000000000000000000000000000001000000000000000000000002000000";
194
195    test_request! {
196        Ioctl {
197            ctl_code: FsctlCodes::PipeTransceive as u32,
198                file_id: [
199                    0x28, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x85, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0,
200                    0x0,
201                ]
202                .into(),
203                max_input_response: 0,
204                max_output_response: 1024,
205                flags: IoctlRequestFlags::new().with_is_fsctl(true),
206                buffer: IoctlReqData::FsctlPipeTransceive(
207                    IoctlBuffer::from(
208                        hex_to_u8_array! {REQ_IOCTL_BUFFER_CONTENT}
209                    ).into(),
210                ),
211        } => const_format::concatcp!("3900000017c01100280500000c000000850000000c0000007800000098000000000000000000000000000000000400000100000000000000", REQ_IOCTL_BUFFER_CONTENT)
212    }
213
214    // Just to make things pretty; do NOT edit.
215    const IOCTL_TEST_BUFFER_CONTENT: &'static str = "05000203100000000401000003000000ec00000001000000000002000000000001000000000000000000020000000000200000000000000001000000000000000c000e000000000000000200000000000000020000000000070000000000000000000000000000000600000000000000410056004900560056004d00000000000400000000000000010400000000000515000000173da72e955653f915dff28001000000000000000000020000000000010000000000000001000000000000000a000c00000000000000020000000000000000000000000006000000000000000000000000000000050000000000000061007600690076006e0000000100000000000000";
216
217    test_response! {
218        Ioctl {
219                ctl_code: FsctlCodes::PipeTransceive as u32,
220                file_id: [
221                    0x28, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x85, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0,
222                    0x0,
223                ]
224                .into(),
225                in_buffer: vec![],
226                out_buffer: smb_tests::hex_to_u8_array! {IOCTL_TEST_BUFFER_CONTENT},
227        } => const_format::concatcp!("3100000017c01100280500000c000000850000000c000000700000000000000070000000040100000000000000000000",IOCTL_TEST_BUFFER_CONTENT)
228    }
229}