1pub const BODY: usize = 64;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub struct FileId(pub [u8; 16]);
14
15impl FileId {
16 pub const ZERO: FileId = FileId([0u8; 16]);
18 pub const LEN: usize = 16;
20 pub const WILDCARD: [u8; 16] = [0xFF; 16];
23}
24
25fn g16(b: &[u8], o: usize) -> u16 {
26 b.get(o..o + 2)
27 .map(|s| u16::from_le_bytes([s[0], s[1]]))
28 .unwrap_or(0)
29}
30fn g32(b: &[u8], o: usize) -> u32 {
31 b.get(o..o + 4)
32 .map(|s| u32::from_le_bytes(s.try_into().unwrap()))
33 .unwrap_or(0)
34}
35fn g64(b: &[u8], o: usize) -> u64 {
36 b.get(o..o + 8)
37 .map(|s| u64::from_le_bytes(s.try_into().unwrap()))
38 .unwrap_or(0)
39}
40
41#[allow(dead_code)]
45mod create_off {
46 use super::BODY;
47
48 pub const STRUCT: usize = BODY;
50 pub const OPLOCK_LEVEL: usize = BODY + 3;
52 pub const IMPERSONATION: usize = BODY + 8;
55 pub const CREATE_FLAGS: usize = BODY + 16;
57 pub const DESIRED_ACCESS: usize = BODY + 24;
59 pub const ATTRIBUTES: usize = BODY + 28;
61 pub const SHARE_ACCESS: usize = BODY + 32;
63 pub const DISPOSITION: usize = BODY + 36;
65 pub const OPTIONS: usize = BODY + 40;
67 pub const NAME_OFFSET: usize = BODY + 44;
69 pub const NAME_LENGTH: usize = BODY + 46;
71 pub const CTX_OFFSET: usize = BODY + 48;
73 pub const CTX_LENGTH: usize = BODY + 52;
75 pub const FIXED_END: usize = BODY + 56;
77}
78
79#[derive(Debug)]
81pub struct CreateReq {
82 pub desired_access: u32,
84 pub attrs: u32,
86 pub share_access: u32,
88 pub disposition: u32,
90 pub options: u32,
92 pub name: String,
94 pub oplock_level: u8,
96 pub lease: Option<LeaseReq>,
98 pub durable: Option<DurableReq>,
100 pub durable_ctx_tags: u8,
103 pub app_instance_id: Option<[u8; 16]>,
105 pub app_instance_version: Option<(u64, u64)>,
108}
109
110#[derive(Debug, Clone, Copy)]
113pub enum DurableReq {
114 RequestV1,
116 RequestV2 {
118 timeout: u32,
120 flags: u32,
122 create_guid: [u8; 16],
124 },
125 ReconnectV1 {
127 file_id: [u8; 16],
129 },
130 ReconnectV2 {
132 file_id: [u8; 16],
134 create_guid: [u8; 16],
136 flags: u32,
138 },
139}
140
141pub mod durable {
143 pub const REQ_V1: &[u8] = b"DHnQ";
145 pub const RECONNECT_V1: &[u8] = b"DHnC";
147 pub const REQ_V2: &[u8] = b"DH2Q";
149 pub const RECONNECT_V2: &[u8] = b"DH2C";
151 pub const FLAG_PERSISTENT: u32 = 0x0000_0002;
153
154 pub mod tag {
158 pub const REQ_V1: u8 = 0x01;
160 pub const RECONNECT_V1: u8 = 0x02;
162 pub const REQ_V2: u8 = 0x04;
164 pub const RECONNECT_V2: u8 = 0x08;
166 }
167}
168
169fn walk_create_contexts(
171 frame: &[u8],
172 ctx_off: usize,
173 ctx_len: usize,
174 mut f: impl FnMut(&[u8], &[u8]),
175) {
176 if ctx_len == 0 || ctx_off < BODY {
177 return;
178 }
179 let mut pos = ctx_off;
180 let end = (ctx_off + ctx_len).min(frame.len());
181 loop {
182 if pos + 16 > end {
183 return;
184 }
185 let next = g32(frame, pos) as usize;
186 let name_off = g16(frame, pos + 4) as usize;
187 let name_len = g16(frame, pos + 6) as usize;
188 let data_off = g16(frame, pos + 10) as usize;
189 let data_len = g32(frame, pos + 12) as usize;
190 if let (Some(name), Some(data)) = (
191 frame.get(pos + name_off..pos + name_off + name_len),
192 frame.get(pos + data_off..(pos + data_off + data_len).min(frame.len())),
193 ) {
194 f(name, data);
195 }
196 if next == 0 {
197 return;
198 }
199 pos += next;
200 }
201}
202
203fn parse_durable_context(frame: &[u8], ctx_off: usize, ctx_len: usize) -> Option<DurableReq> {
207 let mut found: Option<DurableReq> = None;
208 walk_create_contexts(frame, ctx_off, ctx_len, |name, data| {
209 if found.is_some() {
210 return;
211 }
212 found = if name == durable::REQ_V2 && data.len() >= 32 {
213 Some(DurableReq::RequestV2 {
214 timeout: u32::from_le_bytes(data[0..4].try_into().unwrap()),
215 flags: u32::from_le_bytes(data[4..8].try_into().unwrap()),
216 create_guid: data[16..32].try_into().unwrap(),
217 })
218 } else if name == durable::RECONNECT_V2 && data.len() >= 32 {
219 Some(DurableReq::ReconnectV2 {
220 file_id: data[0..16].try_into().unwrap(),
221 create_guid: data[16..32].try_into().unwrap(),
222 flags: data
223 .get(32..36)
224 .map(|b| u32::from_le_bytes(b.try_into().unwrap()))
225 .unwrap_or(0),
226 })
227 } else if name == durable::RECONNECT_V1 && data.len() >= 16 {
228 Some(DurableReq::ReconnectV1 {
229 file_id: data[0..16].try_into().unwrap(),
230 })
231 } else if name == durable::REQ_V1 {
232 Some(DurableReq::RequestV1)
233 } else {
234 None
235 };
236 });
237 found
238}
239
240fn durable_context_tags(frame: &[u8], ctx_off: usize, ctx_len: usize) -> u8 {
242 let mut tags = 0u8;
243 walk_create_contexts(frame, ctx_off, ctx_len, |name, _data| {
244 tags |= match name {
245 n if n == durable::REQ_V1 => durable::tag::REQ_V1,
246 n if n == durable::RECONNECT_V1 => durable::tag::RECONNECT_V1,
247 n if n == durable::REQ_V2 => durable::tag::REQ_V2,
248 n if n == durable::RECONNECT_V2 => durable::tag::RECONNECT_V2,
249 _ => 0,
250 };
251 });
252 tags
253}
254
255pub mod app_instance {
258 pub const ID_NAME: [u8; 16] = [
260 0x45, 0xBC, 0xA6, 0x6A, 0xEF, 0xA7, 0xF7, 0x4A, 0x90, 0x08, 0xFA, 0x46, 0x2E, 0x14, 0x4D,
261 0x74,
262 ];
263 pub const VERSION_NAME: [u8; 16] = [
265 0xB9, 0x82, 0xD0, 0xB7, 0x3B, 0x56, 0x07, 0x4F, 0xA0, 0x7B, 0x52, 0x4A, 0x81, 0x16, 0xA0,
266 0x10,
267 ];
268}
269
270fn parse_app_instance_id(frame: &[u8], ctx_off: usize, ctx_len: usize) -> Option<[u8; 16]> {
273 let mut found = None;
274 walk_create_contexts(frame, ctx_off, ctx_len, |name, data| {
275 if found.is_none() && name == app_instance::ID_NAME && data.len() >= 20 {
276 found = Some(data[4..20].try_into().unwrap());
277 }
278 });
279 found
280}
281
282fn parse_app_instance_version(frame: &[u8], ctx_off: usize, ctx_len: usize) -> Option<(u64, u64)> {
285 let mut found = None;
286 walk_create_contexts(frame, ctx_off, ctx_len, |name, data| {
287 if found.is_none() && name == app_instance::VERSION_NAME && data.len() >= 24 {
288 let high = u64::from_le_bytes(data[8..16].try_into().unwrap());
289 let low = u64::from_le_bytes(data[16..24].try_into().unwrap());
290 found = Some((high, low));
291 }
292 });
293 found
294}
295
296pub fn durable_v2_resp_data(timeout: u32, flags: u32) -> Vec<u8> {
298 let mut d = Vec::with_capacity(8);
299 d.extend_from_slice(&timeout.to_le_bytes());
300 d.extend_from_slice(&flags.to_le_bytes());
301 d
302}
303
304pub fn durable_v1_resp_data() -> Vec<u8> {
307 vec![0u8; 8]
308}
309
310#[derive(Debug, Clone, Copy)]
313pub struct LeaseReq {
314 pub key: [u8; 16],
316 pub state: u32,
318 pub flags: u32,
320 pub parent_key: [u8; 16],
322 pub epoch: u16,
324 pub v2: bool,
326}
327
328impl LeaseReq {
329 fn parse_data(d: &[u8]) -> Option<LeaseReq> {
331 if d.len() < 32 {
332 return None;
333 }
334 let v2 = d.len() >= 52;
335 Some(LeaseReq {
336 key: d.get(0..16)?.try_into().ok()?,
337 state: u32::from_le_bytes(d.get(16..20)?.try_into().ok()?),
338 flags: u32::from_le_bytes(d.get(20..24)?.try_into().ok()?),
339 parent_key: if v2 {
340 d.get(32..48)?.try_into().ok()?
341 } else {
342 [0u8; 16]
343 },
344 epoch: if v2 {
345 u16::from_le_bytes(d.get(48..50)?.try_into().ok()?)
346 } else {
347 0
348 },
349 v2,
350 })
351 }
352}
353
354fn parse_lease_context(frame: &[u8], ctx_off: usize, ctx_len: usize) -> Option<LeaseReq> {
357 if ctx_len == 0 || ctx_off < BODY {
358 return None;
359 }
360 let mut pos = ctx_off;
361 let end = (ctx_off + ctx_len).min(frame.len());
362 loop {
363 if pos + 16 > end {
364 return None;
365 }
366 let next = g32(frame, pos) as usize;
367 let name_off = g16(frame, pos + 4) as usize;
368 let name_len = g16(frame, pos + 6) as usize;
369 let data_off = g16(frame, pos + 10) as usize;
370 let data_len = g32(frame, pos + 12) as usize;
371 let name = frame.get(pos + name_off..pos + name_off + name_len);
372 if name == Some(lease::CONTEXT_NAME) {
373 let data = frame.get(pos + data_off..(pos + data_off + data_len).min(frame.len()))?;
374 return LeaseReq::parse_data(data);
375 }
376 if next == 0 {
377 return None;
378 }
379 pos += next;
380 }
381}
382
383impl CreateReq {
384 pub fn parse(frame: &[u8]) -> Option<CreateReq> {
386 if frame.len() < create_off::FIXED_END || g16(frame, create_off::STRUCT) != 57 {
387 return None;
388 }
389 let name_off = g16(frame, create_off::NAME_OFFSET) as usize;
390 let name_len = g16(frame, create_off::NAME_LENGTH) as usize;
391 let raw = if name_len > 0 && name_off >= BODY {
392 frame.get(name_off..(name_off + name_len).min(frame.len()))?
393 } else {
394 &[]
395 };
396 let units: Vec<u16> = raw
398 .chunks_exact(2)
399 .map(|c| c[0] as u16 | ((c[1] as u16) << 8))
400 .take_while(|&u| u != 0)
401 .collect();
402 let ctx_off = g32(frame, create_off::CTX_OFFSET) as usize;
403 let ctx_len = g32(frame, create_off::CTX_LENGTH) as usize;
404 Some(CreateReq {
405 desired_access: g32(frame, create_off::DESIRED_ACCESS),
406 attrs: g32(frame, create_off::ATTRIBUTES),
407 share_access: g32(frame, create_off::SHARE_ACCESS),
408 disposition: g32(frame, create_off::DISPOSITION),
409 options: g32(frame, create_off::OPTIONS),
410 name: String::from_utf16_lossy(&units),
411 oplock_level: *frame.get(create_off::OPLOCK_LEVEL).unwrap_or(&0),
412 lease: parse_lease_context(frame, ctx_off, ctx_len),
413 durable: parse_durable_context(frame, ctx_off, ctx_len),
414 durable_ctx_tags: durable_context_tags(frame, ctx_off, ctx_len),
415 app_instance_id: parse_app_instance_id(frame, ctx_off, ctx_len),
416 app_instance_version: parse_app_instance_version(frame, ctx_off, ctx_len),
417 })
418 }
419}
420
421pub mod oplock {
423 pub const NONE: u8 = 0x00;
425 pub const LEVEL_II: u8 = 0x01;
427 pub const EXCLUSIVE: u8 = 0x08;
429 pub const BATCH: u8 = 0x09;
431 pub const LEASE: u8 = 0xFF;
433}
434
435pub mod lease {
437 pub const NONE: u32 = 0x00;
439 pub const READ_CACHING: u32 = 0x01;
441 pub const HANDLE_CACHING: u32 = 0x02;
443 pub const WRITE_CACHING: u32 = 0x04;
445 pub const RH: u32 = READ_CACHING | HANDLE_CACHING;
447 pub const RWH: u32 = READ_CACHING | WRITE_CACHING | HANDLE_CACHING;
449 pub const CONTEXT_NAME: &[u8] = b"RqLs";
451 pub const BREAK_FLAG_ACK_REQUIRED: u32 = 0x01;
453}
454
455#[derive(Debug, Clone, Copy)]
457pub struct LeaseResp {
458 pub key: [u8; 16],
460 pub state: u32,
462 pub flags: u32,
464 pub epoch: u16,
466 pub v2: bool,
468}
469
470pub fn lease_context_data(l: &LeaseResp) -> Vec<u8> {
473 let mut c = Vec::new();
474 c.extend_from_slice(&l.key); c.extend_from_slice(&l.state.to_le_bytes()); c.extend_from_slice(&l.flags.to_le_bytes()); c.extend_from_slice(&0u64.to_le_bytes()); if l.v2 {
479 c.extend_from_slice(&[0u8; 16]); c.extend_from_slice(&l.epoch.to_le_bytes()); c.extend_from_slice(&0u16.to_le_bytes()); }
483 c
484}
485
486pub fn encode_create_contexts(entries: &[(&[u8], Vec<u8>)]) -> Vec<u8> {
490 let mut out = Vec::new();
491 for (i, (name, data)) in entries.iter().enumerate() {
492 let start = out.len();
493 let data_off = (16 + name.len()).next_multiple_of(8);
494 out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&16u16.to_le_bytes()); out.extend_from_slice(&(name.len() as u16).to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); let doff = if data.is_empty() { 0 } else { data_off as u16 };
499 out.extend_from_slice(&doff.to_le_bytes()); out.extend_from_slice(&(data.len() as u32).to_le_bytes()); out.extend_from_slice(name);
502 while out.len() - start < data_off {
503 out.push(0);
504 }
505 out.extend_from_slice(data);
506 if i + 1 < entries.len() {
507 while (out.len() - start) % 8 != 0 {
509 out.push(0);
510 }
511 let next = (out.len() - start) as u32;
512 out[start..start + 4].copy_from_slice(&next.to_le_bytes());
513 }
514 }
515 out
516}
517
518#[allow(clippy::too_many_arguments)]
525pub fn build_create_resp(
526 file_id: FileId,
527 action: u32,
528 times: [u64; 4],
529 attrs: u32,
530 alloc: u64,
531 eof: u64,
532 is_dir: bool,
533 oplock: u8,
534 contexts: &[u8],
535) -> Vec<u8> {
536 let _ = is_dir;
537 let (ctx_off, ctx_len) = if contexts.is_empty() {
539 (0u32, 0u32)
540 } else {
541 ((BODY + 88) as u32, contexts.len() as u32)
542 };
543 let mut b = Vec::with_capacity(88 + contexts.len());
544 b.extend_from_slice(&89u16.to_le_bytes()); b.push(oplock); b.push(0); b.extend_from_slice(&action.to_le_bytes()); for t in × {
549 b.extend_from_slice(&t.to_le_bytes());
550 } b.extend_from_slice(&alloc.to_le_bytes()); b.extend_from_slice(&eof.to_le_bytes()); b.extend_from_slice(&attrs.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&file_id.0); b.extend_from_slice(&ctx_off.to_le_bytes()); b.extend_from_slice(&ctx_len.to_le_bytes()); debug_assert_eq!(b.len(), 88);
559 b.extend_from_slice(contexts);
560 b
561}
562
563pub fn build_oplock_break(file_id: FileId, new_level: u8) -> Vec<u8> {
566 let mut b = Vec::with_capacity(24);
567 b.extend_from_slice(&24u16.to_le_bytes()); b.push(new_level); b.push(0); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&file_id.0); b
573}
574
575pub fn build_oplock_break_resp(file_id: FileId, level: u8) -> Vec<u8> {
578 build_oplock_break(file_id, level)
579}
580
581#[derive(Debug)]
583pub struct OplockBreakAck {
584 pub level: u8,
586 pub file_id: FileId,
588}
589
590impl OplockBreakAck {
591 pub fn parse(frame: &[u8]) -> Option<OplockBreakAck> {
593 if frame.len() < BODY + 24 || g16(frame, BODY) != 24 {
594 return None;
595 }
596 Some(OplockBreakAck {
597 level: *frame.get(BODY + 2)?,
598 file_id: FileId(frame.get(BODY + 8..BODY + 24)?.try_into().ok()?),
599 })
600 }
601}
602
603pub fn build_lease_break(key: [u8; 16], current: u32, new: u32, epoch: u16, flags: u32) -> Vec<u8> {
606 let mut b = Vec::with_capacity(44);
607 b.extend_from_slice(&44u16.to_le_bytes()); b.extend_from_slice(&epoch.to_le_bytes()); b.extend_from_slice(&flags.to_le_bytes()); b.extend_from_slice(&key); b.extend_from_slice(¤t.to_le_bytes()); b.extend_from_slice(&new.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); debug_assert_eq!(b.len(), 44);
617 b
618}
619
620pub fn build_lease_break_resp(key: [u8; 16], state: u32) -> Vec<u8> {
623 let mut b = Vec::with_capacity(36);
624 b.extend_from_slice(&36u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&key); b.extend_from_slice(&state.to_le_bytes()); b.extend_from_slice(&0u64.to_le_bytes()); debug_assert_eq!(b.len(), 36);
631 b
632}
633
634#[derive(Debug)]
637pub struct LeaseBreakAck {
638 pub key: [u8; 16],
640 pub state: u32,
642}
643
644impl LeaseBreakAck {
645 pub const STRUCTURE_SIZE: u16 = 36;
647
648 pub fn parse(frame: &[u8]) -> Option<LeaseBreakAck> {
650 if frame.len() < BODY + 36 || g16(frame, BODY) != 36 {
651 return None;
652 }
653 Some(LeaseBreakAck {
654 key: frame.get(BODY + 8..BODY + 24)?.try_into().ok()?,
655 state: g32(frame, BODY + 24),
656 })
657 }
658}
659
660#[allow(dead_code)]
664mod read_off {
665 use super::BODY;
666
667 pub const STRUCT: usize = BODY;
669 pub const LENGTH: usize = BODY + 4;
671 pub const OFFSET: usize = BODY + 8;
673 pub const FILE_ID: usize = BODY + 16;
675 pub const MIN_COUNT: usize = BODY + 32;
677 pub const CHANNEL: usize = BODY + 36;
679 pub const REMAINING: usize = BODY + 40;
681}
682
683#[derive(Debug)]
685pub struct ReadReq {
686 pub length: u32,
688 pub offset: u64,
690 pub file_id: FileId,
692 #[allow(dead_code)]
694 pub min_count: u32,
695}
696
697impl ReadReq {
698 pub fn parse(frame: &[u8]) -> Option<ReadReq> {
700 if frame.len() < read_off::STRUCT + 48 || g16(frame, read_off::STRUCT) != 49 {
701 return None;
702 }
703 Some(ReadReq {
704 length: g32(frame, read_off::LENGTH),
705 offset: g64(frame, read_off::OFFSET),
706 file_id: FileId(
707 frame
708 .get(read_off::FILE_ID..read_off::FILE_ID + 16)?
709 .try_into()
710 .ok()?,
711 ),
712 min_count: g32(frame, read_off::MIN_COUNT),
713 })
714 }
715}
716
717pub fn build_read_resp(data: &[u8]) -> Vec<u8> {
720 const DATA_OFF: usize = 80usize;
721 let mut b = Vec::with_capacity(DATA_OFF - BODY + data.len());
722 b.extend_from_slice(&17u16.to_le_bytes()); b.push(DATA_OFF as u8); b.push(0); b.extend_from_slice(&(data.len() as u32).to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); while BODY + b.len() < DATA_OFF {
729 b.push(0);
730 }
731 b.extend_from_slice(data);
732 b
733}
734
735#[allow(dead_code)]
739mod write_off {
740 use super::BODY;
741
742 pub const STRUCT: usize = BODY;
744 pub const DATA_OFFSET: usize = BODY + 2;
746 pub const LENGTH: usize = BODY + 4;
748 pub const OFFSET: usize = BODY + 8;
750 pub const FILE_ID: usize = BODY + 16;
752 pub const CHANNEL: usize = BODY + 32;
754 pub const REMAINING: usize = BODY + 36;
756 pub const CH_INFO_OFFSET: usize = BODY + 40;
758 pub const CH_INFO_LENGTH: usize = BODY + 42;
760}
761
762#[derive(Debug)]
764pub struct WriteReq {
765 pub offset: u64,
767 pub file_id: FileId,
769 pub payload: Vec<u8>,
771}
772
773impl WriteReq {
774 pub fn parse(frame: &[u8]) -> Option<WriteReq> {
776 if frame.len() < write_off::STRUCT + 48 || g16(frame, write_off::STRUCT) != 49 {
777 return None;
778 }
779 let dlen = g32(frame, write_off::LENGTH) as usize;
780 let doff = g16(frame, write_off::DATA_OFFSET) as usize;
781 let payload = if dlen > 0 && doff >= BODY {
782 frame.get(doff..(doff + dlen).min(frame.len()))?.to_vec()
783 } else {
784 Vec::new()
785 };
786 Some(WriteReq {
787 offset: g64(frame, write_off::OFFSET),
788 file_id: FileId(
789 frame
790 .get(write_off::FILE_ID..write_off::FILE_ID + 16)?
791 .try_into()
792 .ok()?,
793 ),
794 payload,
795 })
796 }
797}
798
799pub fn build_write_resp(written: u32) -> Vec<u8> {
801 let mut b = Vec::with_capacity(16);
802 b.extend_from_slice(&17u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&written.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); debug_assert_eq!(b.len(), 16);
809 b
810}
811
812mod close_off {
816 use super::BODY;
817
818 pub const STRUCT: usize = BODY;
820 pub const FLAGS: usize = BODY + 2;
822 pub const FILE_ID: usize = BODY + 8;
824}
825
826#[derive(Debug)]
828pub struct CloseReq {
829 pub query_attrs: bool,
831 pub file_id: FileId,
833}
834
835impl CloseReq {
836 pub fn parse(frame: &[u8]) -> Option<CloseReq> {
838 if frame.len() < close_off::STRUCT + 24 || g16(frame, close_off::STRUCT) != 24 {
839 return None;
840 }
841 Some(CloseReq {
842 query_attrs: g16(frame, close_off::FLAGS) & 0x0001 != 0,
843 file_id: FileId(
844 frame
845 .get(close_off::FILE_ID..close_off::FILE_ID + 16)?
846 .try_into()
847 .ok()?,
848 ),
849 })
850 }
851}
852
853pub fn build_close_resp(times: [u64; 4], alloc: u64, eof: u64, attrs: u32) -> Vec<u8> {
855 let mut b = Vec::with_capacity(60);
856 b.extend_from_slice(&60u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); for t in × {
860 b.extend_from_slice(&t.to_le_bytes());
861 }
862 b.extend_from_slice(&alloc.to_le_bytes());
863 b.extend_from_slice(&eof.to_le_bytes());
864 b.extend_from_slice(&attrs.to_le_bytes());
865 debug_assert_eq!(b.len(), 60);
866 b
867}
868
869#[derive(Debug)]
873pub struct FlushReq {
874 pub file_id: FileId,
876}
877
878impl FlushReq {
879 pub fn parse(frame: &[u8]) -> Option<FlushReq> {
881 if frame.len() < BODY + 20 || g16(frame, BODY) != 24 {
882 return None;
883 }
884 Some(FlushReq {
885 file_id: FileId(frame.get(BODY + 4..BODY + 20)?.try_into().ok()?),
886 })
887 }
888}
889
890pub fn build_flush_resp() -> Vec<u8> {
892 let mut b = Vec::with_capacity(4);
893 b.extend_from_slice(&4u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b
896}
897
898#[allow(dead_code)]
902mod qdir_off {
903 use super::BODY;
904
905 pub const STRUCT: usize = BODY;
907 pub const CLASS: usize = BODY + 2;
909 pub const FLAGS: usize = BODY + 3;
911 pub const FILE_INDEX: usize = BODY + 4;
913 pub const FILE_ID: usize = BODY + 8;
915 pub const NAME_OFFSET: usize = BODY + 24;
917 pub const NAME_LENGTH: usize = BODY + 26;
919 pub const FIXED_END: usize = BODY + 32;
921}
922
923pub mod find_flags {
925 pub const RESTART_SCANS: u8 = 0x01;
927 pub const RETURN_SINGLE_ENTRY: u8 = 0x02;
929 pub const SCAN: u8 = 0x04;
931 pub const INDEX_SPECIFIED: u8 = 0x08;
933 pub const REOPEN: u8 = 0x10;
935}
936
937#[derive(Debug)]
939pub struct QueryDirReq {
940 pub class: u8,
942 pub flags: u8,
944 pub file_index: u32,
946 pub file_id: FileId,
948 pub pattern: String,
950}
951
952impl QueryDirReq {
953 pub fn parse(frame: &[u8]) -> Option<QueryDirReq> {
955 if frame.len() < qdir_off::FIXED_END || g16(frame, qdir_off::STRUCT) != 33 {
956 return None;
957 }
958 let off = g16(frame, qdir_off::NAME_OFFSET) as usize;
959 let len = g16(frame, qdir_off::NAME_LENGTH) as usize;
960 let raw = if len > 0 && off >= BODY {
961 frame.get(off..(off + len).min(frame.len()))?
962 } else {
963 &[]
964 };
965 let units: Vec<u16> = raw
966 .chunks_exact(2)
967 .map(|c| c[0] as u16 | ((c[1] as u16) << 8))
968 .collect();
969 Some(QueryDirReq {
970 class: *frame.get(qdir_off::CLASS)?,
971 flags: *frame.get(qdir_off::FLAGS)?,
972 file_index: g32(frame, qdir_off::FILE_INDEX),
973 file_id: FileId(
974 frame
975 .get(qdir_off::FILE_ID..qdir_off::FILE_ID + 16)?
976 .try_into()
977 .ok()?,
978 ),
979 pattern: String::from_utf16_lossy(&units),
980 })
981 }
982}
983
984pub fn build_info_resp(buffer: &[u8]) -> Vec<u8> {
987 const BUF_OFF: usize = BODY + 8; let mut b = Vec::with_capacity(BUF_OFF - BODY + buffer.len());
989 b.extend_from_slice(&9u16.to_le_bytes()); b.extend_from_slice(&(BUF_OFF as u16).to_le_bytes()); b.extend_from_slice(&(buffer.len() as u32).to_le_bytes()); while !(BODY + b.len()).is_multiple_of(8) || BODY + b.len() < BUF_OFF {
993 b.push(0);
994 }
995 b.extend_from_slice(buffer);
996 b
997}
998
999#[allow(dead_code)]
1003mod qinfo_off {
1004 use super::BODY;
1005
1006 pub const STRUCT: usize = BODY;
1008 pub const INFO_TYPE: usize = BODY + 2;
1010 pub const CLASS: usize = BODY + 3;
1012 pub const OUTPUT_LEN: usize = BODY + 4;
1014 pub const INPUT_OFFSET: usize = BODY + 8;
1016 pub const INPUT_LENGTH: usize = BODY + 12;
1018 pub const ADDITIONAL: usize = BODY + 16;
1020 pub const FLAGS: usize = BODY + 20;
1022 pub const FILE_ID: usize = BODY + 24;
1024 pub const FIXED_END: usize = BODY + 40;
1026}
1027
1028pub mod info_type {
1030 pub const FILE: u8 = 0x01;
1032 pub const FS: u8 = 0x02;
1034 pub const SECURITY: u8 = 0x03;
1036 pub const QUOTA: u8 = 0x04;
1038}
1039
1040#[derive(Debug)]
1042pub struct QueryInfoReq {
1043 pub info_type: u8,
1045 pub class: u8,
1047 pub output_len: u32,
1049 pub additional: u32,
1051 pub file_id: FileId,
1053 pub input: Vec<u8>,
1055}
1056
1057impl QueryInfoReq {
1058 pub fn parse(frame: &[u8]) -> Option<QueryInfoReq> {
1060 if frame.len() < qinfo_off::FIXED_END || g16(frame, qinfo_off::STRUCT) != 41 {
1061 return None;
1062 }
1063 let off = g16(frame, qinfo_off::INPUT_OFFSET) as usize;
1064 let len = g32(frame, qinfo_off::INPUT_LENGTH) as usize;
1065 let input = if len > 0 && off >= BODY {
1066 frame.get(off..(off + len).min(frame.len()))?.to_vec()
1067 } else {
1068 Vec::new()
1069 };
1070 Some(QueryInfoReq {
1071 info_type: *frame.get(qinfo_off::INFO_TYPE)?,
1072 class: *frame.get(qinfo_off::CLASS)?,
1073 output_len: g32(frame, qinfo_off::OUTPUT_LEN),
1074 additional: g32(frame, qinfo_off::ADDITIONAL),
1075 file_id: FileId(
1076 frame
1077 .get(qinfo_off::FILE_ID..qinfo_off::FILE_ID + 16)?
1078 .try_into()
1079 .ok()?,
1080 ),
1081 input,
1082 })
1083 }
1084}
1085
1086#[allow(dead_code)]
1090mod sinfo_off {
1091 use super::BODY;
1092
1093 pub const STRUCT: usize = BODY;
1095 pub const INFO_TYPE: usize = BODY + 2;
1097 pub const CLASS: usize = BODY + 3;
1099 pub const BUFFER_LEN: usize = BODY + 4;
1101 pub const BUFFER_OFFSET: usize = BODY + 8;
1103 pub const ADDITIONAL: usize = BODY + 12;
1105 pub const FILE_ID: usize = BODY + 16;
1107 pub const FIXED_END: usize = BODY + 32;
1109}
1110
1111#[derive(Debug)]
1113pub struct SetInfoReq {
1114 pub info_type: u8,
1116 pub class: u8,
1118 pub additional: u32,
1120 pub file_id: FileId,
1122 pub buffer: Vec<u8>,
1124}
1125
1126impl SetInfoReq {
1127 pub fn parse(frame: &[u8]) -> Option<SetInfoReq> {
1129 if frame.len() < sinfo_off::FIXED_END || g16(frame, sinfo_off::STRUCT) != 33 {
1130 return None;
1131 }
1132 let off = g16(frame, sinfo_off::BUFFER_OFFSET) as usize;
1133 let len = g32(frame, sinfo_off::BUFFER_LEN) as usize;
1134 let buffer = if len > 0 && off >= BODY {
1135 frame.get(off..(off + len).min(frame.len()))?.to_vec()
1136 } else {
1137 Vec::new()
1138 };
1139 Some(SetInfoReq {
1140 info_type: *frame.get(sinfo_off::INFO_TYPE)?,
1141 class: *frame.get(sinfo_off::CLASS)?,
1142 additional: g32(frame, sinfo_off::ADDITIONAL),
1143 file_id: FileId(
1144 frame
1145 .get(sinfo_off::FILE_ID..sinfo_off::FILE_ID + 16)?
1146 .try_into()
1147 .ok()?,
1148 ),
1149 buffer,
1150 })
1151 }
1152}
1153
1154pub fn build_set_info_resp() -> Vec<u8> {
1156 2u16.to_le_bytes().to_vec()
1157}
1158
1159pub const SHAREFLAG_ENCRYPT_DATA: u32 = 0x0000_8000;
1163
1164pub const SHAREFLAG_COMPRESS_DATA: u32 = 0x0010_0000;
1166
1167pub const SHAREFLAG_CONTINUOUSLY_AVAILABLE: u32 = 0x0000_0002;
1169
1170pub const SHARE_CAP_CONTINUOUS_AVAILABILITY: u32 = 0x0000_0010;
1172
1173pub fn build_tree_connect_resp(share_type: u8, share_flags: u32, capabilities: u32) -> Vec<u8> {
1175 let mut b = Vec::with_capacity(16);
1176 b.extend_from_slice(&16u16.to_le_bytes()); b.push(share_type); b.push(0); b.extend_from_slice(&share_flags.to_le_bytes()); b.extend_from_slice(&capabilities.to_le_bytes()); b.extend_from_slice(&0x001F_01FFu32.to_le_bytes()); debug_assert_eq!(b.len(), 16);
1183 b
1184}
1185
1186pub fn build_tree_disconnect_resp() -> Vec<u8> {
1189 let mut b = Vec::with_capacity(4);
1190 b.extend_from_slice(&4u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b
1193}
1194
1195pub fn build_logoff_resp() -> Vec<u8> {
1197 let mut b = Vec::with_capacity(4);
1198 b.extend_from_slice(&4u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b
1201}
1202
1203pub fn build_echo_resp() -> Vec<u8> {
1205 let mut b = Vec::with_capacity(4);
1206 b.extend_from_slice(&4u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b
1209}
1210
1211pub mod tcon_off {
1213 pub const STRUCT: usize = super::BODY;
1215 pub const PATH_OFFSET: usize = super::BODY + 4;
1217 pub const PATH_LENGTH: usize = super::BODY + 6;
1219}
1220
1221pub mod share_type {
1223 pub const DISK: u8 = 0x01;
1225 pub const PIPE: u8 = 0x02;
1227 pub const PRINT: u8 = 0x03;
1229}
1230
1231pub mod caps {
1233 use crate::consts;
1234 pub const DFS: u32 = consts::CAP_DFS;
1236 pub const LARGE_MTU: u32 = consts::CAP_LARGE_MTU;
1238 pub const LEASING: u32 = 0x0000_0008;
1240}
1241
1242#[derive(Debug)]
1244pub struct LockElem {
1245 pub offset: u64,
1247 pub length: u64,
1249 pub shared: bool,
1251 pub exclusive: bool,
1253 pub unlock: bool,
1255 pub fail_immediately: bool,
1257}
1258
1259#[derive(Debug)]
1263pub struct LockReq {
1264 pub file_id: FileId,
1266 pub locks: Vec<LockElem>,
1268}
1269
1270impl LockReq {
1271 pub fn parse(frame: &[u8]) -> Option<LockReq> {
1273 if frame.len() < BODY + 48 || g16(frame, BODY) != 48 {
1274 return None;
1275 }
1276 let count = g16(frame, BODY + 2) as usize;
1277 let fid = FileId(frame.get(BODY + 8..BODY + 24)?.try_into().ok()?);
1278 let mut locks = Vec::with_capacity(count.min(1024));
1279 for i in 0..count {
1280 let base = BODY + 24 + i * 24;
1282 let e = frame.get(base..base + 24)?;
1283 let flags = g32(e, 16);
1286 locks.push(LockElem {
1287 offset: g64(e, 0),
1288 length: g64(e, 8),
1289 shared: flags & 0x01 != 0,
1290 exclusive: flags & 0x02 != 0,
1291 unlock: flags & 0x04 != 0,
1292 fail_immediately: flags & 0x10 != 0,
1293 });
1294 }
1295 Some(LockReq {
1296 file_id: fid,
1297 locks,
1298 })
1299 }
1300}
1301
1302pub fn build_lock_resp() -> Vec<u8> {
1304 let mut b = Vec::with_capacity(4);
1305 b.extend_from_slice(&4u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b
1308}
1309
1310pub mod error {
1313 pub const RESPONSE_STRUCTURE_SIZE: u16 = 9;
1315 pub const SYMLINK_ERROR_TAG: u32 = 0x4C4D_5953;
1317 pub const IO_REPARSE_TAG_SYMLINK: u32 = 0xA000_000C;
1319 pub const SYMLINK_FLAG_RELATIVE: u32 = 0x0000_0001;
1321 pub const REPARSE_HEADER_LEN: u16 = 12;
1324 pub const SYMLINK_FIXED_LEN: u32 = 24;
1328}
1329
1330pub fn build_symlink_error_response(
1337 substitute: &str,
1338 print: &str,
1339 unparsed_path_len: u16,
1340 relative: bool,
1341) -> Vec<u8> {
1342 let sub: Vec<u8> = substitute
1343 .encode_utf16()
1344 .flat_map(|u| u.to_le_bytes())
1345 .collect();
1346 let prt: Vec<u8> = print.encode_utf16().flat_map(|u| u.to_le_bytes()).collect();
1347 let path_len = (sub.len() + prt.len()) as u16;
1348 let flags = if relative {
1349 error::SYMLINK_FLAG_RELATIVE
1350 } else {
1351 0
1352 };
1353
1354 let mut data = Vec::new();
1355 data.extend_from_slice(&(error::SYMLINK_FIXED_LEN + path_len as u32).to_le_bytes()); data.extend_from_slice(&error::SYMLINK_ERROR_TAG.to_le_bytes());
1357 data.extend_from_slice(&error::IO_REPARSE_TAG_SYMLINK.to_le_bytes());
1358 data.extend_from_slice(&(error::REPARSE_HEADER_LEN + path_len).to_le_bytes()); data.extend_from_slice(&unparsed_path_len.to_le_bytes());
1360 data.extend_from_slice(&0u16.to_le_bytes()); data.extend_from_slice(&(sub.len() as u16).to_le_bytes()); data.extend_from_slice(&(sub.len() as u16).to_le_bytes()); data.extend_from_slice(&(prt.len() as u16).to_le_bytes()); data.extend_from_slice(&flags.to_le_bytes());
1365 data.extend_from_slice(&sub);
1366 data.extend_from_slice(&prt);
1367
1368 let mut body = Vec::with_capacity(8 + data.len());
1369 body.extend_from_slice(&error::RESPONSE_STRUCTURE_SIZE.to_le_bytes()); body.push(0); body.push(0); body.extend_from_slice(&(data.len() as u32).to_le_bytes()); body.extend_from_slice(&data);
1374 body
1375}
1376
1377pub mod notify_filter {
1381 pub const FILE_NAME: u32 = 0x0000_0001;
1383 pub const DIR_NAME: u32 = 0x0000_0002;
1385 pub const ATTRIBUTES: u32 = 0x0000_0004;
1387 pub const SIZE: u32 = 0x0000_0008;
1389 pub const LAST_WRITE: u32 = 0x0000_0010;
1391 pub const LAST_ACCESS: u32 = 0x0000_0020;
1393 pub const CREATION: u32 = 0x0000_0040;
1395 pub const EA: u32 = 0x0000_0080;
1397 pub const STREAM_NAME: u32 = 0x0000_0200;
1399 pub const STREAM_SIZE: u32 = 0x0000_0400;
1401 pub const STREAM_WRITE: u32 = 0x0000_0800;
1403}
1404
1405pub mod notify_action {
1407 pub const ADDED: u32 = 0x0000_0001;
1409 pub const REMOVED: u32 = 0x0000_0002;
1411 pub const MODIFIED: u32 = 0x0000_0003;
1413 pub const RENAMED_OLD_NAME: u32 = 0x0000_0004;
1415 pub const RENAMED_NEW_NAME: u32 = 0x0000_0005;
1417}
1418
1419pub const WATCH_TREE: u16 = 0x0001;
1421
1422#[derive(Debug)]
1424pub struct ChangeNotifyReq {
1425 pub file_id: FileId,
1427 pub watch_tree: bool,
1429 pub output_len: u32,
1431 pub filter: u32,
1433}
1434
1435impl ChangeNotifyReq {
1436 pub fn parse(frame: &[u8]) -> Option<ChangeNotifyReq> {
1438 if frame.len() < BODY + 32 || g16(frame, BODY) != 32 {
1439 return None;
1440 }
1441 Some(ChangeNotifyReq {
1442 file_id: FileId(frame.get(BODY + 8..BODY + 24)?.try_into().ok()?),
1443 watch_tree: g16(frame, BODY + 2) & WATCH_TREE != 0,
1444 output_len: g32(frame, BODY + 4),
1445 filter: g32(frame, BODY + 24),
1446 })
1447 }
1448}
1449
1450pub fn build_file_notify_information(entries: &[(u32, &str)]) -> Vec<u8> {
1454 let mut out = Vec::new();
1455 for (i, (action, name)) in entries.iter().enumerate() {
1456 let rec = out.len();
1457 let name16: Vec<u8> = name.encode_utf16().flat_map(|u| u.to_le_bytes()).collect();
1458 out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&action.to_le_bytes());
1460 out.extend_from_slice(&(name16.len() as u32).to_le_bytes());
1461 out.extend_from_slice(&name16);
1462 while out.len() % 4 != 0 {
1463 out.push(0);
1464 }
1465 if i + 1 < entries.len() {
1466 let next = (out.len() - rec) as u32;
1467 out[rec..rec + 4].copy_from_slice(&next.to_le_bytes());
1468 }
1469 }
1470 out
1471}
1472
1473pub fn build_change_notify_resp(buffer: &[u8]) -> Vec<u8> {
1476 let mut b = Vec::with_capacity(8 + buffer.len());
1477 b.extend_from_slice(&9u16.to_le_bytes()); b.extend_from_slice(&72u16.to_le_bytes()); b.extend_from_slice(&(buffer.len() as u32).to_le_bytes());
1480 b.extend_from_slice(buffer);
1481 b
1482}
1483
1484mod ioctl_off {
1488 use super::BODY;
1489 pub const STRUCT: usize = BODY;
1491 pub const CTL_CODE: usize = BODY + 4;
1493 pub const FILE_ID: usize = BODY + 8;
1495 pub const INPUT_OFFSET: usize = BODY + 24;
1497 pub const INPUT_COUNT: usize = BODY + 28;
1499 pub const MAX_OUTPUT: usize = BODY + 44;
1501 pub const FLAGS: usize = BODY + 48;
1503}
1504
1505pub mod fsctl {
1507 pub const DFS_GET_REFERRALS: u32 = 0x0006_0194;
1509 pub const PIPE_WAIT: u32 = 0x0011_0018;
1512 pub const QUERY_NETWORK_INTERFACE_INFO: u32 = 0x0014_01FC;
1515 pub const SRV_REQUEST_RESUME_KEY: u32 = 0x0014_0078;
1517 pub const SRV_COPYCHUNK: u32 = 0x0014_40F2;
1519 pub const SRV_COPYCHUNK_WRITE: u32 = 0x0014_80F2;
1521 pub const OFFLOAD_READ: u32 = 0x0009_4264;
1523 pub const OFFLOAD_WRITE: u32 = 0x0009_8268;
1525 pub const SET_SPARSE: u32 = 0x0009_00C4;
1527 pub const SET_ZERO_DATA: u32 = 0x0009_80C8;
1529 pub const FILE_LEVEL_TRIM: u32 = 0x0009_8208;
1531 pub const GET_INTEGRITY_INFORMATION: u32 = 0x0009_027C;
1533 pub const SET_INTEGRITY_INFORMATION: u32 = 0x0009_C280;
1535 pub const LMR_REQUEST_RESILIENCY: u32 = 0x0014_01D4;
1537 pub const PIPE_TRANSACT: u32 = 0x0011_C017;
1540 pub const VALIDATE_NEGOTIATE_INFO: u32 = 0x0014_0204;
1542}
1543
1544#[derive(Debug)]
1546pub struct IoctlReq {
1547 pub ctl_code: u32,
1549 pub file_id: FileId,
1551 pub input: Vec<u8>,
1553 pub max_output: u32,
1555 #[allow(dead_code)]
1557 pub is_fsctl: bool,
1558}
1559
1560impl IoctlReq {
1561 pub fn parse(frame: &[u8]) -> Option<IoctlReq> {
1563 if frame.len() < ioctl_off::FLAGS + 8 || g16(frame, ioctl_off::STRUCT) != 57 {
1564 return None;
1565 }
1566 let ioff = g32(frame, ioctl_off::INPUT_OFFSET) as usize;
1567 let icnt = g32(frame, ioctl_off::INPUT_COUNT) as usize;
1568 let input = if icnt > 0 && ioff >= BODY {
1569 frame.get(ioff..(ioff + icnt).min(frame.len()))?.to_vec()
1570 } else {
1571 Vec::new()
1572 };
1573 Some(IoctlReq {
1574 ctl_code: g32(frame, ioctl_off::CTL_CODE),
1575 file_id: FileId(
1576 frame
1577 .get(ioctl_off::FILE_ID..ioctl_off::FILE_ID + 16)?
1578 .try_into()
1579 .ok()?,
1580 ),
1581 input,
1582 max_output: g32(frame, ioctl_off::MAX_OUTPUT),
1583 is_fsctl: g32(frame, ioctl_off::FLAGS) & 0x1 == 0,
1584 })
1585 }
1586}
1587
1588pub fn build_ioctl_resp(file_id: FileId, ctl_code: u32, output: &[u8]) -> Vec<u8> {
1591 const FIXED: usize = 48;
1592 let mut b = Vec::with_capacity(FIXED + output.len());
1593 b.extend_from_slice(&49u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&ctl_code.to_le_bytes());
1596 b.extend_from_slice(&file_id.0);
1597 b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); let out_off = BODY + FIXED;
1600 b.extend_from_slice(&(out_off as u32).to_le_bytes()); b.extend_from_slice(&(output.len() as u32).to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); while !(BODY + b.len()).is_multiple_of(8) || BODY + b.len() < out_off {
1605 b.push(0);
1606 }
1607 b.extend_from_slice(output);
1608 debug_assert_eq!(b.len(), FIXED + output.len());
1609 b
1610}
1611
1612pub mod copychunk_limits {
1616 pub const MAX_CHUNKS: u32 = 16;
1618 pub const MAX_CHUNK_SIZE: u32 = 1_048_576;
1620 pub const MAX_TOTAL_SIZE: u32 = 16_777_216;
1622}
1623
1624#[derive(Debug, Clone, Copy)]
1626pub struct CopyChunk {
1627 pub source_offset: u64,
1629 pub target_offset: u64,
1631 pub length: u32,
1633}
1634
1635#[derive(Debug)]
1638pub struct CopyChunkCopy {
1639 pub source_key: [u8; 24],
1641 pub chunks: Vec<CopyChunk>,
1643}
1644
1645impl CopyChunkCopy {
1646 pub fn parse(input: &[u8]) -> Option<CopyChunkCopy> {
1648 if input.len() < 32 {
1649 return None;
1650 }
1651 let source_key: [u8; 24] = input[0..24].try_into().ok()?;
1652 let chunk_count = g32(input, 24) as usize;
1653 let mut chunks = Vec::with_capacity(chunk_count.min(64));
1655 for i in 0..chunk_count {
1656 let base = 32 + i * 24;
1657 let e = input.get(base..base + 24)?;
1658 chunks.push(CopyChunk {
1659 source_offset: g64(e, 0),
1660 target_offset: g64(e, 8),
1661 length: g32(e, 16),
1662 });
1663 }
1664 Some(CopyChunkCopy { source_key, chunks })
1665 }
1666}
1667
1668pub fn build_resume_key_resp(key: &[u8; 24]) -> Vec<u8> {
1671 let mut b = Vec::with_capacity(32);
1672 b.extend_from_slice(key);
1673 b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b
1676}
1677
1678pub fn build_copychunk_resp(
1682 chunks_written: u32,
1683 chunk_bytes_written: u32,
1684 total_bytes_written: u32,
1685) -> Vec<u8> {
1686 let mut b = Vec::with_capacity(12);
1687 b.extend_from_slice(&chunks_written.to_le_bytes());
1688 b.extend_from_slice(&chunk_bytes_written.to_le_bytes());
1689 b.extend_from_slice(&total_bytes_written.to_le_bytes());
1690 b
1691}
1692
1693pub fn parse_zero_data(input: &[u8]) -> Option<(u64, u64)> {
1696 if input.len() < 16 {
1697 return None;
1698 }
1699 Some((g64(input, 0), g64(input, 8)))
1700}
1701
1702pub mod offload {
1707 pub const TOKEN_LEN: usize = 512;
1709 pub const TOKEN_ID_OFFSET: usize = 8;
1711 pub const TOKEN_TYPE_DATA: u32 = 0x0000_0001;
1714 pub const READ_INPUT_SIZE: u32 = 32;
1716 pub const READ_OUTPUT_SIZE: u32 = 16 + TOKEN_LEN as u32;
1718 pub const WRITE_OUTPUT_SIZE: u32 = 16;
1720}
1721
1722pub fn parse_offload_read(input: &[u8]) -> Option<(u64, u64)> {
1725 if input.len() < offload::READ_INPUT_SIZE as usize {
1726 return None;
1727 }
1728 Some((g64(input, 16), g64(input, 24)))
1729}
1730
1731pub fn build_offload_read_resp(transfer_length: u64, token_id: &[u8; 16]) -> Vec<u8> {
1734 let mut b = Vec::with_capacity(offload::READ_OUTPUT_SIZE as usize);
1735 b.extend_from_slice(&offload::READ_OUTPUT_SIZE.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&transfer_length.to_le_bytes()); b.extend_from_slice(&offload::TOKEN_TYPE_DATA.to_be_bytes());
1740 b.extend_from_slice(&0u16.to_be_bytes()); b.extend_from_slice(&(token_id.len() as u16).to_be_bytes()); b.extend_from_slice(token_id);
1743 b.resize(offload::READ_OUTPUT_SIZE as usize, 0); b
1745}
1746
1747pub fn parse_offload_write(input: &[u8]) -> Option<(u64, u64, u64, [u8; 16])> {
1751 let header = 4 + 4 + 8 + 8 + 8; if input.len() < header + offload::TOKEN_LEN {
1753 return None;
1754 }
1755 let file_offset = g64(input, 8);
1756 let copy_length = g64(input, 16);
1757 let transfer_offset = g64(input, 24);
1758 let id_at = header + offload::TOKEN_ID_OFFSET;
1759 let token_id: [u8; 16] = input[id_at..id_at + 16].try_into().ok()?;
1760 Some((file_offset, copy_length, transfer_offset, token_id))
1761}
1762
1763pub fn build_offload_write_resp(length_written: u64) -> Vec<u8> {
1766 let mut b = Vec::with_capacity(offload::WRITE_OUTPUT_SIZE as usize);
1767 b.extend_from_slice(&offload::WRITE_OUTPUT_SIZE.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&length_written.to_le_bytes()); b
1771}
1772
1773pub fn parse_file_level_trim(input: &[u8]) -> Option<(u32, u32)> {
1776 if input.len() < 8 {
1777 return None;
1778 }
1779 Some((g32(input, 0), g32(input, 4)))
1780}
1781
1782pub fn build_file_level_trim_resp(num_ranges_processed: u32) -> Vec<u8> {
1785 num_ranges_processed.to_le_bytes().to_vec()
1786}
1787
1788pub fn parse_set_integrity(input: &[u8]) -> Option<u16> {
1791 (input.len() >= 8).then(|| g16(input, 0))
1792}
1793
1794pub fn build_get_integrity_resp(algorithm: u16, flags: u32) -> Vec<u8> {
1798 const CHUNK_SIZE: u32 = 512;
1799 const CLUSTER_SIZE: u32 = 4096;
1800 let mut b = Vec::with_capacity(16);
1801 b.extend_from_slice(&algorithm.to_le_bytes());
1802 b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&flags.to_le_bytes());
1804 b.extend_from_slice(&CHUNK_SIZE.to_le_bytes());
1805 b.extend_from_slice(&CLUSTER_SIZE.to_le_bytes());
1806 b
1807}
1808
1809pub mod tf_off {
1813 pub const PROTOCOL_ID: usize = 0;
1815 pub const SIGNATURE: usize = 4;
1817 pub const NONCE: usize = 20;
1819 pub const MSG_SIZE: usize = 36;
1821 pub const RESERVED: usize = 40;
1823 pub const FLAGS: usize = 42;
1825 pub const SESSION_ID: usize = 44;
1827 pub const HDR_SIZE: usize = 52;
1829}
1830
1831pub const TF_MAGIC: [u8; 4] = [0xFD, b'S', b'M', b'B'];
1833pub const TF_FLAGS_ENCRYPTED: u16 = 0x0001;
1835
1836#[derive(Debug)]
1838pub struct TransformHdr {
1839 pub original_len: usize,
1841 pub session_id: u64,
1843 pub flags: u16,
1845}
1846
1847impl TransformHdr {
1848 pub fn parse(frame: &[u8]) -> Option<TransformHdr> {
1850 if frame.len() < tf_off::HDR_SIZE || frame[..4] != TF_MAGIC {
1851 return None;
1852 }
1853 Some(TransformHdr {
1854 original_len: g32(frame, tf_off::MSG_SIZE) as usize,
1855 session_id: g64(frame, tf_off::SESSION_ID),
1856 flags: g16(frame, tf_off::FLAGS),
1857 })
1858 }
1859
1860 pub fn aad<'a>(&self, frame: &'a [u8]) -> &'a [u8] {
1864 &frame[tf_off::NONCE..tf_off::HDR_SIZE]
1865 }
1866}
1867
1868#[allow(clippy::too_many_arguments)]
1871pub fn build_transform(session_id: u64, nonce: &[u8; 16], original_len: usize) -> Vec<u8> {
1872 let mut t = Vec::with_capacity(tf_off::HDR_SIZE);
1873 t.extend_from_slice(&TF_MAGIC);
1874 t.extend_from_slice(&[0u8; 16]); t.extend_from_slice(nonce);
1876 t.extend_from_slice(&(original_len as u32).to_le_bytes());
1877 t.extend_from_slice(&0u16.to_le_bytes()); t.extend_from_slice(&TF_FLAGS_ENCRYPTED.to_le_bytes()); t.extend_from_slice(&session_id.to_le_bytes());
1880 debug_assert_eq!(t.len(), tf_off::HDR_SIZE);
1881 t
1882}
1883
1884#[cfg(test)]
1885mod change_notify_tests {
1886 use super::*;
1887
1888 fn change_notify_request(file_id: [u8; 16], watch_tree: bool, filter: u32) -> Vec<u8> {
1889 let mut f = vec![0u8; BODY + 32];
1890 f[BODY..BODY + 2].copy_from_slice(&32u16.to_le_bytes()); let flags: u16 = if watch_tree { WATCH_TREE } else { 0 };
1892 f[BODY + 2..BODY + 4].copy_from_slice(&flags.to_le_bytes());
1893 f[BODY + 4..BODY + 8].copy_from_slice(&65536u32.to_le_bytes()); f[BODY + 8..BODY + 24].copy_from_slice(&file_id);
1895 f[BODY + 24..BODY + 28].copy_from_slice(&filter.to_le_bytes());
1896 f
1897 }
1898
1899 #[test]
1900 fn parses_change_notify_request() {
1901 let fid = [7u8; 16];
1902 let frame = change_notify_request(
1903 fid,
1904 true,
1905 notify_filter::FILE_NAME | notify_filter::LAST_WRITE,
1906 );
1907 let req = ChangeNotifyReq::parse(&frame).expect("parse");
1908 assert_eq!(req.file_id.0, fid);
1909 assert!(req.watch_tree);
1910 assert_eq!(req.output_len, 65536);
1911 assert_eq!(
1912 req.filter,
1913 notify_filter::FILE_NAME | notify_filter::LAST_WRITE
1914 );
1915 }
1916
1917 #[test]
1918 fn rejects_wrong_structure_size() {
1919 let mut frame = change_notify_request([0u8; 16], false, 0);
1920 frame[BODY..BODY + 2].copy_from_slice(&31u16.to_le_bytes());
1921 assert!(ChangeNotifyReq::parse(&frame).is_none());
1922 }
1923
1924 #[test]
1925 fn single_notify_record_is_self_terminating() {
1926 let buf = build_file_notify_information(&[(notify_action::ADDED, "new.txt")]);
1927 assert_eq!(&buf[0..4], &0u32.to_le_bytes());
1929 assert_eq!(&buf[4..8], ¬ify_action::ADDED.to_le_bytes());
1930 assert_eq!(&buf[8..12], &14u32.to_le_bytes());
1931 assert_eq!(
1932 &buf[12..26],
1933 &"new.txt"
1934 .encode_utf16()
1935 .flat_map(|u| u.to_le_bytes())
1936 .collect::<Vec<_>>()[..]
1937 );
1938 assert_eq!(buf.len() % 4, 0);
1939 }
1940
1941 #[test]
1942 fn chained_notify_records_link_via_next_offset() {
1943 let buf = build_file_notify_information(&[
1944 (notify_action::ADDED, "a.txt"),
1945 (notify_action::REMOVED, "b.txt"),
1946 ]);
1947 let next = u32::from_le_bytes(buf[0..4].try_into().unwrap()) as usize;
1948 assert_ne!(next, 0, "first record must point to the second");
1949 assert_eq!(next % 4, 0, "records are 4-byte aligned");
1950 assert_eq!(
1951 &buf[next..next + 4],
1952 &0u32.to_le_bytes(),
1953 "second record terminates"
1954 );
1955 assert_eq!(
1956 &buf[next + 4..next + 8],
1957 ¬ify_action::REMOVED.to_le_bytes()
1958 );
1959 }
1960
1961 #[test]
1962 fn change_notify_response_header_offsets() {
1963 let notify = build_file_notify_information(&[(notify_action::MODIFIED, "x")]);
1964 let body = build_change_notify_resp(¬ify);
1965 assert_eq!(&body[0..2], &9u16.to_le_bytes(), "StructureSize");
1966 assert_eq!(&body[2..4], &72u16.to_le_bytes(), "OutputBufferOffset");
1967 assert_eq!(
1968 &body[4..8],
1969 &(notify.len() as u32).to_le_bytes(),
1970 "OutputBufferLength"
1971 );
1972 assert_eq!(&body[8..], ¬ify[..]);
1973 }
1974
1975 #[test]
1979 fn fixed_four_byte_responses_carry_reserved() {
1980 for (name, body) in [
1981 ("tree_disconnect", build_tree_disconnect_resp()),
1982 ("logoff", build_logoff_resp()),
1983 ("echo", build_echo_resp()),
1984 ("flush", build_flush_resp()),
1985 ("lock", build_lock_resp()),
1986 ] {
1987 assert_eq!(body.len(), 4, "{name} response must be 4 bytes");
1988 assert_eq!(&body[0..2], &4u16.to_le_bytes(), "{name} StructureSize=4");
1989 assert_eq!(&body[2..4], &0u16.to_le_bytes(), "{name} Reserved=0");
1990 }
1991 }
1992}
1993
1994#[cfg(test)]
1995mod fsctl_tests {
1996 use super::*;
1997
1998 #[test]
1999 fn parses_copychunk_copy() {
2000 let key = [0xABu8; 24];
2001 let mut input = Vec::new();
2002 input.extend_from_slice(&key);
2003 input.extend_from_slice(&2u32.to_le_bytes()); input.extend_from_slice(&0u32.to_le_bytes()); for (so, to, len) in [(0u64, 100u64, 10u32), (10, 110, 20)] {
2006 input.extend_from_slice(&so.to_le_bytes());
2007 input.extend_from_slice(&to.to_le_bytes());
2008 input.extend_from_slice(&len.to_le_bytes());
2009 input.extend_from_slice(&0u32.to_le_bytes()); }
2011 let cc = CopyChunkCopy::parse(&input).expect("parse");
2012 assert_eq!(cc.source_key, key);
2013 assert_eq!(cc.chunks.len(), 2);
2014 assert_eq!(cc.chunks[1].source_offset, 10);
2015 assert_eq!(cc.chunks[1].target_offset, 110);
2016 assert_eq!(cc.chunks[1].length, 20);
2017 }
2018
2019 #[test]
2020 fn copychunk_response_fields() {
2021 let r = build_copychunk_resp(3, 0, 999);
2022 assert_eq!(&r[0..4], &3u32.to_le_bytes(), "ChunksWritten");
2023 assert_eq!(&r[4..8], &0u32.to_le_bytes(), "ChunkBytesWritten");
2024 assert_eq!(&r[8..12], &999u32.to_le_bytes(), "TotalBytesWritten");
2025 }
2026
2027 #[test]
2028 fn resume_key_response_carries_key() {
2029 let key = [7u8; 24];
2030 let r = build_resume_key_resp(&key);
2031 assert_eq!(&r[0..24], &key);
2032 assert_eq!(&r[24..28], &0u32.to_le_bytes(), "ContextLength=0");
2033 }
2034
2035 #[test]
2036 fn parses_zero_data_range() {
2037 let mut input = Vec::new();
2038 input.extend_from_slice(&4096u64.to_le_bytes());
2039 input.extend_from_slice(&8192u64.to_le_bytes());
2040 assert_eq!(parse_zero_data(&input), Some((4096, 8192)));
2041 assert!(parse_zero_data(&input[..8]).is_none());
2042 }
2043}
2044
2045#[cfg(test)]
2046mod lock_tests {
2047 use super::*;
2048
2049 fn lock_request(file_id: [u8; 16], elems: &[(u64, u64, u32)]) -> Vec<u8> {
2050 let mut f = vec![0u8; BODY];
2051 f.extend_from_slice(&48u16.to_le_bytes()); f.extend_from_slice(&(elems.len() as u16).to_le_bytes()); f.extend_from_slice(&0u32.to_le_bytes()); f.extend_from_slice(&file_id); for &(off, len, flags) in elems {
2056 f.extend_from_slice(&off.to_le_bytes());
2057 f.extend_from_slice(&len.to_le_bytes());
2058 f.extend_from_slice(&flags.to_le_bytes());
2059 f.extend_from_slice(&0u32.to_le_bytes()); }
2061 f
2062 }
2063
2064 #[test]
2065 fn parses_lock_elements_at_correct_offset() {
2066 let fid = [9u8; 16];
2068 let frame = lock_request(fid, &[(0, 10, 0x02 | 0x10)]); let req = LockReq::parse(&frame).expect("parse");
2070 assert_eq!(req.file_id.0, fid);
2071 assert_eq!(req.locks.len(), 1);
2072 assert_eq!(req.locks[0].offset, 0);
2073 assert_eq!(req.locks[0].length, 10);
2074 assert!(req.locks[0].exclusive);
2075 assert!(req.locks[0].fail_immediately);
2076 assert!(!req.locks[0].unlock);
2077 }
2078
2079 #[test]
2080 fn parses_unlock_and_shared_flags() {
2081 let frame = lock_request([0u8; 16], &[(5, 5, 0x01), (5, 5, 0x04)]); let req = LockReq::parse(&frame).expect("parse");
2083 assert_eq!(req.locks.len(), 2);
2084 assert!(req.locks[0].shared && !req.locks[0].unlock);
2085 assert!(req.locks[1].unlock && !req.locks[1].shared);
2086 }
2087}
2088
2089#[cfg(test)]
2090mod oplock_codec_tests {
2091 use super::*;
2092
2093 #[test]
2094 fn create_response_carries_oplock_level() {
2095 let body = build_create_resp(
2096 FileId([1; 16]),
2097 1,
2098 [0; 4],
2099 0,
2100 0,
2101 0,
2102 false,
2103 oplock::EXCLUSIVE,
2104 &[],
2105 );
2106 assert_eq!(body[2], oplock::EXCLUSIVE, "OplockLevel @2");
2107 assert_eq!(&body[80..84], &0u32.to_le_bytes(), "no contexts");
2108 }
2109
2110 #[test]
2111 fn oplock_break_notification_shape() {
2112 let brk = build_oplock_break(FileId([9; 16]), oplock::NONE);
2113 assert_eq!(brk.len(), 24);
2114 assert_eq!(&brk[0..2], &24u16.to_le_bytes(), "StructureSize");
2115 assert_eq!(brk[2], oplock::NONE, "OplockLevel");
2116 assert_eq!(&brk[8..24], &[9u8; 16], "FileId");
2117 }
2118
2119 #[test]
2120 fn parses_oplock_break_ack() {
2121 let mut frame = vec![0u8; BODY];
2122 frame.extend_from_slice(&build_oplock_break(FileId([7; 16]), oplock::LEVEL_II));
2123 let ack = OplockBreakAck::parse(&frame).expect("parse");
2124 assert_eq!(ack.level, oplock::LEVEL_II);
2125 assert_eq!(ack.file_id.0, [7; 16]);
2126 }
2127}
2128
2129#[cfg(test)]
2130mod symlink_error_tests {
2131 use super::*;
2132
2133 #[test]
2134 fn symlink_error_response_matches_spec_layout() {
2135 let body = build_symlink_error_response("t", "t", 8, true);
2137 assert_eq!(
2138 u16::from_le_bytes([body[0], body[1]]),
2139 error::RESPONSE_STRUCTURE_SIZE
2140 );
2141 assert_eq!(body[2], 0, "ErrorContextCount");
2142 let byte_count = u32::from_le_bytes(body[4..8].try_into().unwrap()) as usize;
2143 let data = &body[8..];
2144 assert_eq!(byte_count, data.len());
2145
2146 let name_bytes = 2u16; assert_eq!(
2148 u32::from_le_bytes(data[0..4].try_into().unwrap()),
2149 error::SYMLINK_FIXED_LEN + (2 * name_bytes) as u32,
2150 "SymLinkLength"
2151 );
2152 assert_eq!(
2153 u32::from_le_bytes(data[4..8].try_into().unwrap()),
2154 error::SYMLINK_ERROR_TAG
2155 );
2156 assert_eq!(
2157 u32::from_le_bytes(data[8..12].try_into().unwrap()),
2158 error::IO_REPARSE_TAG_SYMLINK
2159 );
2160 assert_eq!(
2161 u16::from_le_bytes(data[12..14].try_into().unwrap()),
2162 error::REPARSE_HEADER_LEN + 2 * name_bytes,
2163 "ReparseDataLength"
2164 );
2165 assert_eq!(
2166 u16::from_le_bytes(data[14..16].try_into().unwrap()),
2167 8,
2168 "UnparsedPathLength"
2169 );
2170 assert_eq!(
2171 u16::from_le_bytes(data[16..18].try_into().unwrap()),
2172 0,
2173 "SubstituteNameOffset"
2174 );
2175 assert_eq!(
2176 u16::from_le_bytes(data[18..20].try_into().unwrap()),
2177 name_bytes,
2178 "SubstituteNameLength"
2179 );
2180 assert_eq!(
2181 u16::from_le_bytes(data[20..22].try_into().unwrap()),
2182 name_bytes,
2183 "PrintNameOffset"
2184 );
2185 assert_eq!(
2186 u16::from_le_bytes(data[22..24].try_into().unwrap()),
2187 name_bytes,
2188 "PrintNameLength"
2189 );
2190 assert_eq!(
2191 u32::from_le_bytes(data[24..28].try_into().unwrap()),
2192 error::SYMLINK_FLAG_RELATIVE
2193 );
2194 }
2195
2196 #[test]
2197 fn absolute_symlink_clears_relative_flag() {
2198 let body = build_symlink_error_response("/abs", "/abs", 0, false);
2199 assert_eq!(u32::from_le_bytes(body[8..][24..28].try_into().unwrap()), 0);
2200 }
2201}
2202
2203#[cfg(test)]
2204mod lease_codec_tests {
2205 use super::*;
2206
2207 fn create_with_lease(key: [u8; 16], state: u32, v2: bool) -> Vec<u8> {
2210 let data_len = if v2 { 52usize } else { 32 };
2211 let mut ctx = Vec::new();
2212 ctx.extend_from_slice(&0u32.to_le_bytes()); ctx.extend_from_slice(&16u16.to_le_bytes()); ctx.extend_from_slice(&4u16.to_le_bytes()); ctx.extend_from_slice(&0u16.to_le_bytes()); ctx.extend_from_slice(&24u16.to_le_bytes()); ctx.extend_from_slice(&(data_len as u32).to_le_bytes()); ctx.extend_from_slice(lease::CONTEXT_NAME);
2219 ctx.extend_from_slice(&[0u8; 4]); ctx.extend_from_slice(&key);
2221 ctx.extend_from_slice(&state.to_le_bytes());
2222 ctx.extend_from_slice(&0u32.to_le_bytes()); ctx.extend_from_slice(&0u64.to_le_bytes()); if v2 {
2225 ctx.extend_from_slice(&[0u8; 16]); ctx.extend_from_slice(&7u16.to_le_bytes()); ctx.extend_from_slice(&0u16.to_le_bytes()); }
2229
2230 let mut f = vec![0u8; BODY];
2231 let mut body = vec![0u8; 56];
2232 body[0..2].copy_from_slice(&57u16.to_le_bytes()); body[3] = oplock::LEASE; let ctx_off = BODY + 56;
2235 body[48..52].copy_from_slice(&(ctx_off as u32).to_le_bytes()); body[52..56].copy_from_slice(&(ctx.len() as u32).to_le_bytes()); f.extend_from_slice(&body);
2238 f.extend_from_slice(&ctx);
2239 f
2240 }
2241
2242 #[test]
2243 fn parses_lease_v1_context() {
2244 let frame = create_with_lease([0x11; 16], lease::RWH, false);
2245 let req = CreateReq::parse(&frame).expect("parse");
2246 let l = req.lease.expect("lease context present");
2247 assert_eq!(l.key, [0x11; 16]);
2248 assert_eq!(l.state, lease::RWH);
2249 assert!(!l.v2);
2250 }
2251
2252 #[test]
2253 fn parses_lease_v2_context() {
2254 let frame = create_with_lease([0x22; 16], lease::RH, true);
2255 let l = CreateReq::parse(&frame).unwrap().lease.expect("lease");
2256 assert!(l.v2);
2257 assert_eq!(l.epoch, 7);
2258 assert_eq!(l.state, lease::RH);
2259 }
2260
2261 #[test]
2262 fn create_response_appends_lease_context() {
2263 let grant = LeaseResp {
2264 key: [0x33; 16],
2265 state: lease::RH,
2266 flags: 0,
2267 epoch: 1,
2268 v2: true,
2269 };
2270 let ctx = encode_create_contexts(&[(lease::CONTEXT_NAME, lease_context_data(&grant))]);
2271 let body = build_create_resp(
2272 FileId([1; 16]),
2273 1,
2274 [0; 4],
2275 0,
2276 0,
2277 0,
2278 false,
2279 oplock::LEASE,
2280 &ctx,
2281 );
2282 assert_eq!(body[2], oplock::LEASE, "OplockLevel = lease");
2283 let off = u32::from_le_bytes(body[80..84].try_into().unwrap()) as usize;
2284 let len = u32::from_le_bytes(body[84..88].try_into().unwrap()) as usize;
2285 assert_eq!(off, BODY + 88, "context offset header-relative");
2286 assert_eq!(len, 24 + 52, "v2 context length");
2287 assert_eq!(&body[88 + 16..88 + 20], lease::CONTEXT_NAME);
2289 }
2290
2291 #[test]
2292 fn parses_durable_v2_request_and_reconnect() {
2293 let mut data = Vec::new();
2295 data.extend_from_slice(&5000u32.to_le_bytes()); data.extend_from_slice(&durable::FLAG_PERSISTENT.to_le_bytes()); data.extend_from_slice(&[0u8; 8]); data.extend_from_slice(&[0xAB; 16]); let ctx = encode_create_contexts(&[(durable::REQ_V2, data)]);
2300 let mut frame = vec![0u8; BODY];
2301 let mut body = vec![0u8; 56];
2302 body[0..2].copy_from_slice(&57u16.to_le_bytes());
2303 let ctx_off = BODY + 56;
2304 body[48..52].copy_from_slice(&(ctx_off as u32).to_le_bytes());
2305 body[52..56].copy_from_slice(&(ctx.len() as u32).to_le_bytes());
2306 frame.extend_from_slice(&body);
2307 frame.extend_from_slice(&ctx);
2308 match CreateReq::parse(&frame).unwrap().durable.expect("durable") {
2309 DurableReq::RequestV2 {
2310 timeout,
2311 flags,
2312 create_guid,
2313 } => {
2314 assert_eq!(timeout, 5000);
2315 assert_eq!(flags, durable::FLAG_PERSISTENT);
2316 assert_eq!(create_guid, [0xAB; 16]);
2317 }
2318 other => panic!("expected RequestV2, got {other:?}"),
2319 }
2320 }
2321
2322 #[test]
2323 fn lease_break_notification_shape() {
2324 let brk = build_lease_break(
2325 [9; 16],
2326 lease::RWH,
2327 lease::RH,
2328 2,
2329 lease::BREAK_FLAG_ACK_REQUIRED,
2330 );
2331 assert_eq!(brk.len(), 44);
2332 assert_eq!(&brk[0..2], &44u16.to_le_bytes(), "StructureSize");
2333 assert_eq!(&brk[8..24], &[9u8; 16], "LeaseKey");
2334 assert_eq!(
2335 u32::from_le_bytes(brk[24..28].try_into().unwrap()),
2336 lease::RWH,
2337 "Current"
2338 );
2339 assert_eq!(
2340 u32::from_le_bytes(brk[28..32].try_into().unwrap()),
2341 lease::RH,
2342 "New"
2343 );
2344 }
2345
2346 #[test]
2347 fn parses_lease_break_ack() {
2348 let mut frame = vec![0u8; BODY];
2349 frame.extend_from_slice(&build_lease_break_resp([7; 16], lease::RH));
2350 let ack = LeaseBreakAck::parse(&frame).expect("parse");
2351 assert_eq!(ack.key, [7; 16]);
2352 assert_eq!(ack.state, lease::RH);
2353 }
2354}
2355
2356#[cfg(test)]
2357mod query_dir_tests {
2358 use super::*;
2359
2360 #[test]
2361 fn parses_resume_file_index() {
2362 let mut f = vec![0u8; 64];
2363 let mut body = vec![0u8; 32];
2364 body[0..2].copy_from_slice(&33u16.to_le_bytes()); body[3] = find_flags::INDEX_SPECIFIED; body[4..8].copy_from_slice(&7u32.to_le_bytes()); f.extend_from_slice(&body);
2368 let req = QueryDirReq::parse(&f).expect("parse");
2369 assert_eq!(req.file_index, 7, "resume index parsed");
2370 assert_eq!(
2371 req.flags & find_flags::INDEX_SPECIFIED,
2372 find_flags::INDEX_SPECIFIED
2373 );
2374 }
2375}