smb_msg/
plain.rs

1use binrw::prelude::*;
2
3use super::header::*;
4use super::*;
5
6/// Makes the [`RequestContent`] & [`ResponseContent`] methods
7macro_rules! make_content_impl {
8    (
9        $struct_name:ident,
10        $({$variant:ident, $struct_type:ty},)+
11    ) => {
12        pastey::paste! {
13
14impl $struct_name {
15    /// Returns the name of the content value.
16    pub fn content_name(&self) -> &'static str {
17        use $struct_name::*;
18        match self {
19            $(
20                [<$variant>](_) => stringify!([<$variant>]),
21            )+
22        }
23    }
24
25    $(
26        #[doc = concat!("Attempts to cast the current content type to [", stringify!($struct_type),"].")]
27        pub fn [<to_ $variant:lower>](self) -> crate::Result<$struct_type> {
28            match self {
29                $struct_name::[<$variant>](req) => Ok(req),
30                _ => Err(crate::SmbMsgError::UnexpectedContent{
31                    expected: stringify!([<$variant>]),
32                    actual: self.content_name(),
33                }),
34            }
35        }
36
37        #[doc = concat!("Attempts to cast the current content type to [", stringify!($struct_type),"].")]
38        pub fn [<as_ $variant:lower>](&self) -> crate::Result<&$struct_type> {
39            match self {
40                $struct_name::[<$variant>](req) => Ok(req),
41                _ => Err(crate::SmbMsgError::UnexpectedContent{
42                    expected: stringify!([<$variant>]),
43                    actual: self.content_name(),
44                }),
45            }
46        }
47
48        #[doc = concat!("Attempts to cast the current content type to [", stringify!($struct_type),"].")]
49        pub fn [<as_mut_ $variant:lower>](&mut self) -> crate::Result<&mut $struct_type> {
50            match self {
51                $struct_name::[<$variant>](req) => Ok(req),
52                _ => Err(crate::SmbMsgError::UnexpectedContent{
53                    expected: stringify!([<$variant>]),
54                    actual: self.content_name(),
55                }),
56            }
57        }
58    )+
59}
60        }
61    };
62}
63
64/// Internal, one-use-macro to generate the request-response pairs for the [`RequestContent`] & [`ResponseContent`] enums.
65/// In addition, it appends the special cases.
66macro_rules! make_content {
67    (
68        $({$cmd:ident, $struct_pfx:ident},)+
69    ) => {
70        pastey::paste!{
71
72#[derive(BinRead, BinWrite, Debug)]
73#[brw(import(command: &Command))]
74#[brw(little)]
75pub enum RequestContent {
76    $(
77        #[br(pre_assert(matches!(command, Command::$cmd)))]
78        $cmd($struct_pfx::[<$cmd Request>]),
79    )*
80
81    // cancel request
82    #[br(pre_assert(matches!(command, Command::Cancel)))]
83    Cancel(cancel::CancelRequest),
84
85    // oplock
86    #[br(pre_assert(matches!(command, Command::OplockBreak)))]
87    OplockBreakAck(oplock::OplockBreakAck),
88    #[br(pre_assert(matches!(command, Command::OplockBreak)))]
89    LeaseBreakAck(oplock::LeaseBreakAck),
90}
91
92#[derive(BinRead, BinWrite, Debug)]
93#[brw(import(command: &Command))]
94#[brw(little)]
95pub enum ResponseContent {
96    $(
97        #[br(pre_assert(matches!(command, Command::$cmd)))]
98        $cmd($struct_pfx::[<$cmd Response>]),
99    )*
100
101    #[br(pre_assert(matches!(command, Command::OplockBreak)))]
102    OplockBreakNotify(oplock::OplockBreakNotify),
103    #[br(pre_assert(matches!(command, Command::OplockBreak)))]
104    LeaseBreakNotify(oplock::LeaseBreakNotify),
105    #[br(pre_assert(matches!(command, Command::OplockBreak)))]
106    OplockBreak(oplock::OplockBreakResponse),
107    #[br(pre_assert(matches!(command, Command::OplockBreak)))]
108    LeaseBreak(oplock::LeaseBreakResponse),
109
110    // server to client notification
111    #[br(pre_assert(matches!(command, Command::ServerToClientNotification)))]
112    ServerToClientNotification(notify::ServerToClientNotification),
113
114    // error response
115    Error(error::ErrorResponse),
116}
117
118impl RequestContent {
119    /// Get the command associated with this content.
120    pub fn associated_cmd(&self) -> Command {
121        use RequestContent::*;
122        match self {
123            $(
124                $cmd(_) => Command::$cmd,
125            )*
126
127            Cancel(_) => Command::Cancel,
128            OplockBreakAck(_)
129            | LeaseBreakAck(_) => Command::OplockBreak,
130        }
131    }
132}
133
134impl ResponseContent {
135    /// Get the command associated with this content.
136    pub fn associated_cmd(&self) -> Command {
137        use ResponseContent::*;
138        match self {
139            $(
140                $cmd(_) => Command::$cmd,
141            )*
142
143            | OplockBreakNotify(_)
144            | OplockBreak(_)
145            | LeaseBreakNotify(_)
146            | LeaseBreak(_) => Command::OplockBreak,
147            ServerToClientNotification(_) => Command::ServerToClientNotification,
148            Error(_) => panic!("Error has no matching command!"),
149        }
150    }
151}
152
153// Into<RequestContent> and Into<ResponseContent> implementations
154// for all the common requests/responses pairs.
155// the other type are a bit problematic, so they are currently
156// not implemented, but can be added later if needed.
157$(
158    impl From<$struct_pfx::[<$cmd Request>]>
159        for RequestContent
160    {
161        fn from(req: $struct_pfx::[<$cmd Request>]) -> Self {
162            RequestContent::$cmd(req)
163        }
164    }
165    impl From<$struct_pfx::[<$cmd Response>]>
166        for ResponseContent
167    {
168        fn from(resp: $struct_pfx::[<$cmd Response>]) -> Self {
169            ResponseContent::$cmd(resp)
170        }
171    }
172)+
173
174impl From<cancel::CancelRequest>
175    for RequestContent
176{
177    fn from(req: cancel::CancelRequest) -> Self {
178        RequestContent::Cancel(req)
179    }
180}
181
182make_content_impl!{
183    RequestContent,
184    $(
185        {$cmd, $struct_pfx::[<$cmd Request>]},
186    )+
187    {Cancel, cancel::CancelRequest},
188    {OplockBreakAck, oplock::OplockBreakAck},
189    {LeaseBreakAck, oplock::LeaseBreakAck},
190}
191
192make_content_impl!{
193    ResponseContent,
194    $(
195        {$cmd, $struct_pfx::[<$cmd Response>]},
196    )+
197    {OplockBreakNotify, oplock::OplockBreakNotify},
198    {LeaseBreakNotify, oplock::LeaseBreakNotify},
199    {OplockBreak, oplock::OplockBreakResponse},
200    {LeaseBreak, oplock::LeaseBreakResponse},
201    {ServerToClientNotification, notify::ServerToClientNotification},
202    {Error, error::ErrorResponse},
203}
204        }
205    };
206}
207
208make_content!(
209    {Negotiate, negotiate},
210    {SessionSetup, session_setup},
211    {Logoff, session_setup},
212    {TreeConnect, tree_connect},
213    {TreeDisconnect, tree_connect},
214    {Create, create},
215    {Close, create},
216    {Flush, file},
217    {Read, file},
218    {Write, file},
219    {Lock, lock},
220    {Ioctl, ioctl},
221    {Echo, echo},
222    {QueryDirectory, query_dir},
223    {ChangeNotify, notify},
224    {QueryInfo, info},
225    {SetInfo, info},
226);
227
228impl RequestContent {
229    /// If this is a request has a payload, it returns the size of it.
230    /// Otherwise, it returns 0.
231    ///
232    /// This method shall be used for calculating credits request & charge.
233    pub fn req_payload_size(&self) -> u32 {
234        use RequestContent::*;
235        match self {
236            // 3.3.5.13
237            Write(req) => req.length,
238            // 3.3.5.15: InputCount + OutputCount
239            Ioctl(req) => req.buffer.get_size() + req.max_output_response,
240            _ => 0,
241        }
242    }
243    /// If this is a request that expects a response with size,
244    /// it returns that expected size.
245    ///
246    /// This method shall be used for calculating credits request & charge.
247    pub fn expected_resp_size(&self) -> u32 {
248        use RequestContent::*;
249        match self {
250            // 3.3.5.12
251            Read(req) => req.length,
252            // 3.3.5.18
253            QueryDirectory(req) => req.output_buffer_length,
254            // 3.3.5.15: MaxInputCount + MaxOutputCount
255            Ioctl(req) => req.max_input_response + req.max_output_response,
256            _ => 0,
257        }
258    }
259}
260
261macro_rules! make_plain {
262    ($suffix:ident, $server_to_redir:literal, $binrw_attr:ty) => {
263        pastey::paste! {
264
265        /// A plain, single, SMB2 message.
266        #[$binrw_attr]
267        #[derive(Debug)]
268        #[brw(little)]
269        pub struct [<Plain $suffix>] {
270            #[brw(assert(header.flags.server_to_redir() == $server_to_redir))]
271            pub header: Header,
272            #[brw(args(&header.command))]
273            pub content: [<$suffix Content>],
274        }
275
276        impl [<Plain $suffix>] {
277            pub fn new(content: [<$suffix Content>]) -> [<Plain $suffix>] {
278                [<Plain $suffix>] {
279                    // default is a sync command, so `tree_id` must be set, and `HeaderFlags::async_command` is false
280                    header: Header {
281                        credit_charge: 0,
282                        status: Status::Success as u32,
283                        command: content.associated_cmd(),
284                        credit_request: 0,
285                        flags: HeaderFlags::new(),
286                        next_command: 0,
287                        message_id: u64::MAX,
288                        tree_id: Some(0),
289                        async_id: None,
290                        session_id: 0,
291                        signature: 0,
292                    },
293                    content,
294                }
295            }
296        }
297                }
298    };
299}
300
301make_plain!(Request, false, binrw::binwrite);
302make_plain!(Response, true, binrw::binread);
303
304/// Contains both tests and test helpers for other modules' tests requiring this module.
305#[cfg(test)]
306pub mod tests {
307    use std::io::Cursor;
308
309    use super::*;
310
311    /// Given a content, encode it into a Vec<u8> as if it were a full message,
312    /// But return only the content bytes.
313    ///
314    /// This is useful when encoding structs with offsets relative to the beginning of the SMB header.
315    pub fn encode_content(content: RequestContent) -> Vec<u8> {
316        let mut cursor = Cursor::new(Vec::new());
317        let msg = PlainRequest::new(content);
318        msg.write(&mut cursor).unwrap();
319        let bytes_of_msg = cursor.into_inner();
320        // We only want to return the content of the message, not the header. So cut the HEADER_SIZE bytes:
321        bytes_of_msg[Header::STRUCT_SIZE..].to_vec()
322    }
323
324    pub fn decode_content(bytes: &[u8]) -> PlainResponse {
325        let mut cursor = Cursor::new(bytes);
326        cursor.read_le().unwrap()
327    }
328}