DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
smb2.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file smb2.cpp
6 * @brief SMB2 client wire codec implementation (see smb2.h). All fields little-endian.
7 */
8
9#include "smb2.h"
10
11#if DETWS_ENABLE_SMB
12
13#include <string.h>
14
15// Little-endian primitives (SMB2 is little-endian on the wire).
16static uint16_t rd16(const uint8_t *p)
17{
18 return (uint16_t)(p[0] | (p[1] << 8));
19}
20static uint32_t rd32(const uint8_t *p)
21{
22 return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
23}
24static uint64_t rd64(const uint8_t *p)
25{
26 return (uint64_t)rd32(p) | ((uint64_t)rd32(p + 4) << 32);
27}
28static void wr16(uint8_t *p, uint16_t v)
29{
30 p[0] = (uint8_t)v;
31 p[1] = (uint8_t)(v >> 8);
32}
33static void wr32(uint8_t *p, uint32_t v)
34{
35 p[0] = (uint8_t)v;
36 p[1] = (uint8_t)(v >> 8);
37 p[2] = (uint8_t)(v >> 16);
38 p[3] = (uint8_t)(v >> 24);
39}
40static void wr64(uint8_t *p, uint64_t v)
41{
42 wr32(p, (uint32_t)v);
43 wr32(p + 4, (uint32_t)(v >> 32));
44}
45
46static const uint8_t SMB2_PROTOCOL_ID[4] = {0xFE, 'S', 'M', 'B'};
47
48size_t smb2_transport_frame(uint8_t *out, size_t cap, const uint8_t *msg, size_t msg_len)
49{
50 if (!out || !msg || msg_len > 0x00FFFFFF || 4 + msg_len > cap)
51 return 0;
52 out[0] = 0x00; // Direct TCP: first byte MUST be zero
53 out[1] = (uint8_t)(msg_len >> 16);
54 out[2] = (uint8_t)(msg_len >> 8);
55 out[3] = (uint8_t)(msg_len);
56 memcpy(out + 4, msg, msg_len);
57 return 4 + msg_len;
58}
59
60uint32_t smb2_transport_len(const uint8_t *buf, size_t len)
61{
62 if (!buf || len < 4 || buf[0] != 0x00)
63 return 0;
64 return ((uint32_t)buf[1] << 16) | ((uint32_t)buf[2] << 8) | (uint32_t)buf[3];
65}
66
67size_t smb2_build_header(uint8_t *buf, size_t cap, Smb2Command command, uint16_t credit_request, uint64_t message_id,
68 uint32_t tree_id, uint64_t session_id)
69{
70 if (!buf || cap < SMB2_HEADER_SIZE)
71 return 0;
72 memset(buf, 0, SMB2_HEADER_SIZE);
73 memcpy(buf + 0, SMB2_PROTOCOL_ID, 4); // ProtocolId
74 wr16(buf + 4, 64); // StructureSize
75 // CreditCharge (6), Status/ChannelSequence (8) left 0
76 wr16(buf + 12, (uint16_t)command); // Command
77 wr16(buf + 14, credit_request); // CreditRequest
78 // Flags (16) = 0 (client request), NextCommand (20) = 0
79 wr64(buf + 24, message_id); // MessageId
80 // Reserved (32) = 0
81 wr32(buf + 36, tree_id); // TreeId
82 wr64(buf + 40, session_id); // SessionId
83 // Signature (48..63) = 0
84 return SMB2_HEADER_SIZE;
85}
86
87bool smb2_parse_header(const uint8_t *buf, size_t len, Smb2Header *out)
88{
89 if (!buf || !out || len < SMB2_HEADER_SIZE)
90 return false;
91 if (memcmp(buf, SMB2_PROTOCOL_ID, 4) != 0 || rd16(buf + 4) != 64)
92 return false;
93 out->status = rd32(buf + 8);
94 out->command = (Smb2Command)rd16(buf + 12);
95 out->credit_response = rd16(buf + 14);
96 out->flags = rd32(buf + 16);
97 out->message_id = rd64(buf + 24);
98 out->tree_id = rd32(buf + 36);
99 out->session_id = rd64(buf + 40);
100 return true;
101}
102
103size_t smb2_build_negotiate(uint8_t *buf, size_t cap, const uint8_t client_guid[16], uint16_t security_mode)
104{
105 static const Smb2Dialect dialects[] = {Smb2Dialect::SMB2_DIALECT_0202, Smb2Dialect::SMB2_DIALECT_0210,
106 Smb2Dialect::SMB2_DIALECT_0300, Smb2Dialect::SMB2_DIALECT_0302};
107 const uint16_t ndialects = (uint16_t)(sizeof(dialects) / sizeof(dialects[0]));
108 const size_t total = SMB2_HEADER_SIZE + 36 + (size_t)ndialects * 2; // header + fixed body + dialects
109 if (!buf || !client_guid || cap < total)
110 return 0;
111
112 if (smb2_build_header(buf, cap, Smb2Command::SMB2_NEGOTIATE, 1, 0, 0, 0) == 0)
113 return 0;
114
115 uint8_t *b = buf + SMB2_HEADER_SIZE; // NEGOTIATE request body
116 memset(b, 0, 36);
117 wr16(b + 0, 36); // StructureSize
118 wr16(b + 2, ndialects); // DialectCount
119 wr16(b + 4, security_mode); // SecurityMode
120 // Reserved (6) = 0, Capabilities (8) = 0
121 memcpy(b + 12, client_guid, 16); // ClientGuid
122 // ClientStartTime (28) = 0 (only 3.1.1 reinterprets these 8 bytes as negotiate-context fields)
123 for (uint16_t i = 0; i < ndialects; i++)
124 wr16(b + 36 + i * 2, (uint16_t)dialects[i]);
125 return total;
126}
127
128bool smb2_parse_negotiate_response(const uint8_t *msg, size_t len, Smb2NegotiateResp *out)
129{
130 if (!msg || !out)
131 return false;
132 Smb2Header h;
133 if (!smb2_parse_header(msg, len, &h) || h.command != Smb2Command::SMB2_NEGOTIATE)
134 return false;
135 // The fixed response body is 64 bytes (StructureSize .. NegotiateContextOffset), Buffer follows.
136 if (len < SMB2_HEADER_SIZE + 64)
137 return false;
138 const uint8_t *b = msg + SMB2_HEADER_SIZE;
139 if (rd16(b + 0) != 65) // StructureSize
140 return false;
141
142 out->security_mode = rd16(b + 2);
143 out->dialect = rd16(b + 4);
144 memcpy(out->server_guid, b + 8, 16);
145 out->capabilities = rd32(b + 24);
146 out->max_transact = rd32(b + 28);
147 out->max_read = rd32(b + 32);
148 out->max_write = rd32(b + 36);
149
150 uint16_t sec_off = rd16(b + 56); // SecurityBufferOffset - from the start of the SMB2 header (msg)
151 uint16_t sec_len = rd16(b + 58); // SecurityBufferLength
152 if (sec_len == 0)
153 {
154 out->sec_buf = nullptr;
155 out->sec_buf_len = 0;
156 return true;
157 }
158 if ((size_t)sec_off + sec_len > len || sec_off < SMB2_HEADER_SIZE)
159 return false; // security buffer out of bounds - fail closed
160 out->sec_buf = msg + sec_off;
161 out->sec_buf_len = sec_len;
162 return true;
163}
164
165size_t smb2_build_session_setup(uint8_t *buf, size_t cap, uint64_t message_id, uint64_t session_id,
166 uint8_t security_mode, const uint8_t *sec_buf, size_t sec_len)
167{
168 const size_t body = 24; // fixed SESSION_SETUP request body (§2.2.5)
169 const size_t total = SMB2_HEADER_SIZE + body + sec_len;
170 if (!buf || !sec_buf || sec_len == 0 || sec_len > 0xFFFF || cap < total)
171 return 0;
172 if (smb2_build_header(buf, cap, Smb2Command::SMB2_SESSION_SETUP, 1, message_id, 0, session_id) == 0)
173 return 0;
174
175 uint8_t *b = buf + SMB2_HEADER_SIZE;
176 memset(b, 0, body);
177 wr16(b + 0, 25); // StructureSize (fixed 24 + 1 for the variable buffer)
178 b[2] = 0; // Flags (SMB2_SESSION_FLAG_BINDING only for 3.x channel binding)
179 b[3] = security_mode; // SecurityMode (one byte here)
180 // Capabilities (4) = 0, Channel (8) = 0
181 wr16(b + 12, (uint16_t)(SMB2_HEADER_SIZE + body)); // SecurityBufferOffset (from the header start)
182 wr16(b + 14, (uint16_t)sec_len); // SecurityBufferLength
183 // PreviousSessionId (16) = 0 (a fresh session)
184 memcpy(b + body, sec_buf, sec_len);
185 return total;
186}
187
188bool smb2_parse_session_setup_response(const uint8_t *msg, size_t len, Smb2SessionSetupResp *out)
189{
190 if (!msg || !out)
191 return false;
192 Smb2Header h;
193 if (!smb2_parse_header(msg, len, &h) || h.command != Smb2Command::SMB2_SESSION_SETUP)
194 return false;
195 // The fixed response body is 8 bytes (StructureSize .. SecurityBufferLength), Buffer follows.
196 if (len < SMB2_HEADER_SIZE + 8)
197 return false;
198 const uint8_t *b = msg + SMB2_HEADER_SIZE;
199 if (rd16(b + 0) != 9) // StructureSize
200 return false;
201
202 out->session_flags = rd16(b + 2);
203 uint16_t sec_off = rd16(b + 4); // SecurityBufferOffset - from the start of the SMB2 header (msg)
204 uint16_t sec_len = rd16(b + 6); // SecurityBufferLength
205 if (sec_len == 0)
206 {
207 out->sec_buf = nullptr;
208 out->sec_buf_len = 0;
209 return true;
210 }
211 if ((size_t)sec_off + sec_len > len || sec_off < SMB2_HEADER_SIZE)
212 return false; // security buffer out of bounds - fail closed
213 out->sec_buf = msg + sec_off;
214 out->sec_buf_len = sec_len;
215 return true;
216}
217
218size_t smb2_build_tree_connect(uint8_t *buf, size_t cap, uint64_t message_id, uint64_t session_id,
219 const uint8_t *path_utf16, size_t path_len)
220{
221 const size_t body = 8; // fixed TREE_CONNECT request body (§2.2.9)
222 const size_t total = SMB2_HEADER_SIZE + body + path_len;
223 if (!buf || !path_utf16 || path_len == 0 || path_len > 0xFFFF || cap < total)
224 return 0;
225 if (smb2_build_header(buf, cap, Smb2Command::SMB2_TREE_CONNECT, 1, message_id, 0, session_id) == 0)
226 return 0;
227
228 uint8_t *b = buf + SMB2_HEADER_SIZE;
229 memset(b, 0, body);
230 wr16(b + 0, 9); // StructureSize
231 // Flags/Reserved (2) = 0
232 wr16(b + 4, (uint16_t)(SMB2_HEADER_SIZE + body)); // PathOffset (from the header start) = 72
233 wr16(b + 6, (uint16_t)path_len); // PathLength
234 memcpy(b + body, path_utf16, path_len); // the \\server\share path (UTF-16LE)
235 return total;
236}
237
238bool smb2_parse_tree_connect_response(const uint8_t *msg, size_t len, Smb2TreeConnectResp *out)
239{
240 if (!msg || !out)
241 return false;
242 Smb2Header h;
243 if (!smb2_parse_header(msg, len, &h) || h.command != Smb2Command::SMB2_TREE_CONNECT)
244 return false;
245 if (len < SMB2_HEADER_SIZE + 16) // fixed 16-byte body, no variable buffer
246 return false;
247 const uint8_t *b = msg + SMB2_HEADER_SIZE;
248 if (rd16(b + 0) != 16) // StructureSize
249 return false;
250 out->share_type = b[2];
251 out->share_flags = rd32(b + 4);
252 out->capabilities = rd32(b + 8);
253 out->maximal_access = rd32(b + 12);
254 return true;
255}
256
257size_t smb2_build_create(uint8_t *buf, size_t cap, uint64_t message_id, uint64_t session_id, uint32_t tree_id,
258 uint32_t desired_access, uint32_t share_access, uint32_t create_disposition,
259 uint32_t create_options, const uint8_t *name_utf16, size_t name_len)
260{
261 const size_t body = 56; // fixed CREATE request body (§2.2.13)
262 const size_t total = SMB2_HEADER_SIZE + body + name_len;
263 if (!buf || !name_utf16 || name_len == 0 || name_len > 0xFFFF || cap < total)
264 return 0;
265 if (smb2_build_header(buf, cap, Smb2Command::SMB2_CREATE, 1, message_id, tree_id, session_id) == 0)
266 return 0;
267
268 uint8_t *b = buf + SMB2_HEADER_SIZE;
269 memset(b, 0, body);
270 wr16(b + 0, 57); // StructureSize (fixed 56 + 1 for the variable buffer)
271 // SecurityFlags (2) = 0, RequestedOplockLevel (3) = 0 (SMB2_OPLOCK_LEVEL_NONE)
272 wr32(b + 4, 2); // ImpersonationLevel = Impersonation
273 // SmbCreateFlags (8) = 0, Reserved (16) = 0
274 wr32(b + 24, desired_access); // DesiredAccess
275 wr32(b + 28, 0); // FileAttributes = 0
276 wr32(b + 32, share_access); // ShareAccess
277 wr32(b + 36, create_disposition); // CreateDisposition
278 wr32(b + 40, create_options); // CreateOptions
279 wr16(b + 44, (uint16_t)(SMB2_HEADER_SIZE + body)); // NameOffset (from the header start) = 120
280 wr16(b + 46, (uint16_t)name_len); // NameLength
281 // CreateContextsOffset (48) = 0, CreateContextsLength (52) = 0
282 memcpy(b + body, name_utf16, name_len);
283 return total;
284}
285
286bool smb2_parse_create_response(const uint8_t *msg, size_t len, Smb2CreateResp *out)
287{
288 if (!msg || !out)
289 return false;
290 Smb2Header h;
291 if (!smb2_parse_header(msg, len, &h) || h.command != Smb2Command::SMB2_CREATE)
292 return false;
293 if (len < SMB2_HEADER_SIZE + 88) // fixed 88-byte body (StructureSize .. CreateContextsLength)
294 return false;
295 const uint8_t *b = msg + SMB2_HEADER_SIZE;
296 if (rd16(b + 0) != 89) // StructureSize
297 return false;
298 out->create_action = rd32(b + 4);
299 out->end_of_file = rd64(b + 48);
300 out->file_attributes = rd32(b + 56);
301 memcpy(out->file_id, b + 64, 16); // FileId (persistent 8 + volatile 8)
302 return true;
303}
304
305size_t smb2_build_close(uint8_t *buf, size_t cap, uint64_t message_id, uint64_t session_id, uint32_t tree_id,
306 const uint8_t file_id[16])
307{
308 const size_t body = 24; // fixed CLOSE request body (§2.2.15), no variable buffer
309 const size_t total = SMB2_HEADER_SIZE + body;
310 if (!buf || !file_id || cap < total)
311 return 0;
312 if (smb2_build_header(buf, cap, Smb2Command::SMB2_CLOSE, 1, message_id, tree_id, session_id) == 0)
313 return 0;
314
315 uint8_t *b = buf + SMB2_HEADER_SIZE;
316 memset(b, 0, body);
317 wr16(b + 0, 24); // StructureSize
318 // Flags (2) = 0 (no POSTQUERY_ATTRIB), Reserved (4) = 0
319 memcpy(b + 8, file_id, 16); // FileId
320 return total;
321}
322
323bool smb2_parse_close_response(const uint8_t *msg, size_t len, Smb2CloseResp *out)
324{
325 if (!msg || !out)
326 return false;
327 Smb2Header h;
328 if (!smb2_parse_header(msg, len, &h) || h.command != Smb2Command::SMB2_CLOSE)
329 return false;
330 if (len < SMB2_HEADER_SIZE + 60) // fixed 60-byte body
331 return false;
332 const uint8_t *b = msg + SMB2_HEADER_SIZE;
333 if (rd16(b + 0) != 60) // StructureSize
334 return false;
335 out->end_of_file = rd64(b + 48);
336 out->file_attributes = rd32(b + 56);
337 return true;
338}
339
340size_t smb2_build_read(uint8_t *buf, size_t cap, uint64_t message_id, uint64_t session_id, uint32_t tree_id,
341 const uint8_t file_id[16], uint32_t length, uint64_t offset)
342{
343 const size_t body = 48; // fixed READ request body (§2.2.19)
344 const size_t total = SMB2_HEADER_SIZE + body + 1; // + a 1-byte buffer (StructureSize 49 convention)
345 if (!buf || !file_id || cap < total)
346 return 0;
347 if (smb2_build_header(buf, cap, Smb2Command::SMB2_READ, 1, message_id, tree_id, session_id) == 0)
348 return 0;
349
350 uint8_t *b = buf + SMB2_HEADER_SIZE;
351 memset(b, 0, body + 1);
352 wr16(b + 0, 49); // StructureSize
353 b[2] = (uint8_t)(SMB2_HEADER_SIZE + 16); // Padding: requested data offset in the response (header + 16-byte body)
354 // Flags (3) = 0
355 wr32(b + 4, length); // Length
356 wr64(b + 8, offset); // Offset
357 memcpy(b + 16, file_id, 16); // FileId
358 wr32(b + 32, 1); // MinimumCount = 1 (fail if the server returns nothing)
359 // Channel (36) = 0, RemainingBytes (40) = 0, ReadChannelInfoOffset/Length (44/46) = 0
360 // Buffer (b+48) = one 0 byte (already zeroed)
361 return total;
362}
363
364bool smb2_parse_read_response(const uint8_t *msg, size_t len, Smb2ReadResp *out)
365{
366 if (!msg || !out)
367 return false;
368 Smb2Header h;
369 if (!smb2_parse_header(msg, len, &h) || h.command != Smb2Command::SMB2_READ)
370 return false;
371 if (len < SMB2_HEADER_SIZE + 16) // fixed 16-byte body (StructureSize .. Reserved2), Buffer follows
372 return false;
373 const uint8_t *b = msg + SMB2_HEADER_SIZE;
374 if (rd16(b + 0) != 17) // StructureSize
375 return false;
376
377 uint8_t data_off = b[2]; // DataOffset - from the start of the SMB2 header (msg)
378 uint32_t data_len = rd32(b + 4); // DataLength
379 if (data_len == 0)
380 {
381 out->data = nullptr;
382 out->data_len = 0;
383 return true;
384 }
385 if (data_off < SMB2_HEADER_SIZE || (size_t)data_off + data_len > len)
386 return false; // data out of bounds - fail closed
387 out->data = msg + data_off;
388 out->data_len = data_len;
389 return true;
390}
391
392size_t smb2_build_write(uint8_t *buf, size_t cap, uint64_t message_id, uint64_t session_id, uint32_t tree_id,
393 const uint8_t file_id[16], const uint8_t *data, size_t data_len, uint64_t offset)
394{
395 const size_t body = 48; // fixed WRITE request body (§2.2.21)
396 const size_t total = SMB2_HEADER_SIZE + body + data_len;
397 if (!buf || !file_id || !data || data_len == 0 || data_len > 0xFFFFFFFF || cap < total)
398 return 0;
399 if (smb2_build_header(buf, cap, Smb2Command::SMB2_WRITE, 1, message_id, tree_id, session_id) == 0)
400 return 0;
401
402 uint8_t *b = buf + SMB2_HEADER_SIZE;
403 memset(b, 0, body);
404 wr16(b + 0, 49); // StructureSize
405 wr16(b + 2, (uint16_t)(SMB2_HEADER_SIZE + body)); // DataOffset (from the header start) = 112
406 wr32(b + 4, (uint32_t)data_len); // Length
407 wr64(b + 8, offset); // Offset
408 memcpy(b + 16, file_id, 16); // FileId
409 // Channel (32) = 0, RemainingBytes (36) = 0, WriteChannelInfoOffset/Length (40/42) = 0, Flags (44) = 0
410 memcpy(b + body, data, data_len); // the data to write
411 return total;
412}
413
414bool smb2_parse_write_response(const uint8_t *msg, size_t len, Smb2WriteResp *out)
415{
416 if (!msg || !out)
417 return false;
418 Smb2Header h;
419 if (!smb2_parse_header(msg, len, &h) || h.command != Smb2Command::SMB2_WRITE)
420 return false;
421 if (len < SMB2_HEADER_SIZE + 16) // fixed 16-byte body
422 return false;
423 const uint8_t *b = msg + SMB2_HEADER_SIZE;
424 if (rd16(b + 0) != 17) // StructureSize
425 return false;
426 out->count = rd32(b + 4); // Count (bytes written)
427 return true;
428}
429
430#endif // DETWS_ENABLE_SMB
SMB2 client wire codec (MS-SMB2), DETWS_ENABLE_SMB - increment 1: the transport frame,...