smb_msg/
file.rs

1//! File-related messages: Flush, Read, Write.
2#[cfg(feature = "client")]
3use std::io::SeekFrom;
4
5use binrw::prelude::*;
6use modular_bitfield::prelude::*;
7use smb_msg_derive::*;
8
9use super::FileId;
10#[cfg(feature = "client")]
11use super::header::Header;
12use smb_dtyp::binrw_util::prelude::*;
13
14/// SMB2 FLUSH Request.
15///
16/// Used to flush cached file data to persistent storage.
17///
18/// Reference: MS-SMB2 2.2.17
19#[smb_request(size = 24)]
20pub struct FlushRequest {
21    reserved: u16,
22    reserved: u32,
23    /// File identifier for the file to flush.
24    pub file_id: FileId,
25}
26
27/// SMB2 FLUSH Response.
28///
29/// Sent by the server to confirm that data has been flushed.
30///
31/// Reference: MS-SMB2 2.2.18
32#[smb_response(size = 4)]
33#[derive(Default)]
34pub struct FlushResponse {
35    reserved: u16,
36}
37
38/// SMB2 READ Request.
39///
40/// Used to read data from a file or named pipe.
41///
42/// Reference: MS-SMB2 2.2.19
43#[smb_request(size = 49)]
44pub struct ReadRequest {
45    #[bw(calc = 0)]
46    #[br(temp)]
47    _padding: u8,
48    /// Read operation flags.
49    pub flags: ReadFlags,
50    /// Number of bytes to read.
51    pub length: u32,
52    /// Offset in the file to read from.
53    pub offset: u64,
54    /// File identifier for the file to read.
55    pub file_id: FileId,
56    /// Minimum number of bytes to read for the request to succeed.
57    pub minimum_count: u32,
58    #[bw(calc = CommunicationChannel::None)]
59    #[br(assert(channel == CommunicationChannel::None))]
60    #[br(temp)]
61    channel: CommunicationChannel,
62    #[bw(calc = 0)]
63    #[br(assert(_remaining_bytes == 0))]
64    #[br(temp)]
65    _remaining_bytes: u32,
66    #[bw(calc = 0)]
67    #[br(assert(_read_channel_info_offset == 0))]
68    #[br(temp)]
69    _read_channel_info_offset: u16,
70    #[bw(calc = 0)]
71    #[br(assert(_read_channel_info_length == 0))]
72    #[br(temp)]
73    _read_channel_info_length: u16,
74
75    // Well, that's a little awkward, but since we never provide a blob, and yet,
76    // Msft decided it makes sense to make the structure size 0x31, we need to add this padding.
77    #[bw(calc = 0)]
78    #[br(temp)]
79    _pad_blob_placeholder: u8,
80}
81
82/// SMB2 READ Response.
83///
84/// Sent by the server with the data read from the file.
85///
86/// Reference: MS-SMB2 2.2.20
87#[smb_response(size = 17)]
88pub struct ReadResponse {
89    #[br(assert(_data_offset.value as usize >= Header::STRUCT_SIZE + Self::STRUCT_SIZE - 1))]
90    #[bw(calc = PosMarker::default())]
91    #[br(temp)]
92    _data_offset: PosMarker<u8>,
93    reserved: u8,
94    #[bw(try_calc = buffer.len().try_into())]
95    #[br(assert(_data_length > 0))]
96    #[br(temp)]
97    _data_length: u32,
98    #[bw(calc = 0)]
99    #[br(assert(_data_remaining == 0))]
100    #[br(temp)]
101    _data_remaining: u32,
102
103    reserved: u32,
104
105    /// Data read from the file.
106    #[br(seek_before = SeekFrom::Start(_data_offset.value as u64))]
107    #[br(count = _data_length)]
108    #[bw(assert(!buffer.is_empty()))]
109    #[bw(write_with = PosMarker::write_aoff, args(&_data_offset))]
110    pub buffer: Vec<u8>,
111}
112
113impl ReadResponse {
114    pub const STRUCT_SIZE: usize = 17;
115}
116
117/// Flags for read operations.
118///
119/// Reference: MS-SMB2 2.2.19
120#[smb_dtyp::mbitfield]
121pub struct ReadFlags {
122    /// Bypass cache and read directly from disk.
123    pub read_unbuffered: bool,
124    /// Request compressed data.
125    pub read_compressed: bool,
126    #[skip]
127    __: B6,
128}
129
130/// Communication channel types for SMB Direct.
131///
132/// Reference: MS-SMB2 2.2.19
133#[smb_request_binrw]
134#[brw(repr(u32))]
135pub enum CommunicationChannel {
136    /// No RDMA channel.
137    None = 0,
138    /// SMB Direct v1.
139    RdmaV1 = 1,
140    /// SMB Direct v1 with invalidate.
141    RdmaV1Invalidate = 2,
142}
143
144/// SMB2 WRITE Request.
145///
146/// Used to write data to a file or named pipe.
147///
148/// Note: This is a zero-copy write where data is sent separately after the message.
149///
150/// Reference: MS-SMB2 2.2.21
151#[smb_request(size = 49)]
152#[allow(clippy::manual_non_exhaustive)]
153pub struct WriteRequest {
154    #[bw(calc = PosMarker::new(0))]
155    #[br(temp)]
156    _data_offset: PosMarker<u16>,
157
158    /// Number of bytes to write.
159    pub length: u32,
160    /// Offset in the file to write to.
161    pub offset: u64,
162    /// File identifier for the file to write.
163    pub file_id: FileId,
164    #[bw(calc = CommunicationChannel::None)]
165    #[br(temp)]
166    #[br(assert(channel == CommunicationChannel::None))]
167    pub channel: CommunicationChannel,
168    #[bw(calc = 0)]
169    #[br(temp)]
170    #[br(assert(_remaining_bytes == 0))]
171    _remaining_bytes: u32,
172    #[bw(calc = 0)]
173    #[br(temp)]
174    #[br(assert(_write_channel_info_offset == 0))]
175    _write_channel_info_offset: u16,
176    #[bw(calc = 0)]
177    #[br(temp)]
178    #[br(assert(_write_channel_info_length == 0))]
179    _write_channel_info_length: u16,
180    /// Write operation flags.
181    pub flags: WriteFlags,
182
183    #[bw(write_with = PosMarker::write_aoff, args(&_data_offset))]
184    _write_offset: (),
185}
186
187impl WriteRequest {
188    pub fn new(offset: u64, file_id: FileId, flags: WriteFlags, length: u32) -> Self {
189        Self {
190            length,
191            offset,
192            file_id,
193            flags,
194            _write_offset: (),
195        }
196    }
197}
198
199/// SMB2 WRITE Response.
200///
201/// Sent by the server to confirm that data has been written.
202///
203/// Reference: MS-SMB2 2.2.22
204#[smb_response(size = 17)]
205pub struct WriteResponse {
206    reserved: u16,
207
208    /// Number of bytes written.
209    pub count: u32,
210
211    /// remaining_bytes
212    reserved: u32,
213    /// write_channel_info_offset
214    reserved: u16,
215    /// write_channel_info_length
216    reserved: u16,
217}
218
219/// Flags for write operations.
220///
221/// Reference: MS-SMB2 2.2.21
222#[smb_dtyp::mbitfield]
223pub struct WriteFlags {
224    /// Bypass cache and write directly to disk.
225    pub write_unbuffered: bool,
226    /// Ensure data is written to persistent storage before response.
227    pub write_through: bool,
228    #[skip]
229    __: B30,
230}
231
232#[cfg(test)]
233mod tests {
234    use crate::*;
235
236    use super::*;
237
238    test_binrw_request! {
239        struct FlushRequest {
240            file_id: [
241                0x14, 0x04, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x51, 0x00, 0x10, 0x00, 0x0c, 0x00,
242                0x00, 0x00,
243            ]
244            .into(),
245        } => "1800000000000000140400000c000000510010000c000000"
246    }
247
248    test_binrw_response! {
249        struct FlushResponse {  } => "04 00 00 00"
250    }
251
252    test_request! {
253        Read {
254            flags: ReadFlags::new(),
255            length: 0x10203040,
256            offset: 0x5060708090a0b0c,
257            file_id: [
258                0x03, 0x03, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x0c, 0x00,
259                0x00, 0x00,
260            ]
261            .into(),
262            minimum_count: 1,
263        } => "31000000403020100c0b0a0908070605030300000c000000c50000000c0000000100000000000000000000000000000000"
264    }
265
266    test_response! {
267        Read {
268            buffer: b"bbbbbb".to_vec(),
269        } => "11005000060000000000000000000000626262626262"
270    }
271
272    test_request! {
273        Write {
274            offset: 0x1234abcd,
275            file_id: [
276                0x14, 0x04, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x51, 0x00, 0x10, 0x00, 0x0c, 0x00,
277                0x00, 0x00,
278            ]
279            .into(),
280            flags: WriteFlags::new(),
281            length: "MeFriend!THIS IS FINE!".as_bytes().to_vec().len() as u32,
282            _write_offset: (),
283        } => "3100700016000000cdab341200000000140400000c000000510010000c00000000000000000000000000000000000000"
284    }
285
286    test_binrw_response! {
287        struct WriteResponse { count: 0xbeefbaaf, } => "11000000afbaefbe0000000000000000"
288    }
289}