smb_server_proto_smb2/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/// First byte of a plaintext SMB2 message ProtocolId (`SMB2_MAGIC[0]`, `\xFE`).
20pub const PROTO_ID_SMB2: u8 = SMB2_MAGIC[0];
21/// First byte of an encrypted TRANSFORM_HEADER ProtocolId (`\xFD`, [MS-SMB2] §2.2.41).
22pub const PROTO_ID_ENCRYPTED: u8 = commands::TF_MAGIC[0];
23/// First byte of a COMPRESSION_TRANSFORM_HEADER ProtocolId (`\xFC`, [MS-SMB2] §2.2.42).
24pub const PROTO_ID_COMPRESSED: u8 = compress::PROTOCOL_ID[0];
25
26pub mod commands;
27pub mod compress;
28pub mod consts;
29pub mod info;
30pub mod negotiate;
31pub mod session_setup;
32
33/// Known SMB2/3 dialect numbers (§2.2.3.1.1).
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum Dialect {
36 /// 2.0.2
37 V202,
38 /// 2.1.0
39 V210,
40 /// 3.0
41 V300,
42 /// 3.0.2
43 V302,
44 /// 3.1.1
45 V311,
46}
47
48impl Dialect {
49 /// Wire dialect revision number.
50 pub fn rev(self) -> u16 {
51 match self {
52 Dialect::V202 => 0x0202,
53 Dialect::V210 => 0x0210,
54 Dialect::V300 => 0x0300,
55 Dialect::V302 => 0x0302,
56 Dialect::V311 => 0x0311,
57 }
58 }
59}
60
61/// Fixed 64-byte SMB2 header (§2.2.1) — decoded view.
62#[derive(Debug, Clone)]
63pub struct Header2 {
64 /// Credit charge (SMB3.1.1) / structure size.
65 pub credit_charge: u16,
66 /// Status.
67 pub status: u32,
68 /// Command code (§2.2.1.2).
69 pub command: u16,
70 /// Credits granted by server.
71 pub credits: u16,
72 /// Header flags.
73 pub flags: u32,
74 /// Next command chain offset (compounding).
75 pub next_command: u32,
76 /// Message identifier.
77 pub message_id: u64,
78 /// Tree identifier (0 when not tree-bound).
79 pub tree_id: u32,
80 /// Session identifier.
81 pub session_id: u64,
82 /// Signature / dedupe tag.
83 pub signature: [u8; 16],
84}
85
86impl Header2 {
87 /// Parse the 64-byte header from `buf`; `None` on bad magic/size.
88 pub fn parse(buf: &[u8]) -> Option<Header2> {
89 if buf.len() < 64 || buf[0..4] != SMB2_MAGIC {
90 return None;
91 }
92 Some(Header2 {
93 credit_charge: u16::from_le_bytes(buf[6..8].try_into().ok()?),
94 status: u32::from_le_bytes(buf[8..12].try_into().ok()?),
95 command: u16::from_le_bytes(buf[12..14].try_into().ok()?),
96 credits: u16::from_le_bytes(buf[14..16].try_into().ok()?),
97 flags: u32::from_le_bytes(buf[16..20].try_into().ok()?),
98 next_command: u32::from_le_bytes(buf[20..24].try_into().ok()?),
99 message_id: u64::from_le_bytes(buf[24..32].try_into().ok()?),
100 // ProcessId/Reserved occupies 32..36; TreeId 36..40; SessionId
101 // spans 40..48 ([MS-SMB2] §2.2.1).
102 tree_id: u32::from_le_bytes(buf[36..40].try_into().ok()?),
103 session_id: u64::from_le_bytes(buf[40..48].try_into().ok()?),
104 signature: buf[48..64].try_into().ok()?,
105 })
106 }
107
108 /// True when the response flag (bit 0) is set.
109 pub fn is_response(&self) -> bool {
110 self.flags & 0x0000_0001 != 0
111 }
112
113 /// True when the signed flag (bit 2) is set.
114 pub fn is_signed(&self) -> bool {
115 self.flags & 0x0000_0008 != 0
116 }
117
118 /// True when the async flag is set: `Flags & SMB2_FLAGS_ASYNC_COMMAND`
119 /// (0x00000002, [MS-SMB2] §2.2.1.2).
120 pub fn is_async(&self) -> bool {
121 self.flags & 0x0000_0002 != 0
122 }
123}