Skip to main content

smb_server_proto_smb3/
lib.rs

1//! SMB2 wire structures ([MS-SMB2]).
2//!
3//! Pass-2 scaffold. Planned modules against the spec stored at
4//! `docs/protocol/smb2_3/MS-SMB2.pdf`:
5//! - header: 64-byte SMB2 header (§2.2.1)
6//! - negotiate: §2.2.3 (dialects 2.0.2/2.1.0/3.x, capabilities, contexts)
7//! - session: SESSION_SETUP §2.2.5, LOGOFF §2.2.7
8//! - tree: TREE_CONNECT/CONNECTX/DISCONNECT §2.2.9–2.2.11
9//! - create/read/write/close/flush: §2.2.13–2.2.17
10//! - find/query/set info/dir: §2.2.33–2.2.37
11//! - credits: credit grant/charge accounting (§3.3.1.1)
12#![forbid(unsafe_code)]
13#![deny(missing_docs)]
14#![warn(missing_debug_implementations)]
15
16/// SMB2 header magic `\xFESMB`.
17pub const SMB2_MAGIC: [u8; 4] = [0xFE, b'S', b'M', b'B'];
18
19/// Known SMB2/3 dialect numbers (§2.2.3.1.1).
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Dialect {
22    /// 2.0.2
23    V202,
24    /// 2.1.0
25    V210,
26    /// 3.0
27    V300,
28    /// 3.0.2
29    V302,
30    /// 3.1.1
31    V311,
32}
33
34/// Fixed 64-byte SMB2 header (§2.2.1) — decoded view.
35#[derive(Debug, Clone)]
36pub struct Header2 {
37    /// Credit charge (SMB3.1.1) / structure size.
38    pub credit_charge: u16,
39    /// Status.
40    pub status: u32,
41    /// Command code (§2.2.1.2).
42    pub command: u16,
43    /// Credits granted by server.
44    pub credits: u16,
45    /// Header flags.
46    pub flags: u32,
47    /// Next command chain offset (compounding).
48    pub next_command: u32,
49    /// Message identifier.
50    pub message_id: u64,
51    /// Session identifier.
52    pub session_id: u64,
53    /// Signature / dedupe tag.
54    pub signature: [u8; 16],
55}
56
57impl Header2 {
58    /// Parse the 64-byte header from `buf`; `None` on bad magic/size.
59    pub fn parse(buf: &[u8]) -> Option<Header2> {
60        if buf.len() < 64 || buf[0..4] != SMB2_MAGIC {
61            return None;
62        }
63        Some(Header2 {
64            credit_charge: u16::from_le_bytes(buf[6..8].try_into().ok()?),
65            status: u32::from_le_bytes(buf[8..12].try_into().ok()?),
66            command: u16::from_le_bytes(buf[12..14].try_into().ok()?),
67            credits: u16::from_le_bytes(buf[14..16].try_into().ok()?),
68            flags: u32::from_le_bytes(buf[16..20].try_into().ok()?),
69            next_command: u32::from_le_bytes(buf[20..24].try_into().ok()?),
70            message_id: u64::from_le_bytes(buf[24..32].try_into().ok()?),
71            session_id: u64::from_le_bytes(buf[36..44].try_into().ok()?),
72            signature: buf[48..64].try_into().ok()?,
73        })
74    }
75
76    /// True when the response flag (bit 0) is set.
77    pub fn is_response(&self) -> bool {
78        self.flags & 0x0000_0001 != 0
79    }
80}