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        paste::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        paste::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
174make_content_impl!{
175    RequestContent,
176    $(
177        {$cmd, $struct_pfx::[<$cmd Request>]},
178    )+
179    {Cancel, cancel::CancelRequest},
180    {OplockBreakAck, oplock::OplockBreakAck},
181    {LeaseBreakAck, oplock::LeaseBreakAck},
182}
183
184make_content_impl!{
185    ResponseContent,
186    $(
187        {$cmd, $struct_pfx::[<$cmd Response>]},
188    )+
189    {OplockBreakNotify, oplock::OplockBreakNotify},
190    {LeaseBreakNotify, oplock::LeaseBreakNotify},
191    {OplockBreak, oplock::OplockBreakResponse},
192    {LeaseBreak, oplock::LeaseBreakResponse},
193    {ServerToClientNotification, notify::ServerToClientNotification},
194    {Error, error::ErrorResponse},
195}
196        }
197    };
198}
199
200make_content!(
201    {Negotiate, negotiate},
202    {SessionSetup, session_setup},
203    {Logoff, session_setup},
204    {TreeConnect, tree_connect},
205    {TreeDisconnect, tree_connect},
206    {Create, create},
207    {Close, create},
208    {Flush, file},
209    {Read, file},
210    {Write, file},
211    {Lock, lock},
212    {Ioctl, ioctl},
213    {Echo, echo},
214    {QueryDirectory, query_dir},
215    {ChangeNotify, notify},
216    {QueryInfo, info},
217    {SetInfo, info},
218);
219
220impl RequestContent {
221    /// If this is a request has a payload, it returns the size of it.
222    /// Otherwise, it returns 0.
223    ///
224    /// This method shall be used for calculating credits request & charge.
225    pub fn req_payload_size(&self) -> u32 {
226        use RequestContent::*;
227        match self {
228            // 3.3.5.13
229            Write(req) => req.length,
230            // 3.3.5.15: InputCount + OutputCount
231            Ioctl(req) => req.buffer.get_size() + req.max_output_response,
232            _ => 0,
233        }
234    }
235    /// If this is a request that expects a response with size,
236    /// it returns that expected size.
237    ///
238    /// This method shall be used for calculating credits request & charge.
239    pub fn expected_resp_size(&self) -> u32 {
240        use RequestContent::*;
241        match self {
242            // 3.3.5.12
243            Read(req) => req.length,
244            // 3.3.5.18
245            QueryDirectory(req) => req.output_buffer_length,
246            // 3.3.5.15: MaxInputCount + MaxOutputCount
247            Ioctl(req) => req.max_input_response + req.max_output_response,
248            _ => 0,
249        }
250    }
251}
252
253macro_rules! make_plain {
254    ($suffix:ident, $server_to_redir:literal, $binrw_attr:ty) => {
255        paste::paste! {
256
257        /// A plain, single, SMB2 message.
258        #[$binrw_attr]
259        #[derive(Debug)]
260        #[brw(little)]
261        pub struct [<Plain $suffix>] {
262            #[brw(assert(header.flags.server_to_redir() == $server_to_redir))]
263            pub header: Header,
264            #[brw(args(&header.command))]
265            pub content: [<$suffix Content>],
266        }
267
268        impl [<Plain $suffix>] {
269            pub fn new(content: [<$suffix Content>]) -> [<Plain $suffix>] {
270                [<Plain $suffix>] {
271                    header: Header {
272                        credit_charge: 0,
273                        status: Status::Success as u32,
274                        command: content.associated_cmd(),
275                        credit_request: 0,
276                        flags: HeaderFlags::new(),
277                        next_command: 0,
278                        message_id: u64::MAX,
279                        tree_id: Some(0),
280                        async_id: None,
281                        session_id: 0,
282                        signature: 0,
283                    },
284                    content,
285                }
286            }
287        }
288                }
289    };
290}
291
292make_plain!(Request, false, binrw::binwrite);
293make_plain!(Response, true, binrw::binread);
294
295/// Contains both tests and test helpers for other modules' tests requiring this module.
296#[cfg(test)]
297pub mod tests {
298    use std::io::Cursor;
299
300    use super::*;
301
302    /// Given a content, encode it into a Vec<u8> as if it were a full message,
303    /// But return only the content bytes.
304    ///
305    /// This is useful when encoding structs with offsets relative to the beginning of the SMB header.
306    pub fn encode_content(content: RequestContent) -> Vec<u8> {
307        let mut cursor = Cursor::new(Vec::new());
308        let msg = PlainRequest::new(content);
309        msg.write(&mut cursor).unwrap();
310        let bytes_of_msg = cursor.into_inner();
311        // We only want to return the content of the message, not the header. So cut the HEADER_SIZE bytes:
312        bytes_of_msg[Header::STRUCT_SIZE..].to_vec()
313    }
314
315    pub fn decode_content(bytes: &[u8]) -> PlainResponse {
316        let mut cursor = Cursor::new(bytes);
317        cursor.read_le().unwrap()
318    }
319}