smb_msg/
plain.rs

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