@@ -42,6 +42,9 @@ const dialect_smb_300 = u16(0x0300)
4242const dialect_smb_302 = u16 (0x0302 )
4343const dialect_smb_311 = u16 (0x0311 )
4444
45+ // smb2_dialect_311 is the SMB 3.1.1 dialect identifier (public alias).
46+ pub const smb2_dialect_311 = u16 (0x0311 )
47+
4548// SMB2 NEGOTIATE request structure size constant.
4649const negotiate_struct_size = u16 (36 )
4750
@@ -71,6 +74,63 @@ pub enum ShareType {
7174 print // Printer share
7275}
7376
77+ // --- SMB2 command enumeration ---
78+
79+ // Smb2Command enumerates SMB2 command codes (MS-SMB2 §2.2.1).
80+ pub enum Smb2Command {
81+ negotiate // 0x0000 — Negotiate protocol dialect
82+ session_setup // 0x0001 — Authenticate and set up session
83+ tree_connect // 0x0003 — Connect to a named share
84+ create // 0x0005 — Open or create a file
85+ read // 0x0008 — Read data from a file
86+ write // 0x0009 — Write data to a file
87+ close // 0x0006 — Close a file handle
88+ }
89+
90+ // min_structure_size returns the minimum StructureSize for the request body
91+ // of each SMB2 command, per MS-SMB2 specification tables.
92+ pub fn (c Smb2Command) min_structure_size () int {
93+ return match c {
94+ .negotiate { 36 } // §2.2.3
95+ .session_setup { 25 } // §2.2.5
96+ .tree_connect { 9 } // §2.2.9
97+ .create { 57 } // §2.2.13
98+ .read { 49 } // §2.2.19
99+ .write { 49 } // §2.2.21
100+ .close { 24 } // §2.2.15
101+ }
102+ }
103+
104+ // code returns the 2-byte little-endian command code for this command.
105+ pub fn (c Smb2Command) code () u16 {
106+ return match c {
107+ .negotiate { u16 (0x0000 ) }
108+ .session_setup { u16 (0x0001 ) }
109+ .tree_connect { u16 (0x0003 ) }
110+ .create { u16 (0x0005 ) }
111+ .read { u16 (0x0008 ) }
112+ .write { u16 (0x0009 ) }
113+ .close { u16 (0x0006 ) }
114+ }
115+ }
116+
117+ // --- SMB2 header struct ---
118+
119+ // Smb2Header holds all fields of the 64-byte SMB2 packet header (MS-SMB2 §2.2.1.2).
120+ pub struct Smb2Header {
121+ pub :
122+ protocol_id [4 ]u8 // Must be [0xFE, 'S', 'M', 'B']
123+ header_length u16 // Fixed: 64
124+ credit_charge u16 // Credits consumed by this request
125+ status u32 // NT status code (response) or channel sequence (request)
126+ command Smb2 Command // SMB2 command code
127+ credits_requested u16 // Credits requested (client) or granted (server)
128+ flags u32 // Header flags bitmask
129+ message_id u64 // Unique message identifier per session
130+ session_id u64 // Session identifier (0 before auth)
131+ signature [16 ]u8 // Message signature (when signing enabled)
132+ }
133+
74134// --- Data structures ---
75135
76136// SmbHeader holds the parsed SMB2 protocol header fields.
@@ -244,6 +304,87 @@ pub fn parse_header(data []u8) !SmbHeader {
244304 }
245305}
246306
307+ // encode_smb2_header serialises a Smb2Header to the 64-byte SMB2 wire
308+ // format (MS-SMB2 §2.2.1.2), using little-endian byte order throughout.
309+ pub fn encode_smb2_header (h Smb2 Header) []u8 {
310+ mut out := []u8 {len: 0 , cap: 64 }
311+ // ProtocolId (4 bytes)
312+ for b in h.protocol_id { out << b }
313+ // StructureSize (2 bytes LE) = 64
314+ out << u8 (64 & 0xFF ) << u8 (64 >> 8 )
315+ // CreditCharge (2 bytes LE)
316+ out << u8 (h.credit_charge & 0xFF ) << u8 (h.credit_charge >> 8 )
317+ // Status (4 bytes LE)
318+ out << u8 (h.status & 0xFF ) << u8 ((h.status >> 8 ) & 0xFF )
319+ out << u8 ((h.status >> 16 ) & 0xFF ) << u8 ((h.status >> 24 ) & 0xFF )
320+ // Command (2 bytes LE)
321+ cmd_code := h.command.code ()
322+ out << u8 (cmd_code & 0xFF ) << u8 (cmd_code >> 8 )
323+ // CreditsRequested (2 bytes LE)
324+ out << u8 (h.credits_requested & 0xFF ) << u8 (h.credits_requested >> 8 )
325+ // Flags (4 bytes LE)
326+ out << u8 (h.flags & 0xFF ) << u8 ((h.flags >> 8 ) & 0xFF )
327+ out << u8 ((h.flags >> 16 ) & 0xFF ) << u8 ((h.flags >> 24 ) & 0xFF )
328+ // NextCommand (4 bytes) = 0
329+ out << u8 (0x00 ) << u8 (0x00 ) << u8 (0x00 ) << u8 (0x00 )
330+ // MessageId (8 bytes LE)
331+ for i in 0 ..8 { out << u8 ((h.message_id >> (u64 (i) * 8 )) & 0xFF ) }
332+ // Reserved / AsyncId / TreeId (4 bytes) = 0
333+ out << u8 (0x00 ) << u8 (0x00 ) << u8 (0x00 ) << u8 (0x00 )
334+ // SessionId (8 bytes LE)
335+ for i in 0 ..8 { out << u8 ((h.session_id >> (u64 (i) * 8 )) & 0xFF ) }
336+ // Signature (16 bytes)
337+ for b in h.signature { out << b }
338+ return out
339+ }
340+
341+ // decode_smb2_header parses the first 64 bytes of a received SMB2 buffer.
342+ // Returns an error if the buffer is too short or the magic bytes are wrong.
343+ pub fn decode_smb2_header (data []u8 ) ! Smb2 Header {
344+ if data.len < 64 {
345+ return error ('SMB2 header requires 64 bytes, got ${data.len} ' )
346+ }
347+ // Verify magic
348+ if data[0 ] != 0xFE || data[1 ] != 0x53 || data[2 ] != 0x4D || data[3 ] != 0x42 {
349+ return error ('invalid SMB2 magic bytes: expected FE534D42' )
350+ }
351+ mut pid := [4 ]u8 {}
352+ for i in 0 ..4 { pid[i] = data[i] }
353+ credit_charge := (u16 (data[5 ]) << 8 ) | u16 (data[4 ])
354+ status := u32 (data[8 ]) | (u32 (data[9 ]) << 8 ) | (u32 (data[10 ]) << 16 ) | (u32 (data[11 ]) << 24 )
355+ cmd_code := u16 (data[12 ]) | (u16 (data[13 ]) << 8 )
356+ credits_req := u16 (data[14 ]) | (u16 (data[15 ]) << 8 )
357+ flags := u32 (data[16 ]) | (u32 (data[17 ]) << 8 ) | (u32 (data[18 ]) << 16 ) | (u32 (data[19 ]) << 24 )
358+ msg_id := u64 (data[24 ]) | (u64 (data[25 ]) << 8 ) | (u64 (data[26 ]) << 16 ) | (u64 (data[27 ]) << 24 ) |
359+ (u64 (data[28 ]) << 32 ) | (u64 (data[29 ]) << 40 ) | (u64 (data[30 ]) << 48 ) | (u64 (data[31 ]) << 56 )
360+ sess_id := u64 (data[40 ]) | (u64 (data[41 ]) << 8 ) | (u64 (data[42 ]) << 16 ) | (u64 (data[43 ]) << 24 ) |
361+ (u64 (data[44 ]) << 32 ) | (u64 (data[45 ]) << 40 ) | (u64 (data[46 ]) << 48 ) | (u64 (data[47 ]) << 56 )
362+ mut sig := [16 ]u8 {}
363+ for i in 0 ..16 { sig[i] = data[48 + i] }
364+ command := match cmd_code {
365+ u16 (0x0000 ) { Smb2 Command.negotiate }
366+ u16 (0x0001 ) { Smb2 Command.session_setup }
367+ u16 (0x0003 ) { Smb2 Command.tree_connect }
368+ u16 (0x0005 ) { Smb2 Command.create }
369+ u16 (0x0006 ) { Smb2 Command.close }
370+ u16 (0x0008 ) { Smb2 Command.read }
371+ u16 (0x0009 ) { Smb2 Command.write }
372+ else { return error ('unknown SMB2 command code 0x${cmd_code:04X} ' ) }
373+ }
374+ return Smb2 Header{
375+ protocol_id: pid
376+ header_length: u16 (64 )
377+ credit_charge: credit_charge
378+ status: status
379+ command: command
380+ credits_requested: credits_req
381+ flags: flags
382+ message_id: msg_id
383+ session_id: sess_id
384+ signature: sig
385+ }
386+ }
387+
247388// --- Tests ---
248389
249390fn test_smb2_magic () {
@@ -284,3 +425,41 @@ fn test_parse_header_invalid_magic() {
284425 assert false
285426}
286427
428+ fn test_negotiate_request_contains_dialect_311 () {
429+ pkt := encode_negotiate_request ()
430+ // Dialects start after the 64-byte header + 34 bytes of NEGOTIATE body prefix
431+ // (StructureSize(2) + DialectCount(2) + SecurityMode(2) + Reserved(2) +
432+ // Capabilities(4) + ClientGuid(16) + NegotiateContextOffset(4) + DialectCount × 2)
433+ // Check that 0x0311 appears somewhere in the last 6 bytes (3 dialects × 2 bytes)
434+ last6 := pkt[pkt.len - 6 ..]
435+ found_311 := (last6 [4 ] == u8 (smb2_ dialect_311 & 0xFF ) && last6 [5 ] == u8 (smb2_ dialect_311 >> 8 ))
436+ assert found_311
437+ }
438+
439+ fn test_smb2_command_min_structure_size () {
440+ assert Smb2 Command.negotiate.min_structure_size () == 36
441+ assert Smb2 Command.session_setup.min_structure_size () == 25
442+ assert Smb2 Command.create.min_structure_size () == 57
443+ }
444+
445+ fn test_encode_decode_smb2_header_roundtrip () {
446+ hdr := Smb2 Header{
447+ protocol_id: [u8 (0xFE ), 0x53 , 0x4D , 0x42 ]!
448+ header_length: u16 (64 )
449+ credit_charge: u16 (1 )
450+ status: u32 (0 )
451+ command: .negotiate
452+ credits_requested: u16 (31 )
453+ flags: u32 (0 )
454+ message_id: u64 (1 )
455+ session_id: u64 (0 )
456+ signature: [16 ]u8 {}
457+ }
458+ wire := encode_smb2_header (hdr)
459+ assert wire.len == 64
460+ decoded := decode_smb2_header (wire) or { panic (err) }
461+ assert decoded.command == hdr.command
462+ assert decoded.message_id == hdr.message_id
463+ assert decoded.protocol_id[0 ] == u8 (0xFE )
464+ }
465+
0 commit comments