1use crate::format::bytes::read_le_uint as read_uint;
15use crate::format::{FormatContext, FormatError, FormatResult};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct SharedMessageTableMessage {
24 pub version: u8,
26 pub table_address: u64,
28 pub nindexes: u8,
30}
31
32impl SharedMessageTableMessage {
33 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<Self> {
35 let sa = ctx.sizeof_addr as usize;
36 need(buf, 1 + sa + 1)?;
37 let version = buf[0];
38 if version != 0 {
39 return Err(FormatError::InvalidVersion(version));
40 }
41 Ok(Self {
42 version,
43 table_address: read_uint(&buf[1..], sa),
44 nindexes: buf[1 + sa],
45 })
46 }
47
48 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
50 let sa = ctx.sizeof_addr as usize;
51 let mut buf = Vec::with_capacity(1 + sa + 1);
52 buf.push(self.version);
53 buf.extend_from_slice(&self.table_address.to_le_bytes()[..sa]);
54 buf.push(self.nindexes);
55 buf
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct BtreeKMessage {
66 pub chunk_internal_k: u16,
68 pub snode_internal_k: u16,
70 pub sym_leaf_k: u16,
72}
73
74impl BtreeKMessage {
75 pub fn decode(buf: &[u8]) -> FormatResult<Self> {
77 need(buf, 1 + 6)?;
78 let version = buf[0];
79 if version != 0 {
80 return Err(FormatError::InvalidVersion(version));
81 }
82 Ok(Self {
83 chunk_internal_k: u16::from_le_bytes([buf[1], buf[2]]),
84 snode_internal_k: u16::from_le_bytes([buf[3], buf[4]]),
85 sym_leaf_k: u16::from_le_bytes([buf[5], buf[6]]),
86 })
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct DriverInfoMessage {
99 pub name: String,
102 pub data: Vec<u8>,
104}
105
106impl DriverInfoMessage {
107 pub fn decode(buf: &[u8]) -> FormatResult<Self> {
109 need(buf, 1 + 8 + 2)?;
110 let version = buf[0];
111 if version != 0 {
112 return Err(FormatError::InvalidVersion(version));
113 }
114 let name_bytes: Vec<u8> = buf[1..9].iter().copied().take_while(|&b| b != 0).collect();
115 let name = String::from_utf8_lossy(&name_bytes).into_owned();
116 let len = u16::from_le_bytes([buf[9], buf[10]]) as usize;
117 if len == 0 {
118 return Err(FormatError::InvalidData(
119 "driver info message declares a zero-length payload".into(),
120 ));
121 }
122 need(buf, 11 + len)?;
123 Ok(Self {
124 name,
125 data: buf[11..11 + len].to_vec(),
126 })
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum FileSpaceStrategy {
134 FsmAggr,
136 Page,
138 Aggr,
140 None,
142 Unknown(u8),
144}
145
146impl FileSpaceStrategy {
147 fn from_byte(b: u8) -> Self {
148 match b {
152 0 => Self::FsmAggr,
153 1 => Self::Page,
154 2 => Self::Aggr,
155 3 => Self::None,
156 other => Self::Unknown(other),
157 }
158 }
159
160 fn to_byte(self) -> u8 {
163 match self {
164 Self::FsmAggr => 0,
165 Self::Page => 1,
166 Self::Aggr => 2,
167 Self::None => 3,
168 Self::Unknown(b) => b,
169 }
170 }
171}
172
173pub const FS_ADDR_COUNT_V1: usize = 12;
176
177const FS_ADDR_COUNT_V0: usize = 6;
181
182pub const PAGE_SIZE_MAX: u64 = 1024 * 1024 * 1024;
186
187pub const PAGE_SIZE_MIN: u64 = 512;
192
193pub(crate) const DEFAULT_FILE_SPACE_PAGE_SIZE: u64 = 4096;
197
198#[derive(Debug, Clone, PartialEq, Eq)]
214pub struct FileSpaceInfoMessage {
215 pub version: u8,
220 pub strategy: FileSpaceStrategy,
222 pub persist: bool,
224 pub threshold: u64,
226 pub page_size: u64,
228 pub pgend_meta_thres: u16,
230 pub eoa_pre_fsm_fsalloc: u64,
232 pub fs_addr: Vec<u64>,
243}
244
245impl FileSpaceInfoMessage {
246 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<Self> {
248 let sa = ctx.sizeof_addr as usize;
249 let ss = ctx.sizeof_size as usize;
250 need(buf, 1)?;
251 let version = buf[0];
252 let mut pos = 1;
253
254 if version == 0 {
255 need(buf, pos + 1 + ss)?;
258 let legacy_strategy = buf[pos];
259 pos += 1;
260 let threshold = read_uint(&buf[pos..], ss);
261 pos += ss;
262
263 let (strategy, persist, threshold) = match legacy_strategy {
266 1 => (FileSpaceStrategy::FsmAggr, true, threshold),
267 2 => (FileSpaceStrategy::FsmAggr, false, threshold),
268 3 => (FileSpaceStrategy::Aggr, false, 1),
269 4 => (FileSpaceStrategy::None, false, 1),
270 other => {
271 return Err(FormatError::InvalidData(format!(
272 "invalid file-space strategy {other} in version-0 file-space info message"
273 )))
274 }
275 };
276
277 let mut fs_addr = vec![crate::format::UNDEF_ADDR; FS_ADDR_COUNT_V1];
280 if persist {
281 need(buf, pos + FS_ADDR_COUNT_V0 * sa)?;
282 for slot in fs_addr.iter_mut().take(FS_ADDR_COUNT_V0) {
283 *slot = read_uint(&buf[pos..], sa);
284 pos += sa;
285 }
286 }
287
288 return Ok(Self {
289 version: 0,
290 strategy,
291 persist,
292 threshold,
293 page_size: DEFAULT_FILE_SPACE_PAGE_SIZE,
301 pgend_meta_thres: 0,
302 eoa_pre_fsm_fsalloc: crate::format::UNDEF_ADDR,
303 fs_addr,
304 });
305 }
306
307 if version != 1 {
308 return Err(FormatError::InvalidVersion(version));
309 }
310
311 need(buf, pos + 2 + ss + ss + 2 + sa)?;
312 let strategy = FileSpaceStrategy::from_byte(buf[pos]);
313 pos += 1;
314 let persist = buf[pos] != 0;
315 pos += 1;
316 let threshold = read_uint(&buf[pos..], ss);
317 pos += ss;
318 let page_size = read_uint(&buf[pos..], ss);
319 pos += ss;
320 if page_size == 0 || page_size > PAGE_SIZE_MAX {
321 return Err(FormatError::InvalidData(format!(
322 "invalid file-space page size {page_size}"
323 )));
324 }
325 let pgend_meta_thres = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
326 pos += 2;
327 let eoa_pre_fsm_fsalloc = read_uint(&buf[pos..], sa);
328 pos += sa;
329
330 let mut fs_addr = vec![crate::format::UNDEF_ADDR; FS_ADDR_COUNT_V1];
331 if persist {
332 need(buf, pos + FS_ADDR_COUNT_V1 * sa)?;
333 for slot in fs_addr.iter_mut() {
334 *slot = read_uint(&buf[pos..], sa);
335 pos += sa;
336 }
337 }
338
339 Ok(Self {
340 version: 1,
341 strategy,
342 persist,
343 threshold,
344 page_size,
345 pgend_meta_thres,
346 eoa_pre_fsm_fsalloc,
347 fs_addr,
348 })
349 }
350 pub fn encode(&self, ctx: &FormatContext) -> FormatResult<Vec<u8>> {
357 if self.version == 0 {
358 return self.encode_v0(ctx);
359 }
360 let sa = ctx.sizeof_addr as usize;
361 let ss = ctx.sizeof_size as usize;
362 let mut buf = Vec::with_capacity(3 + 2 * ss + 2 + sa + FS_ADDR_COUNT_V1 * sa);
363 buf.push(1);
364 buf.push(self.strategy.to_byte());
365 buf.push(self.persist as u8);
366 buf.extend_from_slice(&self.threshold.to_le_bytes()[..ss]);
367 buf.extend_from_slice(&self.page_size.to_le_bytes()[..ss]);
368 buf.extend_from_slice(&self.pgend_meta_thres.to_le_bytes());
369 buf.extend_from_slice(&self.eoa_pre_fsm_fsalloc.to_le_bytes()[..sa]);
370 if self.persist {
371 for addr in &self.fs_addr {
372 buf.extend_from_slice(&addr.to_le_bytes()[..sa]);
373 }
374 }
375 Ok(buf)
376 }
377
378 pub fn encode_v0(&self, ctx: &FormatContext) -> FormatResult<Vec<u8>> {
402 let sa = ctx.sizeof_addr as usize;
403 let ss = ctx.sizeof_size as usize;
404 let legacy = match (self.strategy, self.persist) {
406 (FileSpaceStrategy::FsmAggr, true) => 1,
407 (FileSpaceStrategy::FsmAggr, false) => 2,
408 (FileSpaceStrategy::Aggr, false) => 3,
409 (FileSpaceStrategy::None, false) => 4,
410 (strategy, persist) => {
411 return Err(FormatError::InvalidData(format!(
412 "strategy {strategy:?} with persist {persist} has no version-0 \
413 file-space info encoding"
414 )))
415 }
416 };
417 if legacy > 2 && self.threshold != 1 {
418 return Err(FormatError::InvalidData(format!(
419 "version-0 file-space strategy {legacy} cannot carry threshold {}",
420 self.threshold
421 )));
422 }
423 if self.fs_addr.len() > FS_ADDR_COUNT_V0
424 && self.fs_addr[FS_ADDR_COUNT_V0..]
425 .iter()
426 .any(|&a| a != crate::format::UNDEF_ADDR)
427 {
428 return Err(FormatError::InvalidData(
429 "a version-0 file-space info message has no slot for a page-type \
430 free-space manager"
431 .into(),
432 ));
433 }
434
435 let mut buf = Vec::with_capacity(2 + ss + FS_ADDR_COUNT_V0 * sa);
436 buf.push(0);
437 buf.push(legacy);
438 buf.extend_from_slice(&self.threshold.to_le_bytes()[..ss]);
439 if self.persist {
440 let undef = [crate::format::UNDEF_ADDR];
441 for slot in 0..FS_ADDR_COUNT_V0 {
442 let addr = self.fs_addr.get(slot).unwrap_or(&undef[0]);
443 buf.extend_from_slice(&addr.to_le_bytes()[..sa]);
444 }
445 }
446 Ok(buf)
447 }
448}
449
450fn need(buf: &[u8], n: usize) -> FormatResult<()> {
451 if buf.len() < n {
452 Err(FormatError::BufferTooShort {
453 needed: n,
454 available: buf.len(),
455 })
456 } else {
457 Ok(())
458 }
459}
460
461#[cfg(test)]
464mod tests {
465 use super::*;
466
467 fn ctx() -> FormatContext {
468 FormatContext::default_v3()
469 }
470
471 #[test]
472 fn shmesg_roundtrip() {
473 let mut buf = vec![0u8];
474 buf.extend_from_slice(&88u64.to_le_bytes());
475 buf.push(3);
476 let m = SharedMessageTableMessage::decode(&buf, &ctx()).unwrap();
477 assert_eq!(m.table_address, 88);
478 assert_eq!(m.nindexes, 3);
479 }
480
481 #[test]
484 fn shmesg_encodes_the_fixture_body() {
485 let m = SharedMessageTableMessage {
486 version: 0,
487 table_address: 88,
488 nindexes: 1,
489 };
490 assert_eq!(
491 m.encode(&ctx()),
492 vec![0, 0x58, 0, 0, 0, 0, 0, 0, 0, 1],
493 "H5O__shmesg_encode order: version, table address, index count"
494 );
495 assert_eq!(
496 SharedMessageTableMessage::decode(&m.encode(&ctx()), &ctx()).unwrap(),
497 m
498 );
499 }
500
501 #[test]
502 fn shmesg_rejects_bad_version() {
503 let mut buf = vec![9u8];
504 buf.extend_from_slice(&88u64.to_le_bytes());
505 buf.push(1);
506 assert!(matches!(
507 SharedMessageTableMessage::decode(&buf, &ctx()).unwrap_err(),
508 FormatError::InvalidVersion(9)
509 ));
510 }
511
512 #[test]
513 fn shmesg_rejects_short_buffer() {
514 assert!(matches!(
515 SharedMessageTableMessage::decode(&[0u8; 4], &ctx()).unwrap_err(),
516 FormatError::BufferTooShort { .. }
517 ));
518 }
519
520 #[test]
521 fn btreek_field_order_matches_upstream() {
522 let buf = [0u8, 0x40, 0x00, 0x20, 0x00, 0x08, 0x00];
524 let m = BtreeKMessage::decode(&buf).unwrap();
525 assert_eq!(m.chunk_internal_k, 64);
526 assert_eq!(m.snode_internal_k, 32);
527 assert_eq!(m.sym_leaf_k, 8);
528 }
529
530 #[test]
531 fn btreek_rejects_bad_version() {
532 let buf = [1u8, 0, 0, 0, 0, 0, 0];
533 assert!(matches!(
534 BtreeKMessage::decode(&buf).unwrap_err(),
535 FormatError::InvalidVersion(1)
536 ));
537 }
538
539 #[test]
540 fn drvinfo_decodes_name_and_payload() {
541 let mut buf = vec![0u8];
542 buf.extend_from_slice(b"NCSAmult");
543 buf.extend_from_slice(&4u16.to_le_bytes());
544 buf.extend_from_slice(&[1, 2, 3, 4]);
545 let m = DriverInfoMessage::decode(&buf).unwrap();
546 assert_eq!(m.name, "NCSAmult");
547 assert_eq!(m.data, vec![1, 2, 3, 4]);
548 }
549
550 #[test]
551 fn drvinfo_rejects_zero_length() {
552 let mut buf = vec![0u8];
553 buf.extend_from_slice(b"NCSAfami");
554 buf.extend_from_slice(&0u16.to_le_bytes());
555 assert!(matches!(
556 DriverInfoMessage::decode(&buf).unwrap_err(),
557 FormatError::InvalidData(_)
558 ));
559 }
560
561 fn fsinfo_v1(persist: bool) -> Vec<u8> {
562 let mut buf = vec![1u8, 1u8, persist as u8];
564 buf.extend_from_slice(&1u64.to_le_bytes()); buf.extend_from_slice(&4096u64.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0x1000u64.to_le_bytes()); if persist {
569 for i in 0..FS_ADDR_COUNT_V1 {
570 buf.extend_from_slice(&((0x2000 + i as u64) * 8).to_le_bytes());
571 }
572 }
573 buf
574 }
575
576 #[test]
577 fn fsinfo_v1_paged_non_persisting() {
578 let m = FileSpaceInfoMessage::decode(&fsinfo_v1(false), &ctx()).unwrap();
579 assert_eq!(m.strategy, FileSpaceStrategy::Page);
580 assert!(!m.persist);
581 assert_eq!(m.page_size, 4096);
582 assert!(m.fs_addr.iter().all(|&a| a == crate::format::UNDEF_ADDR));
583 }
584
585 #[test]
586 fn fsinfo_v1_persisting_reads_twelve_addresses() {
587 let m = FileSpaceInfoMessage::decode(&fsinfo_v1(true), &ctx()).unwrap();
588 assert!(m.persist);
589 assert_eq!(m.fs_addr.len(), FS_ADDR_COUNT_V1);
590 assert_eq!(m.fs_addr[0], 0x2000 * 8);
591 assert_eq!(m.eoa_pre_fsm_fsalloc, 0x1000);
592 }
593
594 #[test]
595 fn fsinfo_v1_rejects_absurd_page_size() {
596 let mut buf = fsinfo_v1(false);
597 buf[11..19].copy_from_slice(&(PAGE_SIZE_MAX + 1).to_le_bytes());
598 assert!(matches!(
599 FileSpaceInfoMessage::decode(&buf, &ctx()).unwrap_err(),
600 FormatError::InvalidData(_)
601 ));
602 }
603
604 #[test]
605 fn fsinfo_v0_all_persist_maps_onto_the_version_one_fields() {
606 let mut buf = vec![0u8, 1u8];
607 buf.extend_from_slice(&7u64.to_le_bytes()); for i in 0..FS_ADDR_COUNT_V0 {
609 buf.extend_from_slice(&(0x100u64 + i as u64).to_le_bytes());
610 }
611 let m = FileSpaceInfoMessage::decode(&buf, &ctx()).unwrap();
612 assert_eq!(m.version, 0);
613 assert_eq!(m.strategy, FileSpaceStrategy::FsmAggr);
614 assert!(m.persist);
615 assert_eq!(m.threshold, 7);
616 assert_eq!(m.fs_addr.len(), FS_ADDR_COUNT_V1);
619 assert_eq!(m.fs_addr[5], 0x105);
620 assert!(m.fs_addr[FS_ADDR_COUNT_V0..]
621 .iter()
622 .all(|&a| a == crate::format::UNDEF_ADDR));
623 }
624
625 #[test]
626 fn fsinfo_v0_vfd_maps_to_none() {
627 let mut buf = vec![0u8, 4u8];
628 buf.extend_from_slice(&0u64.to_le_bytes());
629 let m = FileSpaceInfoMessage::decode(&buf, &ctx()).unwrap();
630 assert_eq!(m.strategy, FileSpaceStrategy::None);
631 assert!(!m.persist);
632 assert!(m.fs_addr.iter().all(|&a| a == crate::format::UNDEF_ADDR));
633 }
634
635 fn fsinfo_v0(legacy: u8, threshold: u64) -> Vec<u8> {
641 let mut buf = vec![0u8, legacy];
642 buf.extend_from_slice(&threshold.to_le_bytes());
643 if legacy == 1 {
644 for i in 0..FS_ADDR_COUNT_V0 {
645 buf.extend_from_slice(&(0x400u64 + 0x40 * i as u64).to_le_bytes());
646 }
647 }
648 buf
649 }
650
651 #[test]
652 fn fsinfo_v0_round_trips_as_version_zero() {
653 for (legacy, threshold) in [(1u8, 9u64), (2, 9), (3, 1), (4, 1)] {
654 let bytes = fsinfo_v0(legacy, threshold);
655 let m = FileSpaceInfoMessage::decode(&bytes, &ctx()).unwrap();
656 assert_eq!(m.version, 0, "strategy {legacy}");
657 assert_eq!(
658 m.encode(&ctx()).unwrap(),
659 bytes,
660 "strategy {legacy} did not re-emit the body it was read from"
661 );
662 }
663 }
664
665 #[test]
666 fn a_version_zero_message_re_emits_a_moved_manager() {
667 let mut m = FileSpaceInfoMessage::decode(&fsinfo_v0(1, 9), &ctx()).unwrap();
670 m.fs_addr[0] = 0x2000;
671 m.fs_addr[2] = 0x3000;
672 m.eoa_pre_fsm_fsalloc = 0x9000;
673 let bytes = m.encode(&ctx()).unwrap();
674 assert_eq!(bytes.len(), fsinfo_v0(1, 9).len());
675 let again = FileSpaceInfoMessage::decode(&bytes, &ctx()).unwrap();
676 assert_eq!(again.fs_addr[0], 0x2000);
677 assert_eq!(again.fs_addr[2], 0x3000);
678 assert_eq!(again.eoa_pre_fsm_fsalloc, crate::format::UNDEF_ADDR);
681 assert_eq!(
682 FileSpaceInfoMessage {
683 eoa_pre_fsm_fsalloc: m.eoa_pre_fsm_fsalloc,
684 ..again
685 },
686 m
687 );
688 }
689
690 #[test]
691 fn fsinfo_v0_refuses_what_it_cannot_encode() {
692 let paged = FileSpaceInfoMessage::decode(&fsinfo_v1(true), &ctx()).unwrap();
693 assert!(matches!(
694 paged.encode_v0(&ctx()).unwrap_err(),
695 FormatError::InvalidData(_)
696 ));
697
698 let mut aggr = FileSpaceInfoMessage::decode(&fsinfo_v0(3, 1), &ctx()).unwrap();
699 aggr.threshold = 64;
700 assert!(matches!(
701 aggr.encode(&ctx()).unwrap_err(),
702 FormatError::InvalidData(_)
703 ));
704
705 let mut paged_manager = FileSpaceInfoMessage::decode(&fsinfo_v0(1, 9), &ctx()).unwrap();
706 paged_manager.fs_addr[FS_ADDR_COUNT_V0] = 0x800;
707 assert!(matches!(
708 paged_manager.encode(&ctx()).unwrap_err(),
709 FormatError::InvalidData(_)
710 ));
711 }
712
713 #[test]
714 fn fsinfo_v1_round_trips() {
715 for persist in [false, true] {
716 let bytes = fsinfo_v1(persist);
717 let m = FileSpaceInfoMessage::decode(&bytes, &ctx()).unwrap();
718 assert_eq!(m.encode(&ctx()).unwrap(), bytes);
719 }
720 }
721
722 #[test]
723 fn fsinfo_rejects_unknown_version() {
724 let buf = vec![9u8; 40];
725 assert!(matches!(
726 FileSpaceInfoMessage::decode(&buf, &ctx()).unwrap_err(),
727 FormatError::InvalidVersion(9)
728 ));
729 }
730}