Skip to main content

smb_server_proto_smb1/
rw.rs

1//! `WRITE_ANDX` / `READ_ANDX` codecs.
2//!
3//! WRITE_ANDX word layout per [MS-SMB] §2.2.4.3.1:
4//!
5//! ```text
6//! Offset  Size  Field
7//!  0       1    AndXCommand
8//!  1       1    AndXReserved
9//!  2       2    AndXOffset
10//!  4       2    FID
11//!  6       4    Offset
12//! 10       4    Timeout
13//! 14       2    WriteMode
14//! 16       2    Remaining
15//! 18       2    DataLengthHigh   ← [MS-SMB] extension
16//! 20       2    DataLength
17//! 22       2    DataOffset
18//! 24       4    OffsetHigh       (optional, when WC=14)
19//! ```
20
21use smb_server_proto::types::Status;
22
23// ---- Named field offsets into the Words area ----
24
25/// WRITE_ANDX parameter block field offsets ([MS-SMB] §2.2.4.3.1).
26pub mod write_offs {
27    /// AndXCommand byte offset.
28    pub const ANDX_COMMAND: usize = 0;
29    /// AndXReserved byte offset.
30    pub const ANDX_RESERVED: usize = 1;
31    /// AndXOffset byte offset.
32    pub const ANDX_OFFSET: usize = 2;
33    /// FID byte offset.
34    pub const FID: usize = 4;
35    /// Offset low u32 byte offset.
36    pub const OFFSET_LOW: usize = 6;
37    /// Timeout u32 byte offset.
38    pub const TIMEOUT: usize = 10;
39    /// WriteMode u16 byte offset.
40    pub const WRITE_MODE: usize = 14;
41    /// Remaining u16 byte offset.
42    pub const REMAINING: usize = 16;
43    /// DataLengthHigh u16 byte offset (always present in WC=14).
44    pub const DATA_LENGTH_HIGH: usize = 18;
45    /// DataLength u16 byte offset (always present after high part).
46    pub const DATA_LENGTH: usize = 20;
47    /// DataOffset u16 byte offset (always present after DataLength).
48    pub const DATA_OFFSET: usize = 22;
49    /// OffsetHigh u32 byte offset (optional, only in some WC=14 forms).
50    pub const OFFSET_HIGH: usize = 24;
51
52    /// Minimum word bytes for a valid WRITE_ANDX (WC=12 → 24 bytes).
53    pub const MIN_WORDS_BYTES: usize = 24;
54}
55
56/// READ_ANDX parameter block field offsets ([MS-CIFS] §2.2.4.43.1,
57/// extended by [MS-SMB] §2.2.4.x for large reads).
58pub mod read_offs {
59    /// FID byte offset.
60    pub const FID: usize = 4;
61    /// Offset low u32 byte offset.
62    pub const OFFSET_LOW: usize = 6;
63    /// MaxCount u16 byte offset.
64    pub const MAX_COUNT: usize = 10;
65    /// MinCount u16 byte offset.
66    pub const MIN_COUNT: usize = 12;
67    /// OffsetHigh u32 byte offset (only when WC ≥ 12).
68    pub const OFFSET_HIGH: usize = 20;
69}
70
71// ---- Helper readers ----
72
73fn g16(b: &[u8], off: usize) -> u16 {
74    b.get(off..off + 2)
75        .map(|s| u16::from_le_bytes([s[0], s[1]]))
76        .unwrap_or(0)
77}
78
79fn g32(b: &[u8], off: usize) -> u32 {
80    b.get(off..off + 4)
81        .map(|s| u32::from_le_bytes(s.try_into().unwrap()))
82        .unwrap_or(0)
83}
84
85// ---- WRITE_ANDX ----
86
87/// Parsed WRITE_ANDX request body.
88#[derive(Debug)]
89pub struct WriteReq {
90    /// Open file identifier.
91    pub fid: u16,
92    /// Absolute file offset.
93    pub offset: u64,
94    /// Write-through requested (`WriteMode` bit 0).
95    pub write_through: bool,
96    /// Payload bytes to write.
97    pub payload: Vec<u8>,
98}
99
100impl WriteReq {
101    /// Parse the WRITE_ANDX request body from words + full frame.
102    ///
103    /// Reads all fields by their spec-defined offsets; combines
104    /// `DataLengthHigh` and `DataLength` for the effective size, and
105    /// `OffsetLow` + `OffsetHigh` (when present) for the 64-bit offset.
106    pub fn parse(words: &[u8], frame: &[u8]) -> Result<WriteReq, Status> {
107        use write_offs as wo;
108
109        if words.len() < wo::MIN_WORDS_BYTES {
110            return Err(Status::INVALID_PARAMETER);
111        }
112
113        let fid = g16(words, wo::FID);
114        let offset_low = g32(words, wo::OFFSET_LOW);
115        let write_mode = g16(words, wo::WRITE_MODE);
116
117        // Effective data length combines high and low parts.
118        let len_high = g16(words, wo::DATA_LENGTH_HIGH) as usize;
119        let len_low = g16(words, wo::DATA_LENGTH) as usize;
120        let data_len = (len_high << 16) | len_low;
121
122        // Data offset is relative to the SMB header start.
123        let data_off = g16(words, wo::DATA_OFFSET) as usize;
124
125        // Optional 64-bit offset high part.
126        let mut offset = offset_low as u64;
127        if words.len() >= wo::OFFSET_HIGH + 4 {
128            offset |= (g32(words, wo::OFFSET_HIGH) as u64) << 32;
129        }
130
131        // Validate: payload must be within the frame.
132        let frame_len = frame.len();
133        if data_off < 32 || data_off + data_len > frame_len || data_len == 0 {
134            return Err(Status::INVALID_PARAMETER);
135        }
136
137        Ok(WriteReq {
138            fid,
139            offset,
140            write_through: write_mode & 0x0001 != 0,
141            payload: frame[data_off..data_off + data_len].to_vec(),
142        })
143    }
144}
145
146/// Build a successful WRITE_ANDX response (WC=6).
147///
148/// Fields: AndX triple(terminal), CountLow, Available, CountHigh, Reserved.
149pub fn write_response(written: u64) -> Vec<u8> {
150    let mut p = Vec::with_capacity(12);
151    p.push(0xFF); // terminal AndX
152    p.push(0);
153    p.extend_from_slice(&0u16.to_le_bytes()); // AndXOffset
154    p.extend_from_slice(&(written as u16).to_le_bytes()); // CountLow
155    p.extend_from_slice(&65535u16.to_le_bytes()); // Available
156    p.extend_from_slice(&((written >> 16) as u16).to_le_bytes()); // CountHigh
157    p.extend_from_slice(&0u16.to_le_bytes()); // Reserved
158    p
159}
160
161// ---- READ_ANDX ----
162
163/// Parsed READ_ANDX request body.
164#[derive(Debug)]
165pub struct ReadReq {
166    /// Open file identifier.
167    pub fid: u16,
168    /// Absolute file offset.
169    pub offset: u64,
170    /// Maximum bytes the client will accept.
171    pub max_count: usize,
172}
173
174impl ReadReq {
175    /// Parse the READ_ANDX request using spec-named offsets.
176    pub fn parse(words: &[u8]) -> Result<ReadReq, Status> {
177        use read_offs as ro;
178
179        if words.len() < ro::MIN_COUNT + 2 {
180            return Err(Status::INVALID_PARAMETER);
181        }
182
183        let fid = g16(words, ro::FID);
184        let mut offset = g32(words, ro::OFFSET_LOW) as u64;
185        let max_count = g16(words, ro::MAX_COUNT) as usize;
186
187        // 64-bit offset extension when WC is large enough.
188        if words.len() >= ro::OFFSET_HIGH + 4 {
189            let hi = g32(words, ro::OFFSET_HIGH);
190            offset |= (hi as u64) << 32;
191        }
192
193        Ok(ReadReq { fid, offset, max_count })
194    }
195}
196
197/// Build a READ_ANDX response (WC=12).
198///
199/// Layout per [MS-CIFS] §2.2.4.43.2 / [MS-SMB] extensions:
200/// AndX triple, Remaining, CompactionMode, Reserved, DataLength, DataOffset,
201/// DataLengthHigh(u32), Reserved(u32), Reserved tail(2B) = 24 bytes total.
202///
203/// Data starts after ByteCount, padded to even alignment.
204pub fn read_response(data: &[u8]) -> (Vec<u8>, Vec<u8>) {
205    // Named offsets into the response parameter block.
206    const PARAMS_LEN: usize = 24; // WC=12
207    const DATA_LEN_OFF: usize = 10;
208    const DATA_OFFS_OFF: usize = 12;
209
210    // Data area begins after header(32)+wcnt(1)+params(PARAMS_LEN)+bytecount(2).
211    let bc_end = 32 + 1 + PARAMS_LEN + 2;
212    let doff = (bc_end + 1) & !1usize; // even-aligned
213
214    let mut params = Vec::with_capacity(PARAMS_LEN);
215    params.push(0xFF); // terminal AndX
216    params.push(0);
217    params.extend_from_slice(&0u16.to_le_bytes()); // AndXOffset
218    params.extend_from_slice(&0u16.to_le_bytes()); // Available
219    params.extend_from_slice(&0u16.to_le_bytes()); // DataCompactionMode
220    params.extend_from_slice(&0u16.to_le_bytes()); // Reserved
221    params.extend_from_slice(&(data.len() as u16).to_le_bytes()); // DataLength @DATA_LEN_OFF
222    params.extend_from_slice(&(doff as u16).to_le_bytes()); // DataOffset @DATA_OFFS_OFF
223    params.extend_from_slice(&0u32.to_le_bytes()); // DataLengthHigh
224    params.extend_from_slice(&[0u8; 6]); // reserved tail (total = 24B)
225
226    debug_assert_eq!(params.len(), PARAMS_LEN);
227    debug_assert_eq!(
228        u16::from_le_bytes(params[DATA_LEN_OFF..DATA_LEN_OFF + 2].try_into().unwrap()),
229        data.len() as u16
230    );
231    debug_assert_eq!(
232        u16::from_le_bytes(params[DATA_OFFS_OFF..DATA_OFFS_OFF + 2].try_into().unwrap()),
233        doff as u16
234    );
235
236    let pad = doff - bc_end;
237    let mut bytes = vec![0u8; pad];
238    bytes.extend_from_slice(data);
239    (params, bytes)
240}