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
182impl From<error::ErrorResponse>
183    for ResponseContent
184{
185    fn from(resp: error::ErrorResponse) -> Self {
186        ResponseContent::Error(resp)
187    }
188}
189
190make_content_impl!{
191    RequestContent,
192    $(
193        {$cmd, $struct_pfx::[<$cmd Request>]},
194    )+
195    {Cancel, cancel::CancelRequest},
196    {OplockBreakAck, oplock::OplockBreakAck},
197    {LeaseBreakAck, oplock::LeaseBreakAck},
198}
199
200make_content_impl!{
201    ResponseContent,
202    $(
203        {$cmd, $struct_pfx::[<$cmd Response>]},
204    )+
205    {OplockBreakNotify, oplock::OplockBreakNotify},
206    {LeaseBreakNotify, oplock::LeaseBreakNotify},
207    {OplockBreak, oplock::OplockBreakResponse},
208    {LeaseBreak, oplock::LeaseBreakResponse},
209    {ServerToClientNotification, notify::ServerToClientNotification},
210    {Error, error::ErrorResponse},
211}
212        }
213    };
214}
215
216make_content!(
217    {Negotiate, negotiate},
218    {SessionSetup, session_setup},
219    {Logoff, session_setup},
220    {TreeConnect, tree_connect},
221    {TreeDisconnect, tree_connect},
222    {Create, create},
223    {Close, create},
224    {Flush, file},
225    {Read, file},
226    {Write, file},
227    {Lock, lock},
228    {Ioctl, ioctl},
229    {Echo, echo},
230    {QueryDirectory, query_dir},
231    {ChangeNotify, notify},
232    {QueryInfo, info},
233    {SetInfo, info},
234);
235
236impl RequestContent {
237    /// If this is a request has a payload, it returns the size of it.
238    /// Otherwise, it returns 0.
239    ///
240    /// This method shall be used for calculating credits request & charge.
241    pub fn req_payload_size(&self) -> u32 {
242        use RequestContent::*;
243        match self {
244            // 3.3.5.13
245            Write(req) => req.length,
246            // 3.3.5.15: InputCount + OutputCount
247            Ioctl(req) => req.buffer.get_size() + req.max_output_response,
248            _ => 0,
249        }
250    }
251    /// If this is a request that expects a response with size,
252    /// it returns that expected size.
253    ///
254    /// This method shall be used for calculating credits request & charge.
255    pub fn expected_resp_size(&self) -> u32 {
256        use RequestContent::*;
257        match self {
258            // 3.3.5.12
259            Read(req) => req.length,
260            // 3.3.5.18
261            QueryDirectory(req) => req.output_buffer_length,
262            // 3.3.5.15: MaxInputCount + MaxOutputCount
263            Ioctl(req) => req.max_input_response + req.max_output_response,
264            _ => 0,
265        }
266    }
267}
268
269macro_rules! make_plain {
270    ($suffix:ident, $server_to_redir:literal, $binrw_attr:ty) => {
271        pastey::paste! {
272
273        /// A plain, single, SMB2 message.
274        #[$binrw_attr]
275        #[derive(Debug)]
276        #[brw(little)]
277        pub struct [<Plain $suffix>] {
278            #[brw(assert(header.flags.server_to_redir() == $server_to_redir))]
279            pub header: Header,
280            #[brw(args(&header.command))]
281            pub content: [<$suffix Content>],
282        }
283
284        impl [<Plain $suffix>] {
285            pub fn new(content: [<$suffix Content>]) -> [<Plain $suffix>] {
286                let cmd = content.associated_cmd();
287                Self::new_with_command(content, cmd)
288            }
289
290            pub fn new_with_command(content: [<$suffix Content>], command: Command) -> [<Plain $suffix>] {
291                [<Plain $suffix>] {
292                    // default is a sync command, so `tree_id` must be set, and `HeaderFlags::async_command` is false
293                    header: Header {
294                        credit_charge: 0,
295                        status: Status::Success as u32,
296                        command,
297                        credit_request: 0,
298                        flags: HeaderFlags::new(),
299                        next_command: 0,
300                        message_id: u64::MAX,
301                        tree_id: Some(0),
302                        async_id: None,
303                        session_id: 0,
304                        signature: 0,
305                    },
306                    content,
307                }
308            }
309        }
310                }
311    };
312}
313
314macro_rules! gen_req_resp {
315    ($req_attr:ty, $res_attr:ty) => {
316        make_plain!(Request, false, $req_attr);
317        make_plain!(Response, true, $res_attr);
318    };
319}
320
321#[cfg(all(feature = "server", feature = "client"))]
322gen_req_resp!(binrw::binrw, binrw::binrw);
323#[cfg(all(feature = "client", not(feature = "server")))]
324gen_req_resp!(binrw::binwrite, binrw::binread);
325#[cfg(all(feature = "server", not(feature = "client")))]
326gen_req_resp!(binrw::binread, binrw::binwrite);