1use crate::slice_codec::{self, CodecError, LockType};
2use crate::wire_types::*;
3use alloc::vec::Vec;
4use bytes::{Buf, Bytes};
5#[path = "owned_codec.rs"]
6mod owned_codec;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum FallocateKind {
11 Allocate,
12 PunchHole,
13 ZeroRange { keep_size: bool },
14}
15
16pub fn classify_fallocate_mode(mode: u32) -> Option<FallocateKind> {
18 match mode {
19 0 => Some(FallocateKind::Allocate),
20 mode if mode == (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE) => {
21 Some(FallocateKind::PunchHole)
22 }
23 FALLOC_FL_ZERO_RANGE => Some(FallocateKind::ZeroRange { keep_size: false }),
24 mode if mode == (FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE) => {
25 Some(FallocateKind::ZeroRange { keep_size: true })
26 }
27 _ => None,
28 }
29}
30
31pub const P9_LOCK_FLAGS_BLOCK: u32 = 1; pub const P9_CHANNEL_SIZE: usize = 1000;
34pub const P9_DEBUG_BUFFER_SIZE: usize = 40;
35pub const P9_READDIR_BATCH_SIZE: usize = 1000;
36pub const P9_NOBODY_UID: u32 = 65534;
37
38pub type P9String = WireString<Bytes>;
40pub type P9Bytes = WireBytes<Bytes>;
41
42impl WireString<Bytes> {
43 pub fn new(data: impl Into<Bytes>) -> Self {
44 Self::from_storage(data.into())
45 }
46}
47
48impl From<Vec<u8>> for WireBytes<Bytes> {
49 fn from(value: Vec<u8>) -> Self {
50 Self(Bytes::from(value))
51 }
52}
53
54impl From<WireBytes<Bytes>> for Bytes {
55 fn from(value: WireBytes<Bytes>) -> Self {
56 value.0
57 }
58}
59
60#[derive(Debug, Clone)]
61pub struct DirEntry {
62 pub qid: Qid,
63 pub offset: u64,
64 pub type_: u8,
65 pub name: P9String,
66}
67
68impl DirEntry {
69 pub fn wire_size(&self) -> usize {
71 Qid::WIRE_SIZE + 8 + 1 + self.name.wire_size()
72 }
73}
74
75#[derive(Debug, Clone)]
76pub struct Tversion {
77 pub msize: u32,
78 pub version: P9String,
79}
80
81#[derive(Debug, Clone)]
82pub struct Tattach {
83 pub fid: u32,
84 pub afid: u32,
85 pub uname: P9String,
86 pub aname: P9String,
87 pub n_uname: u32,
88}
89
90#[derive(Debug, Clone)]
91pub struct Twalk {
92 pub fid: u32,
93 pub newfid: u32,
94 pub nwname: u16,
95 pub wnames: Vec<P9String>,
96}
97
98#[derive(Debug, Clone)]
99pub struct Tlopen {
100 pub fid: u32,
101 pub flags: u32,
102}
103
104#[derive(Debug, Clone)]
105pub struct Tlcreate {
106 pub fid: u32,
107 pub name: P9String,
108 pub flags: u32,
109 pub mode: u32,
110 pub gid: u32,
111}
112
113#[derive(Debug, Clone)]
114pub struct Tread {
115 pub fid: u32,
116 pub offset: u64,
117 pub count: u32,
118}
119
120#[derive(Debug, Clone)]
121pub struct Twrite {
122 pub fid: u32,
123 pub offset: u64,
124 pub count: u32,
125 pub data: P9Bytes,
126}
127
128#[derive(Debug, Clone)]
129pub struct Tclunk {
130 pub fid: u32,
131}
132
133#[derive(Debug, Clone)]
134pub struct Treaddir {
135 pub fid: u32,
136 pub offset: u64,
137 pub count: u32,
138}
139
140#[derive(Debug, Clone)]
141pub struct Tgetattr {
142 pub fid: u32,
143 pub request_mask: u64,
144}
145
146#[derive(Debug, Clone)]
147pub struct Tsetattr {
148 pub fid: u32,
149 pub valid: u32,
150 pub mode: u32,
151 pub uid: u32,
152 pub gid: u32,
153 pub size: u64,
154 pub atime_sec: u64,
155 pub atime_nsec: u64,
156 pub mtime_sec: u64,
157 pub mtime_nsec: u64,
158}
159
160#[derive(Debug, Clone)]
162pub struct Tfallocate {
163 pub fid: u32,
164 pub offset: u64,
165 pub length: u64,
166 pub mode: u32,
167}
168
169#[derive(Debug, Clone)]
170pub struct Rfallocate;
171
172#[derive(Debug, Clone)]
173pub struct Tmkdir {
174 pub dfid: u32,
175 pub name: P9String,
176 pub mode: u32,
177 pub gid: u32,
178}
179
180#[derive(Debug, Clone)]
181pub struct Tsymlink {
182 pub dfid: u32,
183 pub name: P9String,
184 pub symtgt: P9String,
185 pub gid: u32,
186}
187
188#[derive(Debug, Clone)]
189pub struct Tmknod {
190 pub dfid: u32,
191 pub name: P9String,
192 pub mode: u32,
193 pub major: u32,
194 pub minor: u32,
195 pub gid: u32,
196}
197
198#[derive(Debug, Clone)]
199pub struct Tlink {
200 pub dfid: u32,
201 pub fid: u32,
202 pub name: P9String,
203}
204
205#[derive(Debug, Clone)]
206pub struct Trename {
207 pub fid: u32,
208 pub dfid: u32,
209 pub name: P9String,
210}
211
212#[derive(Debug, Clone)]
213pub struct Trenameat {
214 pub olddirfid: u32,
215 pub oldname: P9String,
216 pub newdirfid: u32,
217 pub newname: P9String,
218}
219
220#[derive(Debug, Clone)]
221pub struct Tunlinkat {
222 pub dirfid: u32,
223 pub name: P9String,
224 pub flags: u32,
225}
226
227#[derive(Debug, Clone)]
228pub struct Tfsync {
229 pub fid: u32,
230 pub datasync: u32,
231}
232
233#[derive(Debug, Clone)]
234pub struct Treadlink {
235 pub fid: u32,
236}
237
238#[derive(Debug, Clone)]
239pub struct Tstatfs {
240 pub fid: u32,
241}
242
243#[derive(Debug, Clone)]
245pub struct Tflush {
246 pub oldtag: u16,
247}
248
249#[derive(Debug, Clone)]
250pub struct Rflush;
251
252#[derive(Debug, Clone)]
254pub struct Txattrwalk {
255 pub fid: u32,
256 pub newfid: u32,
257 pub name: P9String,
258}
259
260#[derive(Debug, Clone)]
261pub struct Tlock {
262 pub fid: u32,
263 pub lock_type: LockType,
264 pub flags: u32,
265 pub start: u64,
266 pub length: u64,
267 pub proc_id: u32,
268 pub client_id: P9String,
269}
270
271#[derive(Debug, Clone)]
272pub struct Tgetlock {
273 pub fid: u32,
274 pub lock_type: LockType,
275 pub start: u64,
276 pub length: u64,
277 pub proc_id: u32,
278 pub client_id: P9String,
279}
280
281#[derive(Debug, Clone)]
282pub struct Rxattrwalk {
283 pub size: u64,
284}
285
286#[derive(Debug, Clone)]
287pub struct Rgetlock {
288 pub lock_type: LockType,
289 pub start: u64,
290 pub length: u64,
291 pub proc_id: u32,
292 pub client_id: P9String,
293}
294
295#[derive(Debug, Clone)]
297pub struct Rversion {
298 pub msize: u32,
299 pub version: P9String,
300}
301
302#[derive(Debug, Clone)]
303pub struct Rattach {
304 pub qid: Qid,
305}
306
307#[derive(Debug, Clone)]
310pub struct Trebind {
311 pub fid: u32,
312 pub inode_id: u64,
313 pub root_inode: u64,
315 pub flags: u8,
318 pub uname: P9String,
322 pub n_uname: u32,
324}
325
326#[derive(Debug, Clone)]
328pub struct Twalkgetattr {
329 pub fid: u32,
330 pub newfid: u32,
331 pub nwname: u16,
332 pub wnames: Vec<P9String>,
333}
334
335#[derive(Debug, Clone)]
337pub struct Rwalkgetattr {
338 pub nwqid: u16,
339 pub wqids: Vec<Qid>,
340 pub stat: Stat,
341}
342
343#[derive(Debug, Clone)]
344pub struct Treaddirattr {
345 pub fid: u32,
346 pub offset: u64,
347 pub count: u32,
348}
349
350#[derive(Debug, Clone)]
351pub struct DirEntryPlus {
352 pub qid: Qid,
353 pub offset: u64,
354 pub type_: u8,
355 pub name: P9String,
356 pub stat: Stat,
357}
358
359impl DirEntryPlus {
360 pub fn wire_size(&self) -> usize {
362 Qid::WIRE_SIZE + 8 + 1 + self.name.wire_size() + Stat::WIRE_SIZE
363 }
364}
365
366#[derive(Debug, Clone)]
367pub struct Rreaddirattr {
368 pub count: u32,
369 pub data: P9Bytes,
370}
371
372#[derive(Debug, Clone)]
376pub struct Tlopenat {
377 pub fid: u32,
378 pub newfid: u32,
379 pub flags: u32,
380}
381
382#[derive(Debug, Clone)]
383pub struct Rlopenat {
384 pub qid: Qid,
385 pub iounit: u32,
386}
387
388#[derive(Debug, Clone)]
390pub struct Tlopenatread {
391 pub fid: u32,
392 pub newfid: u32,
393 pub flags: u32,
394 pub count: u32,
396}
397
398#[derive(Debug, Clone)]
399pub struct Rlopenatread {
400 pub qid: Qid,
401 pub iounit: u32,
402 pub eof: u8,
404 pub count: u32,
405 pub data: P9Bytes,
406}
407
408#[derive(Debug, Clone)]
409pub struct Tlcreateattr {
410 pub dfid: u32,
411 pub newfid: u32,
412 pub name: P9String,
413 pub flags: u32,
414 pub mode: u32,
415 pub gid: u32,
416}
417
418#[derive(Debug, Clone)]
419pub struct Rmkdirattr {
420 pub stat: Stat,
421}
422
423#[derive(Debug, Clone)]
424pub struct Rsymlinkattr {
425 pub stat: Stat,
426}
427
428#[derive(Debug, Clone)]
429pub struct Rmknodattr {
430 pub stat: Stat,
431}
432
433#[derive(Debug, Clone)]
434pub struct Rlinkattr {
435 pub stat: Stat,
436}
437
438#[derive(Debug, Clone)]
439pub struct Rsetattrattr {
440 pub stat: Stat,
441}
442
443impl Rreaddirattr {
444 pub fn from_entries(entries: Vec<DirEntryPlus>) -> Result<Self, CodecError> {
445 let size = entries.iter().try_fold(0usize, |n, entry| {
446 n.checked_add(entry.wire_size())
447 .ok_or(CodecError::LengthOverflow)
448 })?;
449 let mut data = alloc::vec![0; size];
450 let mut cursor = 0;
451 for entry in entries {
452 cursor += entry.to_slice(&mut data[cursor..])?;
453 }
454 Ok(Self {
455 count: size.try_into()?,
456 data: data.into(),
457 })
458 }
459 pub fn to_entries(&self) -> Result<Vec<DirEntryPlus>, CodecError> {
460 let mut input = self.data.0.clone();
461 let mut entries = Vec::new();
462 while !input.is_empty() {
463 let (entry, length) = DirEntryPlus::decode(&input)?;
464 entries.push(entry);
465 input.advance(length);
466 }
467 Ok(entries)
468 }
469}
470
471#[derive(Debug, Clone)]
472pub struct Rwalk {
473 pub nwqid: u16,
474 pub wqids: Vec<Qid>,
475}
476
477#[derive(Debug, Clone)]
478pub struct Rlcreate {
479 pub qid: Qid,
480 pub iounit: u32,
481}
482
483#[derive(Debug, Clone)]
484pub struct Rread {
485 pub count: u32,
486 pub data: P9Bytes,
487}
488
489#[derive(Debug, Clone)]
490pub struct Rreaddir {
491 pub count: u32,
492 pub data: P9Bytes,
493}
494
495impl Rreaddir {
496 pub fn from_entries(entries: Vec<DirEntry>) -> Result<Self, CodecError> {
497 let size = entries.iter().try_fold(0usize, |n, entry| {
498 n.checked_add(entry.wire_size())
499 .ok_or(CodecError::LengthOverflow)
500 })?;
501 let mut data = alloc::vec![0; size];
502 let mut cursor = 0;
503 for entry in entries {
504 cursor += entry.to_slice(&mut data[cursor..])?;
505 }
506 Ok(Self {
507 count: size.try_into()?,
508 data: data.into(),
509 })
510 }
511 pub fn to_entries(&self) -> Result<Vec<DirEntry>, CodecError> {
512 let mut input = self.data.0.clone();
513 let mut entries = Vec::new();
514 while !input.is_empty() {
515 let (entry, length) = DirEntry::decode(&input)?;
516 entries.push(entry);
517 input.advance(length);
518 }
519 Ok(entries)
520 }
521}
522
523#[derive(Debug, Clone)]
524pub struct Rmkdir {
525 pub qid: Qid,
526}
527
528#[derive(Debug, Clone)]
529pub struct Rsymlink {
530 pub qid: Qid,
531}
532
533#[derive(Debug, Clone)]
534pub struct Rmknod {
535 pub qid: Qid,
536}
537
538#[derive(Debug, Clone)]
539pub struct Rreadlink {
540 pub target: P9String,
541}
542
543#[derive(Debug, Clone)]
545pub struct Rclunk;
546
547#[derive(Debug, Clone)]
548pub struct Rsetattr;
549
550#[derive(Debug, Clone)]
551pub struct Rrename;
552
553#[derive(Debug, Clone)]
554pub struct Rlink;
555
556#[derive(Debug, Clone)]
557pub struct Rrenameat;
558
559#[derive(Debug, Clone)]
560pub struct Runlinkat;
561
562#[derive(Debug, Clone)]
563pub struct Rfsync;
564
565#[derive(Debug, Clone)]
567pub struct Tgetlineage;
568
569#[derive(Debug, Clone)]
574pub struct Tfsyncdur {
575 pub fid: u32,
576 pub datasync: u32,
577 pub token: u64,
578}
579
580pub const T_LCREATE: u8 = message_type::TLCREATE;
582pub const T_SYMLINK: u8 = message_type::TSYMLINK;
583pub const T_MKNOD: u8 = message_type::TMKNOD;
584pub const T_RENAME: u8 = message_type::TRENAME;
585pub const T_SETATTR: u8 = message_type::TSETATTR;
586pub const T_WRITE: u8 = message_type::TWRITE;
587pub const T_LINK: u8 = message_type::TLINK;
588pub const T_MKDIR: u8 = message_type::TMKDIR;
589pub const T_RENAMEAT: u8 = message_type::TRENAMEAT;
590pub const T_UNLINKAT: u8 = message_type::TUNLINKAT;
591pub const T_LCREATEATTR: u8 = message_type::TLCREATEATTR;
592pub const T_MKDIRATTR: u8 = message_type::TMKDIRATTR;
593pub const T_SYMLINKATTR: u8 = message_type::TSYMLINKATTR;
594pub const T_MKNODATTR: u8 = message_type::TMKNODATTR;
595pub const T_LINKATTR: u8 = message_type::TLINKATTR;
596pub const T_SETATTRATTR: u8 = message_type::TSETATTRATTR;
597pub const T_FALLOCATE: u8 = message_type::TFALLOCATE;
599pub const R_FALLOCATE: u8 = message_type::RFALLOCATE;
600
601#[derive(Debug, Clone)]
603pub enum Message {
604 Tversion(Tversion),
605 Rversion(Rversion),
606 Tattach(Tattach),
607 Rattach(Rattach),
608 Twalk(Twalk),
609 Rwalk(Rwalk),
610 Tlopen(Tlopen),
611 Rlopen(Rlopen),
612 Tlcreate(Tlcreate),
613 Rlcreate(Rlcreate),
614 Tread(Tread),
615 Rread(Rread),
616 Twrite(Twrite),
617 Rwrite(Rwrite),
618 Tclunk(Tclunk),
619 Rclunk(Rclunk),
620 Treaddir(Treaddir),
621 Rreaddir(Rreaddir),
622 Tgetattr(Tgetattr),
623 Rgetattr(Rgetattr),
624 Tsetattr(Tsetattr),
625 Rsetattr(Rsetattr),
626 Tfallocate(Tfallocate),
627 Rfallocate(Rfallocate),
628 Tmkdir(Tmkdir),
629 Rmkdir(Rmkdir),
630 Tsymlink(Tsymlink),
631 Rsymlink(Rsymlink),
632 Tmknod(Tmknod),
633 Rmknod(Rmknod),
634 Treadlink(Treadlink),
635 Rreadlink(Rreadlink),
636 Tlink(Tlink),
637 Rlink(Rlink),
638 Trename(Trename),
639 Rrename(Rrename),
640 Trenameat(Trenameat),
641 Rrenameat(Rrenameat),
642 Tunlinkat(Tunlinkat),
643 Runlinkat(Runlinkat),
644 Tfsync(Tfsync),
645 Rfsync(Rfsync),
646 Tfsyncdur(Tfsyncdur),
647 Tgetlineage(Tgetlineage),
648 Rgetlineage(Rgetlineage),
649 Tlock(Tlock),
650 Rlock(Rlock),
651 Tgetlock(Tgetlock),
652 Rgetlock(Rgetlock),
653 Rlerror(Rlerror),
654 Tflush(Tflush),
655 Rflush(Rflush),
656 Txattrwalk(Txattrwalk),
657 Rxattrwalk(Rxattrwalk),
658 Tstatfs(Tstatfs),
659 Rstatfs(Rstatfs),
660 Tlopenat(Tlopenat),
663 Rlopenat(Rlopenat),
664 Tlopenatread(Tlopenatread),
665 Rlopenatread(Rlopenatread),
666 Tlcreateattr(Tlcreateattr),
667 Rlcreateattr(Rlcreateattr),
668 Tmkdirattr(Tmkdir),
669 Rmkdirattr(Rmkdirattr),
670 Tsymlinkattr(Tsymlink),
671 Rsymlinkattr(Rsymlinkattr),
672 Tmknodattr(Tmknod),
673 Rmknodattr(Rmknodattr),
674 Tlinkattr(Tlink),
675 Rlinkattr(Rlinkattr),
676 Tsetattrattr(Tsetattr),
677 Rsetattrattr(Rsetattrattr),
678 Trebind(Trebind),
680 Rrebind(Rrebind),
681 Twalkgetattr(Twalkgetattr),
682 Rwalkgetattr(Rwalkgetattr),
683 Treaddirattr(Treaddirattr),
684 Rreaddirattr(Rreaddirattr),
685}
686
687impl Message {
688 pub fn is_zerofs_private_request(&self) -> bool {
690 match self {
691 Message::Tfallocate(_)
692 | Message::Tfsyncdur(_)
693 | Message::Tgetlineage(_)
694 | Message::Tlopenat(_)
695 | Message::Tlopenatread(_)
696 | Message::Tlcreateattr(_)
697 | Message::Tmkdirattr(_)
698 | Message::Tsymlinkattr(_)
699 | Message::Tmknodattr(_)
700 | Message::Tlinkattr(_)
701 | Message::Tsetattrattr(_)
702 | Message::Trebind(_)
703 | Message::Twalkgetattr(_)
704 | Message::Treaddirattr(_) => true,
705 Message::Tversion(_)
706 | Message::Rversion(_)
707 | Message::Tattach(_)
708 | Message::Rattach(_)
709 | Message::Twalk(_)
710 | Message::Rwalk(_)
711 | Message::Tlopen(_)
712 | Message::Rlopen(_)
713 | Message::Tlcreate(_)
714 | Message::Rlcreate(_)
715 | Message::Tread(_)
716 | Message::Rread(_)
717 | Message::Twrite(_)
718 | Message::Rwrite(_)
719 | Message::Tclunk(_)
720 | Message::Rclunk(_)
721 | Message::Treaddir(_)
722 | Message::Rreaddir(_)
723 | Message::Tgetattr(_)
724 | Message::Rgetattr(_)
725 | Message::Tsetattr(_)
726 | Message::Rsetattr(_)
727 | Message::Rfallocate(_)
728 | Message::Tmkdir(_)
729 | Message::Rmkdir(_)
730 | Message::Tsymlink(_)
731 | Message::Rsymlink(_)
732 | Message::Tmknod(_)
733 | Message::Rmknod(_)
734 | Message::Treadlink(_)
735 | Message::Rreadlink(_)
736 | Message::Tlink(_)
737 | Message::Rlink(_)
738 | Message::Trename(_)
739 | Message::Rrename(_)
740 | Message::Trenameat(_)
741 | Message::Rrenameat(_)
742 | Message::Tunlinkat(_)
743 | Message::Runlinkat(_)
744 | Message::Tfsync(_)
745 | Message::Rfsync(_)
746 | Message::Rgetlineage(_)
747 | Message::Tlock(_)
748 | Message::Rlock(_)
749 | Message::Tgetlock(_)
750 | Message::Rgetlock(_)
751 | Message::Rlerror(_)
752 | Message::Tflush(_)
753 | Message::Rflush(_)
754 | Message::Txattrwalk(_)
755 | Message::Rxattrwalk(_)
756 | Message::Tstatfs(_)
757 | Message::Rstatfs(_)
758 | Message::Rlopenat(_)
759 | Message::Rlopenatread(_)
760 | Message::Rlcreateattr(_)
761 | Message::Rmkdirattr(_)
762 | Message::Rsymlinkattr(_)
763 | Message::Rmknodattr(_)
764 | Message::Rlinkattr(_)
765 | Message::Rsetattrattr(_)
766 | Message::Rrebind(_)
767 | Message::Rwalkgetattr(_)
768 | Message::Rreaddirattr(_) => false,
769 }
770 }
771
772 pub fn request_fids(&self) -> impl Iterator<Item = u32> {
775 let fids = match self {
776 Message::Tattach(m) => [Some(m.fid), (m.afid != NOFID).then_some(m.afid)],
777 Message::Twalk(m) => [Some(m.fid), Some(m.newfid)],
778 Message::Tlopen(m) => [Some(m.fid), None],
779 Message::Tlcreate(m) => [Some(m.fid), None],
780 Message::Tread(m) => [Some(m.fid), None],
781 Message::Twrite(m) => [Some(m.fid), None],
782 Message::Tclunk(m) => [Some(m.fid), None],
783 Message::Treaddir(m) => [Some(m.fid), None],
784 Message::Tgetattr(m) => [Some(m.fid), None],
785 Message::Tsetattr(m) | Message::Tsetattrattr(m) => [Some(m.fid), None],
786 Message::Tfallocate(m) => [Some(m.fid), None],
787 Message::Tmkdir(m) | Message::Tmkdirattr(m) => [Some(m.dfid), None],
788 Message::Tsymlink(m) | Message::Tsymlinkattr(m) => [Some(m.dfid), None],
789 Message::Tmknod(m) | Message::Tmknodattr(m) => [Some(m.dfid), None],
790 Message::Treadlink(m) => [Some(m.fid), None],
791 Message::Tlink(m) | Message::Tlinkattr(m) => [Some(m.dfid), Some(m.fid)],
792 Message::Trename(m) => [Some(m.fid), Some(m.dfid)],
793 Message::Trenameat(m) => [Some(m.olddirfid), Some(m.newdirfid)],
794 Message::Tunlinkat(m) => [Some(m.dirfid), None],
795 Message::Tfsync(m) => [Some(m.fid), None],
796 Message::Tfsyncdur(m) => [Some(m.fid), None],
797 Message::Tlock(m) => [Some(m.fid), None],
798 Message::Tgetlock(m) => [Some(m.fid), None],
799 Message::Txattrwalk(m) => [Some(m.fid), Some(m.newfid)],
800 Message::Tstatfs(m) => [Some(m.fid), None],
801 Message::Tlopenat(m) => [Some(m.fid), Some(m.newfid)],
802 Message::Tlopenatread(m) => [Some(m.fid), Some(m.newfid)],
803 Message::Tlcreateattr(m) => [Some(m.dfid), Some(m.newfid)],
804 Message::Trebind(m) => [Some(m.fid), None],
805 Message::Twalkgetattr(m) => [Some(m.fid), Some(m.newfid)],
806 Message::Treaddirattr(m) => [Some(m.fid), None],
807 Message::Tversion(_)
808 | Message::Rversion(_)
809 | Message::Rattach(_)
810 | Message::Rwalk(_)
811 | Message::Rlopen(_)
812 | Message::Rlcreate(_)
813 | Message::Rread(_)
814 | Message::Rwrite(_)
815 | Message::Rclunk(_)
816 | Message::Rreaddir(_)
817 | Message::Rgetattr(_)
818 | Message::Rsetattr(_)
819 | Message::Rfallocate(_)
820 | Message::Rmkdir(_)
821 | Message::Rsymlink(_)
822 | Message::Rmknod(_)
823 | Message::Rreadlink(_)
824 | Message::Rlink(_)
825 | Message::Rrename(_)
826 | Message::Rrenameat(_)
827 | Message::Runlinkat(_)
828 | Message::Rfsync(_)
829 | Message::Tgetlineage(_)
830 | Message::Rgetlineage(_)
831 | Message::Rlock(_)
832 | Message::Rgetlock(_)
833 | Message::Rlerror(_)
834 | Message::Tflush(_)
835 | Message::Rflush(_)
836 | Message::Rxattrwalk(_)
837 | Message::Rstatfs(_)
838 | Message::Rlopenat(_)
839 | Message::Rlopenatread(_)
840 | Message::Rlcreateattr(_)
841 | Message::Rmkdirattr(_)
842 | Message::Rsymlinkattr(_)
843 | Message::Rmknodattr(_)
844 | Message::Rlinkattr(_)
845 | Message::Rsetattrattr(_)
846 | Message::Rrebind(_)
847 | Message::Rwalkgetattr(_)
848 | Message::Rreaddirattr(_) => [None, None],
849 };
850 fids.into_iter().flatten()
851 }
852
853 pub fn is_mutation(&self) -> bool {
855 self.durability_fid().is_some()
856 }
857
858 pub fn durability_fid(&self) -> Option<u32> {
860 match self {
861 Message::Twrite(m) => Some(m.fid),
862 Message::Tsetattr(m) => Some(m.fid),
863 Message::Tsetattrattr(m) => Some(m.fid),
864 Message::Tfallocate(m) => Some(m.fid),
865 Message::Tlcreate(m) => Some(m.fid),
866 Message::Tlcreateattr(m) => Some(m.dfid),
867 Message::Tmkdir(m) => Some(m.dfid),
868 Message::Tmkdirattr(m) => Some(m.dfid),
869 Message::Tsymlink(m) => Some(m.dfid),
870 Message::Tsymlinkattr(m) => Some(m.dfid),
871 Message::Tmknod(m) => Some(m.dfid),
872 Message::Tmknodattr(m) => Some(m.dfid),
873 Message::Tlink(m) => Some(m.dfid),
874 Message::Tlinkattr(m) => Some(m.dfid),
875 Message::Trename(m) => Some(m.dfid),
876 Message::Trenameat(m) => Some(m.newdirfid),
877 Message::Tunlinkat(m) => Some(m.dirfid),
878 _ => None,
879 }
880 }
881
882 pub fn durability_fids(&self) -> impl Iterator<Item = u32> {
884 let extra = match self {
885 Message::Trenameat(m) => Some(m.olddirfid),
886 _ => None,
887 };
888 self.durability_fid().into_iter().chain(extra)
889 }
890}
891
892#[derive(Debug, Clone)]
894pub struct P9Message {
895 pub size: u32,
896 pub type_: u8,
897 pub tag: u16,
898 pub op_id: [u8; 16],
899 pub op_flags: u8,
900 pub op_origin_epoch: u64,
901 pub body: Message,
902}
903
904impl P9Message {
905 pub fn to_bytes(&self) -> Result<Vec<u8>, CodecError> {
906 self.to_bytes_ctx(false)
907 }
908
909 pub fn to_bytes_ctx(&self, op_id_enabled: bool) -> Result<Vec<u8>, CodecError> {
910 Self::encode_body(self.tag, self.envelope(), &self.body, op_id_enabled)
911 }
912
913 pub fn encode_body(
914 tag: u16,
915 envelope: MutationEnvelope,
916 body: &Message,
917 op_id_enabled: bool,
918 ) -> Result<Vec<u8>, CodecError> {
919 let view = body.as_view();
920 let size = slice_codec::encoded_frame_size(&view, op_id_enabled)?;
921 if size > P9_MAX_MSIZE as usize {
922 return Err(CodecError::MessageTooLarge);
923 }
924 let mut output = alloc::vec![0; size];
925 slice_codec::encode_frame(
926 &mut output,
927 P9_MAX_MSIZE,
928 tag,
929 envelope,
930 &view,
931 op_id_enabled,
932 )?;
933 Ok(output)
934 }
935
936 pub fn to_slice_ctx(
937 &self,
938 output: &mut [u8],
939 op_id_enabled: bool,
940 ) -> Result<usize, CodecError> {
941 slice_codec::encode_frame(
942 output,
943 P9_MAX_MSIZE,
944 self.tag,
945 self.envelope(),
946 &self.body.as_view(),
947 op_id_enabled,
948 )
949 }
950
951 fn envelope(&self) -> MutationEnvelope {
952 MutationEnvelope {
953 op_id: self.op_id,
954 flags: self.op_flags,
955 origin_writer_epoch: self.op_origin_epoch,
956 }
957 }
958
959 pub fn from_bytes_ctx(input: &[u8], op_id_enabled: bool) -> Result<Self, CodecError> {
960 let header = slice_codec::decode_header(input, P9_MAX_MSIZE)?;
961 if header.size as usize != input.len() {
962 return Err(CodecError::FrameSizeMismatch);
963 }
964 Self::from_owned_bytes_ctx(Bytes::copy_from_slice(input), op_id_enabled)
965 }
966
967 pub fn from_owned_bytes_ctx(input: Bytes, op_id_enabled: bool) -> Result<Self, CodecError> {
968 let frame = slice_codec::decode_frame(&input, P9_MAX_MSIZE, op_id_enabled)?;
969 Ok(Self {
970 size: frame.header.size,
971 type_: frame.header.type_,
972 tag: frame.header.tag,
973 op_id: frame.envelope.op_id,
974 op_flags: frame.envelope.flags,
975 op_origin_epoch: frame.envelope.origin_writer_epoch,
976 body: Message::from_view(frame.body, &input)?,
977 })
978 }
979
980 pub fn carries_op_id(type_: u8) -> bool {
981 slice_codec::carries_op_id(type_)
982 }
983
984 pub fn new(tag: u16, body: Message) -> Self {
985 let type_ = body.as_view().type_id();
986
987 Self {
988 size: 0,
989 type_,
990 tag,
991 op_id: [0u8; 16],
992 op_flags: 0,
993 op_origin_epoch: 0,
994 body,
995 }
996 }
997
998 pub fn new_with_op_id(tag: u16, op_id: [u8; 16], body: Message) -> Self {
1000 Self::new_with_op_id_flags_and_origin(tag, op_id, 0, 0, body)
1001 }
1002
1003 pub fn new_with_op_id_flags_and_origin(
1005 tag: u16,
1006 op_id: [u8; 16],
1007 op_flags: u8,
1008 op_origin_epoch: u64,
1009 body: Message,
1010 ) -> Self {
1011 let mut msg = Self::new(tag, body);
1012 msg.op_id = op_id;
1013 msg.op_flags = op_flags;
1014 msg.op_origin_epoch = op_origin_epoch;
1015 msg
1016 }
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021 use super::*;
1022 use alloc::vec;
1023
1024 #[test]
1025 fn caller_owned_output_matches_allocating_encoder() {
1026 let message = P9Message::new(
1027 42,
1028 Message::Tgetattr(Tgetattr {
1029 fid: 7,
1030 request_mask: GETATTR_ALL,
1031 }),
1032 );
1033 let expected = message.to_bytes_ctx(false).unwrap();
1034 let mut output = [0u8; 64];
1035
1036 let written = message.to_slice_ctx(&mut output, false).unwrap();
1037
1038 assert_eq!(&output[..written], expected);
1039 }
1040
1041 fn counted_payload(message: &P9Message) -> (u32, &Bytes) {
1042 match &message.body {
1043 Message::Rread(message) => (message.count, &message.data),
1044 Message::Rreaddir(message) => (message.count, &message.data),
1045 Message::Rlopenatread(message) => (message.count, &message.data),
1046 Message::Twrite(message) => (message.count, &message.data),
1047 Message::Rreaddirattr(message) => (message.count, &message.data),
1048 _ => panic!("expected a counted-payload message"),
1049 }
1050 }
1051
1052 fn assert_owned_payload_aliases(
1053 message: P9Message,
1054 op_id_enabled: bool,
1055 payload_offset: usize,
1056 expected_payload: &[u8],
1057 ) {
1058 let frame = Bytes::from(message.to_bytes_ctx(op_id_enabled).unwrap());
1059 let expected_ptr = frame.as_ptr().wrapping_add(payload_offset);
1060 let expected_frame = frame.clone();
1061 let decoded = P9Message::from_owned_bytes_ctx(frame, op_id_enabled).unwrap();
1062 let (decoded_count, decoded_payload) = counted_payload(&decoded);
1063
1064 assert_eq!(decoded.size, expected_frame.len() as u32);
1065 assert_eq!(decoded_count, expected_payload.len() as u32);
1066 assert_eq!(decoded_payload.as_ref(), expected_payload);
1067 assert_eq!(
1068 decoded_payload.as_ptr(),
1069 expected_ptr,
1070 "the decoded payload must be a slice of the owned frame"
1071 );
1072 assert_eq!(
1073 decoded.to_bytes_ctx(op_id_enabled).unwrap(),
1074 expected_frame,
1075 "owned decoding must preserve every scalar field"
1076 );
1077 }
1078
1079 fn tmkdir() -> Message {
1080 Message::Tmkdir(Tmkdir {
1081 dfid: 1,
1082 name: P9String::new(b"d".to_vec()),
1083 mode: 0o755,
1084 gid: 0,
1085 })
1086 }
1087
1088 #[test]
1089 fn durability_fids_yields_both_directories_for_a_renameat() {
1090 let m = Message::Trenameat(Trenameat {
1094 olddirfid: 11,
1095 oldname: P9String::new(b"a".to_vec()),
1096 newdirfid: 22,
1097 newname: P9String::new(b"b".to_vec()),
1098 });
1099 assert_eq!(
1100 m.durability_fid(),
1101 Some(22),
1102 "the primary fid is the dest dir"
1103 );
1104 let mut fids: Vec<u32> = m.durability_fids().collect();
1105 fids.sort_unstable();
1106 assert_eq!(fids, vec![11, 22], "both source and dest dirs are covered");
1107 }
1108
1109 #[test]
1110 fn durability_fids_is_the_single_fid_for_non_rename_ops() {
1111 let fids: Vec<u32> = tmkdir().durability_fids().collect();
1112 assert_eq!(
1113 fids,
1114 vec![1],
1115 "a single-directory op yields just its own fid"
1116 );
1117 }
1118
1119 #[test]
1120 fn request_fids_omits_the_nofid_attach_sentinel() {
1121 let attach = |afid| {
1122 Message::Tattach(Tattach {
1123 fid: 7,
1124 afid,
1125 uname: P9String::new(Vec::new()),
1126 aname: P9String::new(Vec::new()),
1127 n_uname: 0,
1128 })
1129 };
1130
1131 assert_eq!(attach(NOFID).request_fids().collect::<Vec<_>>(), vec![7]);
1132 assert_eq!(attach(8).request_fids().collect::<Vec<_>>(), vec![7, 8]);
1133 }
1134
1135 #[test]
1136 fn request_fids_includes_both_rename_directories() {
1137 let request = Message::Trenameat(Trenameat {
1138 olddirfid: 11,
1139 oldname: P9String::new(b"old".to_vec()),
1140 newdirfid: 22,
1141 newname: P9String::new(b"new".to_vec()),
1142 });
1143
1144 assert_eq!(request.request_fids().collect::<Vec<_>>(), vec![11, 22]);
1145 }
1146
1147 #[test]
1148 fn request_fids_is_empty_for_fidless_messages() {
1149 assert!(
1150 Message::Tflush(Tflush { oldtag: 1 })
1151 .request_fids()
1152 .next()
1153 .is_none()
1154 );
1155 assert!(Message::Rflush(Rflush).request_fids().next().is_none());
1156 }
1157
1158 #[test]
1159 fn fallocate_round_trips_and_tracks_its_file_fid() {
1160 assert_eq!(classify_fallocate_mode(0), Some(FallocateKind::Allocate));
1161 assert_eq!(
1162 classify_fallocate_mode(FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE),
1163 Some(FallocateKind::PunchHole)
1164 );
1165 assert_eq!(
1166 classify_fallocate_mode(FALLOC_FL_ZERO_RANGE),
1167 Some(FallocateKind::ZeroRange { keep_size: false })
1168 );
1169 assert_eq!(
1170 classify_fallocate_mode(FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE),
1171 Some(FallocateKind::ZeroRange { keep_size: true })
1172 );
1173 assert_eq!(classify_fallocate_mode(FALLOC_FL_KEEP_SIZE), None);
1174
1175 let msg = P9Message::new(
1176 9,
1177 Message::Tfallocate(Tfallocate {
1178 fid: 42,
1179 offset: 100,
1180 length: 200,
1181 mode: FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
1182 }),
1183 );
1184 let bytes = msg.to_bytes().unwrap();
1185 let decoded = P9Message::from_bytes_ctx(&bytes, false).unwrap();
1186 assert_eq!(decoded.type_, T_FALLOCATE);
1187 assert_eq!(decoded.body.durability_fid(), Some(42));
1188 match decoded.body {
1189 Message::Tfallocate(tf) => {
1190 assert_eq!(tf.offset, 100);
1191 assert_eq!(tf.length, 200);
1192 assert_eq!(tf.mode, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE);
1193 }
1194 _ => panic!("expected Tfallocate"),
1195 }
1196 }
1197
1198 #[test]
1199 fn op_id_round_trips_on_a_zerofs_request() {
1200 let op_id = [7u8; 16];
1201 let msg =
1202 P9Message::new_with_op_id_flags_and_origin(5, op_id, P9_OP_FLAG_RETRY, 42, tmkdir());
1203 let bytes = msg.to_bytes_ctx(true).unwrap();
1204 let decoded = P9Message::from_bytes_ctx(&bytes, true).unwrap();
1205 assert_eq!(
1206 decoded.op_id, op_id,
1207 "the op-id must round-trip in the ZeroFS dialect"
1208 );
1209 assert_eq!(decoded.op_flags, P9_OP_FLAG_RETRY);
1210 assert_eq!(decoded.op_origin_epoch, 42);
1211 assert_eq!(decoded.tag, 5);
1212 assert!(matches!(decoded.body, Message::Tmkdir(_)));
1213 }
1214
1215 #[test]
1216 fn owned_rread_payload_aliases_the_input_frame() {
1217 let payload = b"rread payload retained from its frame";
1218 let message = P9Message::new(
1219 7,
1220 Message::Rread(Rread {
1221 count: payload.len() as u32,
1222 data: payload.to_vec().into(),
1223 }),
1224 );
1225
1226 assert_owned_payload_aliases(message, false, P9_IOHDRSZ as usize, payload);
1227 }
1228
1229 #[test]
1230 fn owned_rlopenatread_payload_aliases_the_input_frame() {
1231 let payload = b"prefetched bytes retained from their frame";
1232 let message = P9Message::new(
1233 11,
1234 Message::Rlopenatread(Rlopenatread {
1235 qid: qid(),
1236 iounit: 64 * 1024,
1237 eof: 1,
1238 count: payload.len() as u32,
1239 data: payload.to_vec().into(),
1240 }),
1241 );
1242
1243 assert_owned_payload_aliases(message, false, P9_RLOPENATREAD_HDR as usize, payload);
1244 }
1245
1246 #[test]
1247 fn owned_enveloped_twrite_payload_aliases_the_input_frame() {
1248 let payload = b"write bytes retained from their enveloped frame";
1249 let message = P9Message::new_with_op_id_flags_and_origin(
1250 19,
1251 [0xa5; P9_OP_ID_LEN],
1252 P9_OP_FLAG_RETRY,
1253 42,
1254 Message::Twrite(Twrite {
1255 fid: 23,
1256 offset: 29,
1257 count: payload.len() as u32,
1258 data: payload.to_vec().into(),
1259 }),
1260 );
1261
1262 assert_owned_payload_aliases(
1263 message,
1264 true,
1265 P9_TWRITE_HDR as usize + P9_OP_ENVELOPE_LEN,
1266 payload,
1267 );
1268 }
1269
1270 #[test]
1271 fn owned_standard_twrite_payload_aliases_the_input_frame() {
1272 let payload = b"write bytes retained from a standard frame";
1273 let message = P9Message::new(
1274 21,
1275 Message::Twrite(Twrite {
1276 fid: 23,
1277 offset: 29,
1278 count: payload.len() as u32,
1279 data: payload.to_vec().into(),
1280 }),
1281 );
1282
1283 assert_owned_payload_aliases(message, false, P9_TWRITE_HDR as usize, payload);
1284 }
1285
1286 #[test]
1287 fn owned_directory_payloads_alias_the_input_frame() {
1288 let payload = b"encoded directory entries";
1289 for body in [
1290 Message::Rreaddir(Rreaddir {
1291 count: payload.len() as u32,
1292 data: payload.to_vec().into(),
1293 }),
1294 Message::Rreaddirattr(Rreaddirattr {
1295 count: payload.len() as u32,
1296 data: payload.to_vec().into(),
1297 }),
1298 ] {
1299 assert_owned_payload_aliases(
1300 P9Message::new(25, body),
1301 false,
1302 P9_IOHDRSZ as usize,
1303 payload,
1304 );
1305 }
1306 }
1307
1308 #[test]
1309 fn owned_metadata_and_directory_names_alias_the_input_frame() {
1310 let name = P9String::new(Bytes::from_static(b"name\xff"));
1311 let bodies = [
1312 Message::Twalk(Twalk {
1313 fid: 1,
1314 newfid: 2,
1315 nwname: 1,
1316 wnames: vec![name.clone()],
1317 }),
1318 Message::Tsymlink(Tsymlink {
1319 dfid: 1,
1320 name: name.clone(),
1321 symtgt: P9String::new(Bytes::from_static(b"target")),
1322 gid: 0,
1323 }),
1324 Message::Rreaddir(
1325 Rreaddir::from_entries(vec![DirEntry {
1326 qid: qid(),
1327 offset: 1,
1328 type_: 0,
1329 name: name.clone(),
1330 }])
1331 .unwrap(),
1332 ),
1333 Message::Rreaddirattr(
1334 Rreaddirattr::from_entries(vec![DirEntryPlus {
1335 qid: qid(),
1336 offset: 1,
1337 type_: 0,
1338 name,
1339 stat: stat(),
1340 }])
1341 .unwrap(),
1342 ),
1343 ];
1344 for body in bodies {
1345 let message = P9Message::new(1, body);
1346 for dialect in [false, true] {
1347 let frame = Bytes::from(message.to_bytes_ctx(dialect).unwrap());
1348 let start = frame.as_ptr() as usize;
1349 let end = start + frame.len();
1350 let decoded = P9Message::from_owned_bytes_ctx(frame, dialect).unwrap();
1351 let names = match decoded.body {
1352 Message::Twalk(walk) => walk.wnames,
1353 Message::Tsymlink(link) => vec![link.name, link.symtgt],
1354 Message::Rreaddir(dir) => dir
1355 .to_entries()
1356 .unwrap()
1357 .into_iter()
1358 .map(|entry| entry.name)
1359 .collect(),
1360 Message::Rreaddirattr(dir) => dir
1361 .to_entries()
1362 .unwrap()
1363 .into_iter()
1364 .map(|entry| entry.name)
1365 .collect(),
1366 _ => unreachable!(),
1367 };
1368 assert!(!names.is_empty());
1369 for name in names {
1370 let ptr = name.data.as_ptr() as usize;
1371 assert!(!name.is_empty());
1372 assert!(ptr >= start && ptr + name.len() <= end);
1373 }
1374 }
1375 }
1376 }
1377
1378 #[test]
1379 fn owned_and_borrowed_decoders_reject_trailing_data() {
1380 let mut frame = P9Message::new(
1381 27,
1382 Message::Rread(Rread {
1383 count: 7,
1384 data: b"payload".to_vec().into(),
1385 }),
1386 )
1387 .to_bytes()
1388 .unwrap();
1389 frame[P9_HEADER_SIZE..P9_IOHDRSZ as usize].copy_from_slice(&3u32.to_le_bytes());
1390
1391 assert_eq!(
1392 P9Message::from_bytes_ctx(&frame, false).unwrap_err(),
1393 CodecError::TrailingData
1394 );
1395 assert_eq!(
1396 P9Message::from_owned_bytes_ctx(Bytes::from(frame), false).unwrap_err(),
1397 CodecError::TrailingData
1398 );
1399 }
1400
1401 #[test]
1402 fn owned_counted_decoder_rejects_truncated_payloads() {
1403 let payload = b"payload";
1404 let rread = P9Message::new(
1405 31,
1406 Message::Rread(Rread {
1407 count: payload.len() as u32,
1408 data: payload.to_vec().into(),
1409 }),
1410 );
1411 let valid = rread.to_bytes().unwrap();
1412
1413 let count_cases = [
1414 (rread, false, P9_IOHDRSZ as usize - P9_COUNT_FIELD_LEN),
1415 (
1416 P9Message::new(
1417 32,
1418 Message::Rlopenatread(Rlopenatread {
1419 qid: qid(),
1420 iounit: 4096,
1421 eof: 0,
1422 count: payload.len() as u32,
1423 data: payload.to_vec().into(),
1424 }),
1425 ),
1426 false,
1427 P9_RLOPENATREAD_HDR as usize - P9_COUNT_FIELD_LEN,
1428 ),
1429 (
1430 P9Message::new_with_op_id(
1431 33,
1432 [0x5a; P9_OP_ID_LEN],
1433 Message::Twrite(Twrite {
1434 fid: 1,
1435 offset: 2,
1436 count: payload.len() as u32,
1437 data: payload.to_vec().into(),
1438 }),
1439 ),
1440 true,
1441 P9_TWRITE_HDR as usize + P9_OP_ENVELOPE_LEN - P9_COUNT_FIELD_LEN,
1442 ),
1443 ];
1444
1445 for (message, op_id_enabled, count_offset) in count_cases {
1446 let mut truncated = message.to_bytes_ctx(op_id_enabled).unwrap();
1447 truncated[count_offset..count_offset + P9_COUNT_FIELD_LEN]
1448 .copy_from_slice(&((payload.len() + 1) as u32).to_le_bytes());
1449 assert!(
1450 P9Message::from_owned_bytes_ctx(Bytes::from(truncated), op_id_enabled).is_err()
1451 );
1452 }
1453
1454 let mut truncated_count = valid[..P9_HEADER_SIZE + P9_COUNT_FIELD_LEN - 1].to_vec();
1455 let truncated_size = truncated_count.len() as u32;
1456 truncated_count[..P9_SIZE_FIELD_LEN].copy_from_slice(&truncated_size.to_le_bytes());
1457 assert!(P9Message::from_owned_bytes_ctx(Bytes::from(truncated_count), false).is_err());
1458 }
1459
1460 #[test]
1461 fn op_id_write_payload_at_advertised_boundary_fits_msize() {
1462 let msize = 4096u32;
1463 let count = msize - P9_TWRITE_HDR - P9_OP_ENVELOPE_LEN as u32;
1464 let msg = P9Message::new_with_op_id(
1465 5,
1466 [7u8; 16],
1467 Message::Twrite(Twrite {
1468 fid: 9,
1469 offset: 0,
1470 count,
1471 data: vec![0u8; count as usize].into(),
1472 }),
1473 );
1474 let bytes = msg.to_bytes_ctx(true).unwrap();
1475 assert_eq!(bytes.len(), msize as usize);
1476 let decoded = P9Message::from_bytes_ctx(&bytes, true).unwrap();
1477 assert_eq!(decoded.op_id, [7u8; 16]);
1478 assert_eq!(decoded.op_flags, 0);
1479 assert_eq!(decoded.op_origin_epoch, 0);
1480 assert!(matches!(decoded.body, Message::Twrite(_)));
1481 }
1482
1483 #[test]
1484 fn op_id_is_absent_in_standard_framing() {
1485 let op_id = [7u8; 16];
1486 let msg = P9Message::new_with_op_id(5, op_id, tmkdir());
1487 let with = msg.to_bytes_ctx(true).unwrap();
1488 let without = msg.to_bytes_ctx(false).unwrap();
1489 assert_eq!(
1490 with.len(),
1491 without.len() + P9_OP_ENVELOPE_LEN,
1492 "the op envelope is present only in the ZeroFS dialect"
1493 );
1494 let decoded = P9Message::from_bytes_ctx(&without, false).unwrap();
1495 assert_eq!(
1496 decoded.op_id, [0u8; 16],
1497 "standard framing carries no op-id"
1498 );
1499 assert_eq!(decoded.op_flags, 0);
1500 let plain = P9Message::from_bytes_ctx(&without, false).unwrap();
1501 assert_eq!(plain.tag, 5);
1502 }
1503
1504 #[test]
1505 fn idempotent_requests_carry_no_op_id() {
1506 let msg = P9Message::new_with_op_id(5, [7u8; 16], Message::Tclunk(Tclunk { fid: 9 }));
1507 let with = msg.to_bytes_ctx(true).unwrap();
1508 let without = msg.to_bytes_ctx(false).unwrap();
1509 assert_eq!(with, without, "an uncovered op must not carry an op-id");
1510 }
1511
1512 #[test]
1513 fn rebind_round_trips_explicit_root_inode() {
1514 let msg = P9Message::new(
1515 17,
1516 Message::Trebind(Trebind {
1517 fid: 9,
1518 inode_id: 42,
1519 root_inode: 7,
1520 flags: P9_REBIND_REPLAY | P9_REBIND_OPENED,
1521 uname: P9String::new(b"root".to_vec()),
1522 n_uname: 1000,
1523 }),
1524 );
1525 let bytes = msg.to_bytes().unwrap();
1526 let decoded = P9Message::from_bytes_ctx(&bytes, false).unwrap();
1527 match decoded.body {
1528 Message::Trebind(rebind) => {
1529 assert_eq!(rebind.fid, 9);
1530 assert_eq!(rebind.inode_id, 42);
1531 assert_eq!(rebind.root_inode, 7);
1532 assert_eq!(rebind.flags, P9_REBIND_REPLAY | P9_REBIND_OPENED);
1533 assert_eq!(rebind.uname.as_str().unwrap(), "root");
1534 assert_eq!(rebind.n_uname, 1000);
1535 }
1536 _ => panic!("expected Trebind"),
1537 }
1538 }
1539
1540 #[test]
1541 fn rebind_round_trips_binary_credentials() {
1542 let credentials = vec![
1543 P9_REBIND_CREDENTIAL_SENTINEL,
1544 P9_REBIND_CREDENTIAL_VERSION,
1545 0xd2,
1546 0x04,
1547 0,
1548 0,
1549 1,
1550 0x2e,
1551 0x16,
1552 0,
1553 0,
1554 ];
1555 let msg = P9Message::new(
1556 18,
1557 Message::Trebind(Trebind {
1558 fid: 10,
1559 inode_id: 43,
1560 root_inode: 0,
1561 flags: 0,
1562 uname: P9String::new(credentials.clone()),
1563 n_uname: 1001,
1564 }),
1565 );
1566 let bytes = msg.to_bytes().unwrap();
1567 let decoded = P9Message::from_bytes_ctx(&bytes, false).unwrap();
1568 match decoded.body {
1569 Message::Trebind(rebind) => {
1570 assert_eq!(rebind.uname.data, credentials);
1571 assert_eq!(rebind.n_uname, 1001);
1572 }
1573 _ => panic!("expected Trebind"),
1574 }
1575 }
1576
1577 #[test]
1578 fn truncated_large_payload_is_rejected() {
1579 let frame = P9Message::new(
1580 7,
1581 Message::Twrite(Twrite {
1582 fid: 1,
1583 offset: 0,
1584 count: u32::MAX,
1585 data: vec![0xde, 0xad, 0xbe, 0xef].into(),
1586 }),
1587 )
1588 .to_bytes()
1589 .unwrap();
1590 assert!(frame.len() < 64);
1591 assert!(P9Message::from_bytes_ctx(&frame, false).is_err());
1592 }
1593
1594 fn qid() -> Qid {
1595 Qid {
1596 type_: 0x80,
1597 version: 7,
1598 path: 42,
1599 }
1600 }
1601
1602 fn stat() -> Stat {
1603 Stat {
1604 qid: qid(),
1605 mode: 0o755,
1606 uid: 1000,
1607 gid: 1000,
1608 nlink: 2,
1609 rdev: 0,
1610 size: 4096,
1611 blksize: 32768,
1612 blocks: 8,
1613 atime_sec: 1,
1614 atime_nsec: 2,
1615 mtime_sec: 3,
1616 mtime_nsec: 4,
1617 ctime_sec: 5,
1618 ctime_nsec: 6,
1619 btime_sec: 7,
1620 btime_nsec: 8,
1621 r#gen: 9,
1622 data_version: 10,
1623 }
1624 }
1625
1626 #[test]
1627 fn wire_size_matches_serialization() {
1628 for name in [&b""[..], b"a", "h\u{e9}llo-\u{4e16}\u{754c}.txt".as_bytes()] {
1629 let entry = DirEntry {
1630 qid: qid(),
1631 offset: 99,
1632 type_: 4,
1633 name: P9String::new(name.to_vec()),
1634 };
1635 assert_eq!(entry.wire_size(), entry.to_bytes().unwrap().len());
1636
1637 let plus = DirEntryPlus {
1638 qid: qid(),
1639 offset: 99,
1640 type_: 4,
1641 name: P9String::new(name.to_vec()),
1642 stat: stat(),
1643 };
1644 assert_eq!(plus.wire_size(), plus.to_bytes().unwrap().len());
1645 }
1646 }
1647}