1pub const HEADER_LEN: usize = 64;
15
16pub const PROTOCOL_ID: [u8; 4] = *b"\xFESMB";
18
19pub const OFF_PROTOCOL_ID: usize = 0;
22pub const OFF_STRUCTURE_SIZE: usize = 4;
23pub const OFF_CREDIT_CHARGE: usize = 6;
24pub const OFF_STATUS: usize = 8;
25pub const OFF_COMMAND: usize = 12;
26pub const OFF_CREDITS: usize = 14;
27pub const OFF_FLAGS: usize = 16;
28pub const OFF_NEXT_COMMAND: usize = 20;
29pub const OFF_MESSAGE_ID: usize = 24;
30pub const OFF_RESERVED: usize = 32;
31pub const OFF_TREE_ID: usize = 36;
32pub const OFF_SESSION_ID: usize = 40;
33pub const OFF_SIGNATURE: usize = 48;
34
35pub const FLAGS_SERVER_TO_REDIR: u32 = 0x0000_0001;
37pub const FLAGS_ASYNC_COMMAND: u32 = 0x0000_0002;
38pub const FLAGS_RELATED_OPERATIONS: u32 = 0x0000_0004;
39pub const FLAGS_SIGNED: u32 = 0x0000_0008;
40
41pub mod command {
43 pub const NEGOTIATE: u16 = 0x0000;
44 pub const SESSION_SETUP: u16 = 0x0001;
45 pub const LOGOFF: u16 = 0x0002;
46 pub const TREE_CONNECT: u16 = 0x0003;
47 pub const TREE_DISCONNECT: u16 = 0x0004;
48 pub const CREATE: u16 = 0x0005;
49 pub const CLOSE: u16 = 0x0006;
50 pub const FLUSH: u16 = 0x0007;
51 pub const READ: u16 = 0x0008;
52 pub const WRITE: u16 = 0x0009;
53 pub const LOCK: u16 = 0x000A;
54 pub const IOCTL: u16 = 0x000B;
55 pub const CANCEL: u16 = 0x000C;
56 pub const ECHO: u16 = 0x000D;
57 pub const QUERY_DIRECTORY: u16 = 0x000E;
58 pub const CHANGE_NOTIFY: u16 = 0x000F;
59 pub const QUERY_INFO: u16 = 0x0010;
60 pub const SET_INFO: u16 = 0x0011;
61 pub const OPLOCK_BREAK: u16 = 0x0012;
62}
63
64pub mod status {
66 pub const SUCCESS: u32 = 0x0000_0000;
67 pub const INVALID_INFO_CLASS: u32 = 0xC000_0003;
68 pub const INVALID_PARAMETER: u32 = 0xC000_000D;
69 pub const INVALID_DEVICE_REQUEST: u32 = 0xC000_0010;
70 pub const END_OF_FILE: u32 = 0xC000_0011;
71 pub const MORE_PROCESSING_REQUIRED: u32 = 0xC000_0016;
72 pub const ACCESS_DENIED: u32 = 0xC000_0022;
73 pub const DATA_ERROR: u32 = 0xC000_003E;
74 pub const LOGON_FAILURE: u32 = 0xC000_006D;
75 pub const INSUFFICIENT_RESOURCES: u32 = 0xC000_009A;
76 pub const NOT_SUPPORTED: u32 = 0xC000_00BB;
77 pub const NETWORK_NAME_DELETED: u32 = 0xC000_00C9;
78 pub const INTERNAL_ERROR: u32 = 0xC000_00E5;
79 pub const TOO_MANY_OPENED_FILES: u32 = 0xC000_011F;
80 pub const FILE_CLOSED: u32 = 0xC000_0128;
81 pub const USER_SESSION_DELETED: u32 = 0xC000_0203;
82 pub const NO_MORE_FILES: u32 = 0x8000_0006;
83}
84
85pub const NBSS_SESSION_MESSAGE: u8 = 0x00;
93pub const NBSS_SESSION_REQUEST: u8 = 0x81;
95pub const NBSS_POSITIVE_RESPONSE: u8 = 0x82;
97pub const NBSS_KEEPALIVE: u8 = 0x85;
99
100pub const NBSS_MAX_LEN: usize = 0x00FF_FFFF;
102
103pub fn frame(message: &[u8]) -> Vec<u8> {
105 assert!(
106 message.len() <= NBSS_MAX_LEN,
107 "an SMB2 message longer than 24 bits cannot be framed"
108 );
109 let len = message.len() as u32;
110 let mut out = Vec::with_capacity(4 + message.len());
111 out.push(NBSS_SESSION_MESSAGE);
112 out.extend_from_slice(&len.to_be_bytes()[1..4]);
113 out.extend_from_slice(message);
114 out
115}
116
117pub fn parse_frame_header(header: [u8; 4]) -> (u8, usize) {
119 let len = u32::from_be_bytes([0, header[1], header[2], header[3]]) as usize;
120 (header[0], len)
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub struct RequestHeader {
130 pub credit_charge: u16,
131 pub command: u16,
132 pub credit_request: u16,
133 pub flags: u32,
134 pub next_command: u32,
135 pub message_id: u64,
136 pub tree_id: u32,
137 pub session_id: u64,
138}
139
140impl RequestHeader {
141 pub fn parse(msg: &[u8]) -> Option<Self> {
143 if msg.len() < HEADER_LEN || msg[OFF_PROTOCOL_ID..OFF_PROTOCOL_ID + 4] != PROTOCOL_ID {
144 return None;
145 }
146 Some(Self {
147 credit_charge: le16(msg, OFF_CREDIT_CHARGE),
148 command: le16(msg, OFF_COMMAND),
149 credit_request: le16(msg, OFF_CREDITS),
150 flags: le32(msg, OFF_FLAGS),
151 next_command: le32(msg, OFF_NEXT_COMMAND),
152 message_id: le64(msg, OFF_MESSAGE_ID),
153 tree_id: le32(msg, OFF_TREE_ID),
154 session_id: le64(msg, OFF_SESSION_ID),
155 })
156 }
157
158 pub fn is_related(&self) -> bool {
159 self.flags & FLAGS_RELATED_OPERATIONS != 0
160 }
161}
162
163pub const MAX_CREDIT_GRANT: u16 = 64;
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub struct ResponseHeader {
172 pub command: u16,
173 pub status: u32,
174 pub message_id: u64,
175 pub credit_charge: u16,
176 pub credits: u16,
177 pub flags: u32,
179 pub next_command: u32,
180 pub tree_id: u32,
181 pub session_id: u64,
182}
183
184impl ResponseHeader {
185 pub fn for_request(req: &RequestHeader, status: u32) -> Self {
191 Self {
192 command: req.command,
193 status,
194 message_id: req.message_id,
195 credit_charge: req.credit_charge,
196 credits: req.credit_request.clamp(1, MAX_CREDIT_GRANT),
197 flags: req.flags & FLAGS_RELATED_OPERATIONS,
198 next_command: 0,
199 tree_id: req.tree_id,
200 session_id: req.session_id,
201 }
202 }
203
204 pub fn with_status(mut self, status: u32) -> Self {
205 self.status = status;
206 self
207 }
208
209 pub fn with_session_id(mut self, session_id: u64) -> Self {
210 self.session_id = session_id;
211 self
212 }
213
214 pub fn with_tree_id(mut self, tree_id: u32) -> Self {
215 self.tree_id = tree_id;
216 self
217 }
218
219 pub fn encode(&self) -> [u8; HEADER_LEN] {
222 let mut h = [0u8; HEADER_LEN];
223 h[OFF_PROTOCOL_ID..OFF_PROTOCOL_ID + 4].copy_from_slice(&PROTOCOL_ID);
224 h[OFF_STRUCTURE_SIZE..OFF_STRUCTURE_SIZE + 2].copy_from_slice(&64u16.to_le_bytes());
225 h[OFF_CREDIT_CHARGE..OFF_CREDIT_CHARGE + 2]
226 .copy_from_slice(&self.credit_charge.to_le_bytes());
227 h[OFF_STATUS..OFF_STATUS + 4].copy_from_slice(&self.status.to_le_bytes());
228 h[OFF_COMMAND..OFF_COMMAND + 2].copy_from_slice(&self.command.to_le_bytes());
229 h[OFF_CREDITS..OFF_CREDITS + 2].copy_from_slice(&self.credits.to_le_bytes());
230 let flags = self.flags | FLAGS_SERVER_TO_REDIR;
231 h[OFF_FLAGS..OFF_FLAGS + 4].copy_from_slice(&flags.to_le_bytes());
232 h[OFF_NEXT_COMMAND..OFF_NEXT_COMMAND + 4].copy_from_slice(&self.next_command.to_le_bytes());
233 h[OFF_MESSAGE_ID..OFF_MESSAGE_ID + 8].copy_from_slice(&self.message_id.to_le_bytes());
234 h[OFF_TREE_ID..OFF_TREE_ID + 4].copy_from_slice(&self.tree_id.to_le_bytes());
236 h[OFF_SESSION_ID..OFF_SESSION_ID + 8].copy_from_slice(&self.session_id.to_le_bytes());
237 h
239 }
240
241 fn message(&self, body: &[u8]) -> Vec<u8> {
242 let mut out = Vec::with_capacity(HEADER_LEN + body.len());
243 out.extend_from_slice(&self.encode());
244 out.extend_from_slice(body);
245 out
246 }
247}
248
249pub fn chain(responses: Vec<Vec<u8>>) -> Vec<u8> {
252 let count = responses.len();
253 let mut out = Vec::new();
254 for (i, mut response) in responses.into_iter().enumerate() {
255 if i + 1 < count {
256 while response.len() % 8 != 0 {
257 response.push(0);
258 }
259 let next = response.len() as u32;
260 response[OFF_NEXT_COMMAND..OFF_NEXT_COMMAND + 4].copy_from_slice(&next.to_le_bytes());
261 }
262 out.extend_from_slice(&response);
263 }
264 out
265}
266
267pub fn error_response(hdr: &ResponseHeader) -> Vec<u8> {
273 let mut body = Vec::with_capacity(9);
274 body.extend_from_slice(&9u16.to_le_bytes()); body.push(0); body.push(0); body.extend_from_slice(&0u32.to_le_bytes()); body.push(0); hdr.message(&body)
280}
281
282pub fn empty_response(hdr: &ResponseHeader) -> Vec<u8> {
284 hdr.message(&[4, 0, 0, 0])
285}
286
287#[derive(Debug, Clone)]
289pub struct NegotiateParams {
290 pub dialect: u16,
291 pub server_guid: [u8; 16],
292 pub capabilities: u32,
293 pub max_transact_size: u32,
294 pub max_read_size: u32,
295 pub max_write_size: u32,
296 pub system_time: u64,
297 pub security_blob: Vec<u8>,
298}
299
300pub fn negotiate_response(hdr: &ResponseHeader, p: &NegotiateParams) -> Vec<u8> {
302 let mut body = Vec::with_capacity(64 + p.security_blob.len());
303 body.extend_from_slice(&65u16.to_le_bytes()); body.extend_from_slice(&0x0001u16.to_le_bytes()); body.extend_from_slice(&p.dialect.to_le_bytes()); body.extend_from_slice(&0u16.to_le_bytes()); body.extend_from_slice(&p.server_guid); body.extend_from_slice(&p.capabilities.to_le_bytes());
309 body.extend_from_slice(&p.max_transact_size.to_le_bytes());
310 body.extend_from_slice(&p.max_read_size.to_le_bytes());
311 body.extend_from_slice(&p.max_write_size.to_le_bytes());
312 body.extend_from_slice(&p.system_time.to_le_bytes()); body.extend_from_slice(&0u64.to_le_bytes()); let (offset, len) = if p.security_blob.is_empty() {
315 (0u16, 0u16)
316 } else {
317 ((HEADER_LEN + 64) as u16, p.security_blob.len() as u16)
318 };
319 body.extend_from_slice(&offset.to_le_bytes()); body.extend_from_slice(&len.to_le_bytes()); body.extend_from_slice(&0u32.to_le_bytes()); debug_assert_eq!(body.len(), 64);
323 if p.security_blob.is_empty() {
324 body.push(0); } else {
326 body.extend_from_slice(&p.security_blob);
327 }
328 hdr.message(&body)
329}
330
331pub const SESSION_FLAG_IS_GUEST: u16 = 0x0001;
333pub const SESSION_FLAG_IS_NULL: u16 = 0x0002;
334
335pub fn session_setup_response(hdr: &ResponseHeader, session_flags: u16, blob: &[u8]) -> Vec<u8> {
337 let mut body = Vec::with_capacity(8 + blob.len().max(1));
338 body.extend_from_slice(&9u16.to_le_bytes()); body.extend_from_slice(&session_flags.to_le_bytes());
340 let offset = if blob.is_empty() {
341 0u16
342 } else {
343 (HEADER_LEN + 8) as u16
344 };
345 body.extend_from_slice(&offset.to_le_bytes()); body.extend_from_slice(&(blob.len() as u16).to_le_bytes()); if blob.is_empty() {
348 body.push(0);
349 } else {
350 body.extend_from_slice(blob);
351 }
352 hdr.message(&body)
353}
354
355pub const SHARE_TYPE_DISK: u8 = 0x01;
357pub const SHARE_TYPE_PIPE: u8 = 0x02;
358
359pub fn tree_connect_response(hdr: &ResponseHeader, share_type: u8, maximal_access: u32) -> Vec<u8> {
361 let mut body = Vec::with_capacity(16);
362 body.extend_from_slice(&16u16.to_le_bytes()); body.push(share_type);
364 body.push(0); body.extend_from_slice(&0u32.to_le_bytes()); body.extend_from_slice(&0u32.to_le_bytes()); body.extend_from_slice(&maximal_access.to_le_bytes()); hdr.message(&body)
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
373pub struct FileMeta {
374 pub is_directory: bool,
375 pub size: u64,
376 pub time: u64,
378 pub file_index: u64,
380}
381
382pub const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x0000_0010;
383pub const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x0000_0020;
384pub const FILE_ATTRIBUTE_NORMAL: u32 = 0x0000_0080;
385
386impl FileMeta {
387 pub fn attributes(&self) -> u32 {
388 if self.is_directory {
389 FILE_ATTRIBUTE_DIRECTORY
390 } else {
391 FILE_ATTRIBUTE_NORMAL
392 }
393 }
394
395 pub fn allocation_size(&self) -> u64 {
397 self.size.div_ceil(4096) * 4096
398 }
399
400 fn times(&self, out: &mut Vec<u8>) {
401 for _ in 0..4 {
402 out.extend_from_slice(&self.time.to_le_bytes());
403 }
404 }
405}
406
407pub const FILE_OPENED: u32 = 0x0000_0001;
409pub const FILE_CREATED: u32 = 0x0000_0002;
410
411pub fn create_response(
413 hdr: &ResponseHeader,
414 file_id: &[u8; 16],
415 meta: &FileMeta,
416 create_action: u32,
417) -> Vec<u8> {
418 let mut body = Vec::with_capacity(89);
419 body.extend_from_slice(&89u16.to_le_bytes()); body.push(0); body.push(0); body.extend_from_slice(&create_action.to_le_bytes());
423 meta.times(&mut body); body.extend_from_slice(&meta.allocation_size().to_le_bytes());
425 body.extend_from_slice(&meta.size.to_le_bytes()); body.extend_from_slice(&meta.attributes().to_le_bytes());
427 body.extend_from_slice(&0u32.to_le_bytes()); body.extend_from_slice(file_id);
429 body.extend_from_slice(&0u32.to_le_bytes()); body.extend_from_slice(&0u32.to_le_bytes()); body.push(0); hdr.message(&body)
433}
434
435pub const CLOSE_FLAG_POSTQUERY_ATTRIB: u16 = 0x0001;
437
438pub fn close_response(hdr: &ResponseHeader, meta: Option<&FileMeta>) -> Vec<u8> {
442 let mut body = Vec::with_capacity(60);
443 body.extend_from_slice(&60u16.to_le_bytes()); match meta {
445 Some(meta) => {
446 body.extend_from_slice(&CLOSE_FLAG_POSTQUERY_ATTRIB.to_le_bytes());
447 body.extend_from_slice(&0u32.to_le_bytes()); meta.times(&mut body);
449 body.extend_from_slice(&meta.allocation_size().to_le_bytes());
450 body.extend_from_slice(&meta.size.to_le_bytes());
451 body.extend_from_slice(&meta.attributes().to_le_bytes());
452 }
453 None => body.resize(60, 0),
454 }
455 hdr.message(&body)
456}
457
458pub const READ_DATA_OFFSET: u8 = (HEADER_LEN + 16) as u8;
460
461pub fn read_response(hdr: &ResponseHeader, data: &[u8]) -> Vec<u8> {
463 let mut body = Vec::with_capacity(16 + data.len());
464 body.extend_from_slice(&17u16.to_le_bytes()); body.push(READ_DATA_OFFSET); body.push(0); body.extend_from_slice(&(data.len() as u32).to_le_bytes()); body.extend_from_slice(&0u32.to_le_bytes()); body.extend_from_slice(&0u32.to_le_bytes()); body.extend_from_slice(data);
471 if data.is_empty() {
472 body.push(0);
473 }
474 hdr.message(&body)
475}
476
477pub fn write_response(hdr: &ResponseHeader, count: u32) -> Vec<u8> {
479 let mut body = Vec::with_capacity(17);
480 body.extend_from_slice(&17u16.to_le_bytes()); body.extend_from_slice(&0u16.to_le_bytes()); body.extend_from_slice(&count.to_le_bytes()); body.extend_from_slice(&0u32.to_le_bytes()); body.extend_from_slice(&0u16.to_le_bytes()); body.extend_from_slice(&0u16.to_le_bytes()); body.push(0); hdr.message(&body)
488}
489
490fn output_buffer_response(hdr: &ResponseHeader, buffer: &[u8]) -> Vec<u8> {
493 let mut body = Vec::with_capacity(8 + buffer.len().max(1));
494 body.extend_from_slice(&9u16.to_le_bytes()); body.extend_from_slice(&((HEADER_LEN + 8) as u16).to_le_bytes()); body.extend_from_slice(&(buffer.len() as u32).to_le_bytes()); if buffer.is_empty() {
498 body.push(0);
499 } else {
500 body.extend_from_slice(buffer);
501 }
502 hdr.message(&body)
503}
504
505pub fn query_info_response(hdr: &ResponseHeader, buffer: &[u8]) -> Vec<u8> {
507 output_buffer_response(hdr, buffer)
508}
509
510pub fn query_directory_response(hdr: &ResponseHeader, entries: &[u8]) -> Vec<u8> {
512 output_buffer_response(hdr, entries)
513}
514
515pub const INFO_FILE: u8 = 0x01;
521pub const INFO_FILESYSTEM: u8 = 0x02;
522pub const INFO_SECURITY: u8 = 0x03;
523pub const INFO_QUOTA: u8 = 0x04;
524
525pub mod file_class {
527 pub const DIRECTORY: u8 = 1;
528 pub const FULL_DIRECTORY: u8 = 2;
529 pub const BOTH_DIRECTORY: u8 = 3;
530 pub const BASIC: u8 = 4;
531 pub const STANDARD: u8 = 5;
532 pub const INTERNAL: u8 = 6;
533 pub const EA: u8 = 7;
534 pub const ACCESS: u8 = 8;
535 pub const NAMES: u8 = 12;
536 pub const POSITION: u8 = 14;
537 pub const MODE: u8 = 16;
538 pub const ALIGNMENT: u8 = 17;
539 pub const ALL: u8 = 18;
540 pub const STREAM: u8 = 22;
541 pub const NETWORK_OPEN: u8 = 34;
542 pub const ATTRIBUTE_TAG: u8 = 35;
543 pub const ID_BOTH_DIRECTORY: u8 = 37;
544 pub const ID_FULL_DIRECTORY: u8 = 38;
545}
546
547pub mod fs_class {
549 pub const VOLUME: u8 = 1;
550 pub const SIZE: u8 = 3;
551 pub const DEVICE: u8 = 4;
552 pub const ATTRIBUTE: u8 = 5;
553 pub const FULL_SIZE: u8 = 7;
554 pub const SECTOR_SIZE: u8 = 11;
555}
556
557pub fn file_class_needs_size(class: u8) -> bool {
560 matches!(
561 class,
562 file_class::STANDARD | file_class::ALL | file_class::NETWORK_OPEN | file_class::STREAM
563 )
564}
565
566pub fn file_info(class: u8, meta: &FileMeta) -> Option<Vec<u8>> {
568 let mut out = Vec::new();
569 match class {
570 file_class::BASIC => basic_info(meta, &mut out),
571 file_class::STANDARD => standard_info(meta, &mut out),
572 file_class::INTERNAL => out.extend_from_slice(&meta.file_index.to_le_bytes()),
573 file_class::EA => out.extend_from_slice(&0u32.to_le_bytes()),
574 file_class::ACCESS => out.extend_from_slice(&0x001F_01FFu32.to_le_bytes()),
575 file_class::POSITION => out.extend_from_slice(&0u64.to_le_bytes()),
576 file_class::MODE => out.extend_from_slice(&0u32.to_le_bytes()),
577 file_class::ALIGNMENT => out.extend_from_slice(&0u32.to_le_bytes()),
578 file_class::ALL => {
579 basic_info(meta, &mut out); standard_info(meta, &mut out); out.extend_from_slice(&meta.file_index.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0x001F_01FFu32.to_le_bytes()); out.extend_from_slice(&0u64.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); }
589 file_class::STREAM => {
590 if meta.is_directory {
591 return Some(Vec::new());
592 }
593 let name: Vec<u8> = utf16le("::$DATA");
594 out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&(name.len() as u32).to_le_bytes());
596 out.extend_from_slice(&meta.size.to_le_bytes());
597 out.extend_from_slice(&meta.allocation_size().to_le_bytes());
598 out.extend_from_slice(&name);
599 }
600 file_class::NETWORK_OPEN => {
601 meta.times(&mut out);
602 out.extend_from_slice(&meta.allocation_size().to_le_bytes());
603 out.extend_from_slice(&meta.size.to_le_bytes());
604 out.extend_from_slice(&meta.attributes().to_le_bytes());
605 out.extend_from_slice(&0u32.to_le_bytes()); }
607 file_class::ATTRIBUTE_TAG => {
608 out.extend_from_slice(&meta.attributes().to_le_bytes());
609 out.extend_from_slice(&0u32.to_le_bytes()); }
611 _ => return None,
612 }
613 Some(out)
614}
615
616fn basic_info(meta: &FileMeta, out: &mut Vec<u8>) {
617 meta.times(out);
618 out.extend_from_slice(&meta.attributes().to_le_bytes());
619 out.extend_from_slice(&0u32.to_le_bytes()); }
621
622fn standard_info(meta: &FileMeta, out: &mut Vec<u8>) {
623 out.extend_from_slice(&meta.allocation_size().to_le_bytes());
624 out.extend_from_slice(&meta.size.to_le_bytes()); out.extend_from_slice(&1u32.to_le_bytes()); out.push(0); out.push(meta.is_directory as u8); out.extend_from_slice(&0u16.to_le_bytes()); }
630
631const FS_TOTAL_UNITS: u64 = 262_144;
634const FS_FREE_UNITS: u64 = 131_072;
635const FS_SECTORS_PER_UNIT: u32 = 8;
636const FS_BYTES_PER_SECTOR: u32 = 512;
637const FS_SERIAL: u32 = 0x4E47_5342; pub fn fs_info(class: u8, volume_label: &str, created: u64) -> Option<Vec<u8>> {
641 let mut out = Vec::new();
642 match class {
643 fs_class::VOLUME => {
644 let label = utf16le(volume_label);
645 out.extend_from_slice(&created.to_le_bytes()); out.extend_from_slice(&FS_SERIAL.to_le_bytes());
647 out.extend_from_slice(&(label.len() as u32).to_le_bytes());
648 out.push(0); out.push(0); out.extend_from_slice(&label);
651 }
652 fs_class::SIZE => {
653 out.extend_from_slice(&FS_TOTAL_UNITS.to_le_bytes());
654 out.extend_from_slice(&FS_FREE_UNITS.to_le_bytes());
655 out.extend_from_slice(&FS_SECTORS_PER_UNIT.to_le_bytes());
656 out.extend_from_slice(&FS_BYTES_PER_SECTOR.to_le_bytes());
657 }
658 fs_class::DEVICE => {
659 out.extend_from_slice(&0x0000_0007u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); }
662 fs_class::ATTRIBUTE => {
663 let name = utf16le("NTFS");
664 out.extend_from_slice(&0x0000_0007u32.to_le_bytes());
666 out.extend_from_slice(&255u32.to_le_bytes()); out.extend_from_slice(&(name.len() as u32).to_le_bytes());
668 out.extend_from_slice(&name);
669 }
670 fs_class::FULL_SIZE => {
671 out.extend_from_slice(&FS_TOTAL_UNITS.to_le_bytes());
672 out.extend_from_slice(&FS_FREE_UNITS.to_le_bytes()); out.extend_from_slice(&FS_FREE_UNITS.to_le_bytes()); out.extend_from_slice(&FS_SECTORS_PER_UNIT.to_le_bytes());
675 out.extend_from_slice(&FS_BYTES_PER_SECTOR.to_le_bytes());
676 }
677 fs_class::SECTOR_SIZE => {
678 let sector = FS_BYTES_PER_SECTOR;
679 out.extend_from_slice(§or.to_le_bytes()); out.extend_from_slice(§or.to_le_bytes()); out.extend_from_slice(§or.to_le_bytes()); out.extend_from_slice(§or.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); }
687 _ => return None,
688 }
689 Some(out)
690}
691
692pub fn directory_class_supported(class: u8) -> bool {
694 matches!(
695 class,
696 file_class::DIRECTORY
697 | file_class::FULL_DIRECTORY
698 | file_class::BOTH_DIRECTORY
699 | file_class::NAMES
700 | file_class::ID_BOTH_DIRECTORY
701 | file_class::ID_FULL_DIRECTORY
702 )
703}
704
705pub fn directory_entry(class: u8, name: &str, meta: &FileMeta) -> Vec<u8> {
707 let name = utf16le(name);
708 let mut e = Vec::with_capacity(104 + name.len());
709 e.extend_from_slice(&0u32.to_le_bytes()); e.extend_from_slice(&0u32.to_le_bytes()); if class == file_class::NAMES {
712 e.extend_from_slice(&(name.len() as u32).to_le_bytes());
713 e.extend_from_slice(&name);
714 return e;
715 }
716 meta.times(&mut e);
717 e.extend_from_slice(&meta.size.to_le_bytes()); e.extend_from_slice(&meta.allocation_size().to_le_bytes());
719 e.extend_from_slice(&meta.attributes().to_le_bytes());
720 e.extend_from_slice(&(name.len() as u32).to_le_bytes()); match class {
722 file_class::DIRECTORY => {}
723 file_class::FULL_DIRECTORY => e.extend_from_slice(&0u32.to_le_bytes()), file_class::BOTH_DIRECTORY => {
725 e.extend_from_slice(&0u32.to_le_bytes()); e.push(0); e.push(0); e.extend_from_slice(&[0u8; 24]); }
730 file_class::ID_BOTH_DIRECTORY => {
731 e.extend_from_slice(&0u32.to_le_bytes()); e.push(0); e.push(0); e.extend_from_slice(&[0u8; 24]); e.extend_from_slice(&0u16.to_le_bytes()); e.extend_from_slice(&meta.file_index.to_le_bytes()); }
738 file_class::ID_FULL_DIRECTORY => {
739 e.extend_from_slice(&0u32.to_le_bytes()); e.extend_from_slice(&0u32.to_le_bytes()); e.extend_from_slice(&meta.file_index.to_le_bytes()); }
743 _ => {}
744 }
745 e.extend_from_slice(&name);
746 e
747}
748
749pub fn join_directory_entries(entries: &[Vec<u8>]) -> Vec<u8> {
752 let mut out = Vec::new();
753 for (i, entry) in entries.iter().enumerate() {
754 let start = out.len();
755 out.extend_from_slice(entry);
756 if i + 1 < entries.len() {
757 while (out.len() - start) % 8 != 0 {
758 out.push(0);
759 }
760 let next = (out.len() - start) as u32;
761 out[start..start + 4].copy_from_slice(&next.to_le_bytes());
762 }
763 }
764 out
765}
766
767pub fn padded_len(entry: &[u8]) -> usize {
770 entry.len().div_ceil(8) * 8
771}
772
773#[derive(Debug, Clone, Copy, PartialEq, Eq)]
786pub enum ChainError {
787 NotSmb2,
789 BadNextCommand(RequestHeader),
792}
793
794#[derive(Debug, Clone, Copy)]
796pub struct ChainedRequest<'a> {
797 pub header: RequestHeader,
798 pub message: &'a [u8],
800 pub next: Option<usize>,
802}
803
804pub fn next_in_chain(rest: &[u8]) -> Result<ChainedRequest<'_>, ChainError> {
806 let header = RequestHeader::parse(rest).ok_or(ChainError::NotSmb2)?;
807 let next = header.next_command as usize;
808 if next == 0 {
809 return Ok(ChainedRequest {
810 header,
811 message: rest,
812 next: None,
813 });
814 }
815 if next < HEADER_LEN || next > rest.len() || !next.is_multiple_of(8) {
816 return Err(ChainError::BadNextCommand(header));
817 }
818 Ok(ChainedRequest {
819 header,
820 message: &rest[..next],
821 next: Some(next),
822 })
823}
824
825pub fn body(message: &[u8]) -> &[u8] {
827 message.get(HEADER_LEN..).unwrap_or_default()
828}
829
830fn buffer(message: &[u8], offset: usize, len: usize) -> Option<&[u8]> {
832 message.get(offset..offset.checked_add(len)?)
833}
834
835fn file_id(body: &[u8], at: usize) -> Option<[u8; 16]> {
836 body.get(at..at + 16)?.try_into().ok()
837}
838
839pub fn parse_negotiate(body: &[u8]) -> Option<Vec<u16>> {
841 if body.len() < 36 {
842 return None;
843 }
844 let count = le16(body, 2) as usize;
845 let list = body.get(36..36 + 2 * count)?;
846 Some(
847 list.chunks_exact(2)
848 .map(|c| u16::from_le_bytes([c[0], c[1]]))
849 .collect(),
850 )
851}
852
853pub fn parse_session_setup(message: &[u8]) -> Option<&[u8]> {
856 let body = body(message);
857 if body.len() < 24 {
858 return None;
859 }
860 let offset = le16(body, 12) as usize;
861 let len = le16(body, 14) as usize;
862 if len == 0 {
863 return Some(&[]);
864 }
865 buffer(message, offset, len)
866}
867
868pub fn parse_tree_connect(message: &[u8]) -> Option<String> {
870 let body = body(message);
871 if body.len() < 8 {
872 return None;
873 }
874 let offset = le16(body, 4) as usize;
875 let len = le16(body, 6) as usize;
876 from_utf16le(buffer(message, offset, len)?)
877}
878
879#[derive(Debug, Clone, PartialEq, Eq)]
881pub struct CreateRequest {
882 pub disposition: u32,
883 pub options: u32,
884 pub path: String,
886}
887
888pub fn parse_create(message: &[u8]) -> Option<CreateRequest> {
891 let body = body(message);
892 if body.len() < 56 {
893 return None;
894 }
895 let disposition = le32(body, 36);
896 let options = le32(body, 40);
897 let name_offset = le16(body, 44) as usize;
898 let name_len = le16(body, 46) as usize;
899 let name = if name_len == 0 {
900 String::new()
901 } else {
902 from_utf16le(buffer(message, name_offset, name_len)?)?
903 };
904 let name = name.replace('\\', "/");
905 Some(CreateRequest {
906 disposition,
907 options,
908 path: format!("/{}", name.trim_matches('/')),
909 })
910}
911
912pub fn parse_close(body: &[u8]) -> Option<(u16, [u8; 16])> {
914 if body.len() < 24 {
915 return None;
916 }
917 Some((le16(body, 2), file_id(body, 8)?))
918}
919
920pub fn parse_flush(body: &[u8]) -> Option<[u8; 16]> {
922 if body.len() < 24 {
923 return None;
924 }
925 file_id(body, 8)
926}
927
928#[derive(Debug, Clone, Copy, PartialEq, Eq)]
930pub struct ReadRequest {
931 pub length: u32,
933 pub offset: u64,
934 pub file_id: [u8; 16],
935}
936
937pub fn parse_read(body: &[u8]) -> Option<ReadRequest> {
939 if body.len() < 48 {
940 return None;
941 }
942 Some(ReadRequest {
943 length: le32(body, 4),
944 offset: le64(body, 8),
945 file_id: file_id(body, 16)?,
946 })
947}
948
949#[derive(Debug, Clone, Copy, PartialEq, Eq)]
951pub struct WriteRequest<'a> {
952 pub length: u32,
955 pub offset: u64,
956 pub file_id: [u8; 16],
957 pub data: Option<&'a [u8]>,
959}
960
961pub fn parse_write(message: &[u8]) -> Option<WriteRequest<'_>> {
963 let body = body(message);
964 if body.len() < 48 {
965 return None;
966 }
967 let data_offset = le16(body, 2) as usize;
968 let length = le32(body, 4);
969 Some(WriteRequest {
970 length,
971 offset: le64(body, 8),
972 file_id: file_id(body, 16)?,
973 data: buffer(message, data_offset, length as usize),
974 })
975}
976
977#[derive(Debug, Clone, Copy, PartialEq, Eq)]
979pub struct QueryInfoRequest {
980 pub info_type: u8,
981 pub class: u8,
982 pub output_len: u32,
983 pub file_id: [u8; 16],
984}
985
986pub fn parse_query_info(body: &[u8]) -> Option<QueryInfoRequest> {
990 if body.len() < 40 {
991 return None;
992 }
993 Some(QueryInfoRequest {
994 info_type: body[2],
995 class: body[3],
996 output_len: le32(body, 4),
997 file_id: file_id(body, 24)?,
998 })
999}
1000
1001#[derive(Debug, Clone, PartialEq, Eq)]
1003pub struct QueryDirectoryRequest {
1004 pub class: u8,
1005 pub flags: u8,
1006 pub file_id: [u8; 16],
1007 pub pattern: String,
1009 pub output_len: u32,
1010}
1011
1012pub fn parse_query_directory(message: &[u8]) -> Option<QueryDirectoryRequest> {
1016 let body = body(message);
1017 if body.len() < 32 {
1018 return None;
1019 }
1020 let name_offset = le16(body, 24) as usize;
1021 let name_len = le16(body, 26) as usize;
1022 let pattern = if name_len == 0 {
1023 "*".to_string()
1024 } else {
1025 from_utf16le(buffer(message, name_offset, name_len)?)?
1026 };
1027 Some(QueryDirectoryRequest {
1028 class: body[2],
1029 flags: body[3],
1030 file_id: file_id(body, 8)?,
1031 pattern,
1032 output_len: le32(body, 28),
1033 })
1034}
1035
1036pub fn utf16le(s: &str) -> Vec<u8> {
1041 s.encode_utf16().flat_map(|c| c.to_le_bytes()).collect()
1042}
1043
1044pub fn from_utf16le(bytes: &[u8]) -> Option<String> {
1046 if !bytes.len().is_multiple_of(2) {
1047 return None;
1048 }
1049 let units: Vec<u16> = bytes
1050 .chunks_exact(2)
1051 .map(|c| u16::from_le_bytes([c[0], c[1]]))
1052 .take_while(|&c| c != 0)
1053 .collect();
1054 String::from_utf16(&units).ok()
1055}
1056
1057pub fn filetime_from_unix(secs: i64, nanos: u32) -> u64 {
1059 const EPOCH_DIFF_SECS: i64 = 11_644_473_600;
1060 let secs = secs.saturating_add(EPOCH_DIFF_SECS).max(0) as u64;
1061 secs.saturating_mul(10_000_000) + (nanos / 100) as u64
1062}
1063
1064pub fn le16(b: &[u8], off: usize) -> u16 {
1065 u16::from_le_bytes([b[off], b[off + 1]])
1066}
1067
1068pub fn le32(b: &[u8], off: usize) -> u32 {
1069 u32::from_le_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]])
1070}
1071
1072pub fn le64(b: &[u8], off: usize) -> u64 {
1073 let mut a = [0u8; 8];
1074 a.copy_from_slice(&b[off..off + 8]);
1075 u64::from_le_bytes(a)
1076}