1use crate::format::checksum::checksum_metadata;
33use crate::format::creation_order::CreationOrder;
34use crate::format::{FormatContext, FormatError, FormatResult, ObjectFormat};
35
36pub const OHDR_SIGNATURE: [u8; 4] = *b"OHDR";
38
39pub const OCHK_SIGNATURE: [u8; 4] = *b"OCHK";
41
42const MSG_NIL: u8 = 0x00;
45
46const MSG_CONTINUATION: u8 = 0x10;
48
49pub const OHDR_VERSION: u8 = 2;
51
52pub const MAX_MESSAGE_SIZE: usize = u16::MAX as usize;
59
60const FLAG_SIZE_MASK: u8 = 0x03;
62const FLAG_ATTR_CREATION_ORDER_TRACKED: u8 = 0x04;
63const FLAG_ATTR_CREATION_ORDER_INDEXED: u8 = 0x08;
64const FLAG_NON_DEFAULT_ATTR_THRESHOLDS: u8 = 0x10;
65const FLAG_STORE_TIMESTAMPS: u8 = 0x20;
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct ObjectHeaderMessage {
70 pub msg_type: u8,
72 pub flags: u8,
74 pub creation_index: u16,
79 pub data: Vec<u8>,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub struct ObjectTimes {
93 pub access: u32,
95 pub modification: u32,
97 pub change: u32,
99 pub birth: u32,
101}
102
103impl ObjectTimes {
104 pub fn created_at(now: u32) -> Self {
107 Self {
108 access: now,
109 modification: now,
110 change: now,
111 birth: now,
112 }
113 }
114
115 pub fn touched(self, now: u32) -> Self {
122 Self {
123 access: now,
124 change: now,
125 ..self
126 }
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub struct ChunkPlan {
135 split: usize,
138 pub chunk0_size: usize,
140 pub continuation_size: usize,
143 flags: u8,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct ObjectHeader {
154 pub flags: u8,
166 pub times: Option<ObjectTimes>,
168 pub messages: Vec<ObjectHeaderMessage>,
170}
171
172impl ObjectHeader {
173 pub fn new() -> Self {
176 Self {
177 flags: 0,
178 times: None,
179 messages: Vec::new(),
180 }
181 }
182
183 pub fn recorded_times(&self) -> Option<ObjectTimes> {
200 if let Some(times) = self.times {
201 return Some(times);
202 }
203 self.messages
204 .iter()
205 .find(|m| m.msg_type == crate::format::messages::MSG_MOD_TIME)
206 .and_then(|m| crate::format::messages::mod_time::ModificationTime::decode(&m.data).ok())
207 .map(|t| ObjectTimes::created_at(t.0))
208 }
209
210 pub fn add_message(&mut self, msg_type: u8, flags: u8, data: Vec<u8>) {
212 self.add_message_indexed(msg_type, flags, data, 0);
213 }
214
215 pub fn add_message_indexed(
221 &mut self,
222 msg_type: u8,
223 flags: u8,
224 data: Vec<u8>,
225 creation_index: u16,
226 ) {
227 self.messages.push(ObjectHeaderMessage {
228 msg_type,
229 flags,
230 creation_index,
231 data,
232 });
233 }
234
235 pub fn set_attribute_creation_order(&mut self, order: CreationOrder) {
243 self.flags &= !(FLAG_ATTR_CREATION_ORDER_TRACKED | FLAG_ATTR_CREATION_ORDER_INDEXED);
244 if order.is_tracked() {
245 self.flags |= FLAG_ATTR_CREATION_ORDER_TRACKED;
246 }
247 if order.is_indexed() {
248 self.flags |= FLAG_ATTR_CREATION_ORDER_INDEXED;
249 }
250 }
251
252 pub fn attribute_creation_order(&self) -> CreationOrder {
258 CreationOrder::from_flags(
259 self.flags & FLAG_ATTR_CREATION_ORDER_TRACKED != 0,
260 self.flags & FLAG_ATTR_CREATION_ORDER_INDEXED != 0,
261 )
262 }
263
264 fn encoded_flags(&self, flags: u8) -> u8 {
270 let base = flags & !FLAG_STORE_TIMESTAMPS;
271 match self.times {
272 Some(_) => base | FLAG_STORE_TIMESTAMPS,
273 None => base,
274 }
275 }
276
277 fn flags_for_area(&self, area: usize) -> u8 {
283 let bits = match area as u64 {
284 0..=0xFF => 0,
285 0x100..=0xFFFF => 1,
286 0x1_0000..=0xFFFF_FFFF => 2,
287 _ => 3,
288 };
289 (self.flags & !FLAG_SIZE_MASK) | bits
290 }
291
292 pub fn has_creation_order(&self) -> bool {
294 self.flags & FLAG_ATTR_CREATION_ORDER_TRACKED != 0
295 }
296
297 pub fn message_envelope_size(&self) -> usize {
300 if self.has_creation_order() {
301 1 + 2 + 1 + 2 } else {
303 1 + 2 + 1 }
305 }
306
307 fn messages_size(&self, messages: &[ObjectHeaderMessage]) -> usize {
309 messages
310 .iter()
311 .map(|m| self.message_envelope_size() + m.data.len())
312 .sum()
313 }
314
315 fn messages_data_size(&self) -> usize {
317 self.messages_size(&self.messages)
318 }
319
320 fn prefix_size_under(&self, flags: u8) -> usize {
324 let mut size = 4 + 1 + 1; if self.times.is_some() {
326 size += 16; }
328 if flags & FLAG_NON_DEFAULT_ATTR_THRESHOLDS != 0 {
329 size += 4; }
331 size + Self::size_field_width(flags)
332 }
333
334 fn size_field_width(flags: u8) -> usize {
336 match flags & FLAG_SIZE_MASK {
337 0 => 1,
338 1 => 2,
339 2 => 4,
340 3 => 8,
341 _ => unreachable!(),
342 }
343 }
344
345 fn check_message_sizes(messages: &[ObjectHeaderMessage]) -> FormatResult<()> {
353 for msg in messages {
354 if msg.data.len() > MAX_MESSAGE_SIZE {
355 return Err(FormatError::InvalidData(format!(
356 "object header message type 0x{:02X} is {} bytes, over the \
357 {MAX_MESSAGE_SIZE}-byte limit the message size field can express",
358 msg.msg_type,
359 msg.data.len()
360 )));
361 }
362 }
363 Ok(())
364 }
365
366 fn write_messages(
373 &self,
374 buf: &mut Vec<u8>,
375 messages: &[ObjectHeaderMessage],
376 data_size: usize,
377 ) {
378 let envelope = self.message_envelope_size();
379 let mut write = |msg_type: u8, flags: u8, creation_index: u16, data: &[u8]| {
380 buf.push(msg_type);
381 buf.extend_from_slice(&(data.len() as u16).to_le_bytes());
383 buf.push(flags);
384 if self.has_creation_order() {
385 buf.extend_from_slice(&creation_index.to_le_bytes());
386 }
387 buf.extend_from_slice(data);
388 };
389 for msg in messages {
390 write(msg.msg_type, msg.flags, msg.creation_index, &msg.data);
391 }
392 let spare = data_size - self.messages_size(messages);
393 if spare >= envelope {
394 write(MSG_NIL, 0x00, 0, &vec![0u8; spare - envelope]);
395 } else {
396 buf.extend(std::iter::repeat_n(0u8, spare));
397 }
398 }
399
400 pub fn encode(&self) -> FormatResult<Vec<u8>> {
406 let exact = self.messages_data_size();
407 self.encode_chunk0(&self.messages, exact, self.flags_for_area(exact))
408 }
409
410 fn encode_chunk0(
414 &self,
415 messages: &[ObjectHeaderMessage],
416 data_size: usize,
417 flags: u8,
418 ) -> FormatResult<Vec<u8>> {
419 Self::check_message_sizes(messages)?;
420 debug_assert!(data_size >= self.messages_size(messages));
421 let total = self.prefix_size_under(flags) + data_size + 4; let mut buf = Vec::with_capacity(total);
423
424 buf.extend_from_slice(&OHDR_SIGNATURE);
425 buf.push(OHDR_VERSION);
426 buf.push(self.encoded_flags(flags));
427
428 if let Some(t) = self.times {
430 for field in [t.access, t.modification, t.change, t.birth] {
431 buf.extend_from_slice(&field.to_le_bytes());
432 }
433 }
434
435 if flags & FLAG_NON_DEFAULT_ATTR_THRESHOLDS != 0 {
437 buf.extend_from_slice(&8u16.to_le_bytes());
439 buf.extend_from_slice(&6u16.to_le_bytes());
440 }
441
442 let csb = Self::size_field_width(flags);
443 buf.extend_from_slice(&(data_size as u64).to_le_bytes()[..csb]);
444
445 self.write_messages(&mut buf, messages, data_size);
446
447 let cksum = checksum_metadata(&buf);
449 buf.extend_from_slice(&cksum.to_le_bytes());
450
451 debug_assert_eq!(buf.len(), total);
452 Ok(buf)
453 }
454
455 fn encode_continuation(&self, messages: &[ObjectHeaderMessage]) -> FormatResult<Vec<u8>> {
460 Self::check_message_sizes(messages)?;
461 let data_size = self.messages_size(messages);
462 let mut buf = Vec::with_capacity(OCHK_SIGNATURE.len() + data_size + 4);
463 buf.extend_from_slice(&OCHK_SIGNATURE);
464 self.write_messages(&mut buf, messages, data_size);
465 let cksum = checksum_metadata(&buf);
466 buf.extend_from_slice(&cksum.to_le_bytes());
467 Ok(buf)
468 }
469
470 fn message_size_for(&self, format: ObjectFormat, msg: &ObjectHeaderMessage) -> usize {
475 match format {
476 ObjectFormat::Legacy => V1_MSG_HEADER_SIZE + align_old(msg.data.len()),
477 ObjectFormat::Modern => self.message_envelope_size() + msg.data.len(),
478 }
479 }
480
481 fn messages_size_for(&self, format: ObjectFormat, messages: &[ObjectHeaderMessage]) -> usize {
483 messages
484 .iter()
485 .map(|m| self.message_size_for(format, m))
486 .sum()
487 }
488
489 fn chunk0_overhead(&self, format: ObjectFormat, flags: u8) -> usize {
492 match format {
493 ObjectFormat::Legacy => V1_PREFIX_SIZE,
494 ObjectFormat::Modern => self.prefix_size_under(flags) + 4,
495 }
496 }
497
498 fn continuation_overhead(format: ObjectFormat) -> usize {
501 match format {
502 ObjectFormat::Legacy => 0,
503 ObjectFormat::Modern => OCHK_SIGNATURE.len() + 4,
504 }
505 }
506
507 fn continuation_message(ctx: &FormatContext, addr: u64, size: usize) -> ObjectHeaderMessage {
510 let sa = ctx.sizeof_addr as usize;
511 let ss = ctx.sizeof_size as usize;
512 let mut body = Vec::with_capacity(sa + ss);
513 body.extend_from_slice(&addr.to_le_bytes()[..sa]);
514 body.extend_from_slice(&(size as u64).to_le_bytes()[..ss]);
515 ObjectHeaderMessage {
516 msg_type: MSG_CONTINUATION,
517 flags: 0x00,
518 creation_index: 0,
519 data: body,
520 }
521 }
522
523 pub fn plan_chunks(
535 &self,
536 format: ObjectFormat,
537 capacity: usize,
538 ctx: &FormatContext,
539 ) -> FormatResult<ChunkPlan> {
540 self.plan_under(format, capacity, ctx, None)
541 }
542
543 fn plan_under(
547 &self,
548 format: ObjectFormat,
549 capacity: usize,
550 ctx: &FormatContext,
551 flags: Option<u8>,
552 ) -> FormatResult<ChunkPlan> {
553 let exact = self.messages_size_for(format, &self.messages);
554 if exact <= capacity {
555 let flags = flags.unwrap_or_else(|| self.flags_for_area(exact));
556 return Ok(ChunkPlan {
557 split: self.messages.len(),
558 chunk0_size: self.chunk0_overhead(format, flags) + exact,
559 continuation_size: 0,
560 flags,
561 });
562 }
563 let flags = flags.unwrap_or_else(|| self.flags_for_area(capacity));
564 let continuation_message =
567 self.message_size_for(format, &Self::continuation_message(ctx, 0, 0));
568 if capacity < continuation_message {
569 return Err(FormatError::InvalidData(format!(
570 "an object header chunk-0 capacity of {capacity} bytes cannot hold the \
571 {continuation_message}-byte message naming its continuation chunk"
572 )));
573 }
574 let mut used = continuation_message;
575 let mut split = 0;
576 for msg in &self.messages {
577 let size = self.message_size_for(format, msg);
578 if used + size > capacity {
579 break;
580 }
581 used += size;
582 split += 1;
583 }
584 let spilled = self.messages_size_for(format, &self.messages[split..]);
585 Ok(ChunkPlan {
586 split,
587 chunk0_size: self.chunk0_overhead(format, flags) + capacity,
588 continuation_size: Self::continuation_overhead(format) + spilled,
589 flags,
590 })
591 }
592
593 pub fn plan_chunks_in(
609 &self,
610 format: ObjectFormat,
611 block: usize,
612 ctx: &FormatContext,
613 ) -> FormatResult<ChunkPlan> {
614 let too_short = || {
615 FormatError::InvalidData(format!(
616 "a {block}-byte block cannot hold this object header's chunk 0 prefix"
617 ))
618 };
619 let (area, flags) = match format {
620 ObjectFormat::Legacy => {
621 let area = block.checked_sub(V1_PREFIX_SIZE).ok_or_else(too_short)?;
622 if area % 8 != 0 {
623 return Err(FormatError::InvalidData(format!(
624 "a version-1 object header cannot hold a {area}-byte message area: \
625 version 1 aligns every message to eight bytes"
626 )));
627 }
628 (area, self.flags)
629 }
630 ObjectFormat::Modern => {
631 let mut fit = None;
632 for bits in 0..=3u8 {
633 let flags = (self.flags & !FLAG_SIZE_MASK) | bits;
634 let Some(area) = block.checked_sub(self.chunk0_overhead(format, flags)) else {
635 break;
636 };
637 if area as u64 <= u64::MAX >> (64 - 8 * Self::size_field_width(flags)) {
638 fit = Some((area, flags));
639 break;
640 }
641 }
642 fit.ok_or_else(too_short)?
643 }
644 };
645 let mut plan = self.plan_under(format, area, ctx, Some(flags))?;
646 if plan.continuation_size == 0 {
647 plan.chunk0_size = block;
648 }
649 Ok(plan)
650 }
651
652 pub fn encode_chunked(
660 &self,
661 plan: &ChunkPlan,
662 format: ObjectFormat,
663 ctx: &FormatContext,
664 continuation_addr: u64,
665 nlink: u32,
666 ) -> FormatResult<(Vec<u8>, Option<Vec<u8>>)> {
667 let area = plan.chunk0_size - self.chunk0_overhead(format, plan.flags);
668 if plan.continuation_size == 0 {
669 let chunk0 = match format {
670 ObjectFormat::Legacy => self.encode_v1_chunk0(&self.messages, area, nlink, 0)?,
671 ObjectFormat::Modern => self.encode_chunk0(&self.messages, area, plan.flags)?,
672 };
673 return Ok((chunk0, None));
674 }
675 let mut chunk0 = self.messages[..plan.split].to_vec();
676 chunk0.push(Self::continuation_message(
677 ctx,
678 continuation_addr,
679 plan.continuation_size,
680 ));
681 let spilled = &self.messages[plan.split..];
682 let (chunk0, continuation) = match format {
683 ObjectFormat::Legacy => (
684 self.encode_v1_chunk0(&chunk0, area, nlink, spilled.len())?,
685 self.encode_v1_continuation(spilled)?,
686 ),
687 ObjectFormat::Modern => (
688 self.encode_chunk0(&chunk0, area, plan.flags)?,
689 self.encode_continuation(spilled)?,
690 ),
691 };
692 debug_assert_eq!(continuation.len(), plan.continuation_size);
693 Ok((chunk0, Some(continuation)))
694 }
695
696 pub fn decode(buf: &[u8]) -> FormatResult<(Self, usize)> {
699 if buf.len() < 11 {
701 return Err(FormatError::BufferTooShort {
702 needed: 11,
703 available: buf.len(),
704 });
705 }
706
707 if buf[0..4] != OHDR_SIGNATURE {
709 return Err(FormatError::InvalidSignature);
710 }
711
712 let version = buf[4];
714 if version != OHDR_VERSION {
715 return Err(FormatError::InvalidVersion(version));
716 }
717
718 let flags = buf[5] & !(FLAG_STORE_TIMESTAMPS | FLAG_SIZE_MASK);
722 let mut pos: usize = 6;
723
724 let times = if buf[5] & FLAG_STORE_TIMESTAMPS != 0 {
726 if buf.len() < pos + 16 {
727 return Err(FormatError::BufferTooShort {
728 needed: pos + 16,
729 available: buf.len(),
730 });
731 }
732 let read = |off: usize| {
733 u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]])
734 };
735 let t = ObjectTimes {
736 access: read(pos),
737 modification: read(pos + 4),
738 change: read(pos + 8),
739 birth: read(pos + 12),
740 };
741 pos += 16;
742 Some(t)
743 } else {
744 None
745 };
746
747 if flags & FLAG_NON_DEFAULT_ATTR_THRESHOLDS != 0 {
749 if buf.len() < pos + 4 {
750 return Err(FormatError::BufferTooShort {
751 needed: pos + 4,
752 available: buf.len(),
753 });
754 }
755 pos += 4;
757 }
758
759 let chunk0_size_bytes = Self::size_field_width(buf[5]);
761
762 if buf.len() < pos + chunk0_size_bytes {
763 return Err(FormatError::BufferTooShort {
764 needed: pos + chunk0_size_bytes,
765 available: buf.len(),
766 });
767 }
768
769 let chunk0_data_size =
770 crate::format::bytes::read_le_uint(&buf[pos..], chunk0_size_bytes) as usize;
771 pos += chunk0_size_bytes;
772
773 let total_consumed = pos
778 .checked_add(chunk0_data_size)
779 .and_then(|x| x.checked_add(4))
780 .ok_or_else(|| {
781 FormatError::InvalidData("object header chunk-0 size overflows usize".into())
782 })?;
783 if buf.len() < total_consumed {
784 return Err(FormatError::BufferTooShort {
785 needed: total_consumed,
786 available: buf.len(),
787 });
788 }
789
790 let data_end = total_consumed - 4;
793 let stored_cksum = u32::from_le_bytes([
794 buf[data_end],
795 buf[data_end + 1],
796 buf[data_end + 2],
797 buf[data_end + 3],
798 ]);
799 let computed_cksum = checksum_metadata(&buf[..data_end]);
800 if stored_cksum != computed_cksum {
801 return Err(FormatError::ChecksumMismatch {
802 expected: stored_cksum,
803 computed: computed_cksum,
804 });
805 }
806
807 let has_creation_order = flags & FLAG_ATTR_CREATION_ORDER_TRACKED != 0;
809 let messages_end = pos + chunk0_data_size;
810 let mut messages = Vec::new();
811
812 while pos < messages_end {
813 let msg_header_size = if has_creation_order { 6 } else { 4 };
815 if pos + msg_header_size > messages_end {
816 break;
820 }
821
822 let msg_type = buf[pos];
823 let msg_data_size = u16::from_le_bytes([buf[pos + 1], buf[pos + 2]]) as usize;
824 let msg_flags = buf[pos + 3];
825 pos += 4;
826
827 let creation_index = if has_creation_order {
828 let v = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
829 pos += 2;
830 v
831 } else {
832 0
833 };
834
835 if pos + msg_data_size > messages_end {
836 return Err(FormatError::InvalidData(format!(
837 "message data ({} bytes) extends past chunk0 boundary",
838 msg_data_size
839 )));
840 }
841
842 let data = buf[pos..pos + msg_data_size].to_vec();
843 pos += msg_data_size;
844
845 messages.push(ObjectHeaderMessage {
846 msg_type,
847 flags: msg_flags,
848 creation_index,
849 data,
850 });
851 }
852
853 Ok((
854 ObjectHeader {
855 flags,
856 times,
857 messages,
858 },
859 total_consumed,
860 ))
861 }
862}
863
864impl Default for ObjectHeader {
865 fn default() -> Self {
866 Self::new()
867 }
868}
869
870fn align_old(n: usize) -> usize {
877 (n + 7) & !7
878}
879
880const V1_PREFIX_SIZE: usize = 16;
883
884const V1_MSG_HEADER_SIZE: usize = 8;
887
888impl ObjectHeader {
889 fn check_v1_encodable(&self) -> FormatResult<()> {
903 if self.flags & (FLAG_ATTR_CREATION_ORDER_TRACKED | FLAG_ATTR_CREATION_ORDER_INDEXED) != 0 {
904 return Err(FormatError::InvalidData(
905 "a version-1 object header cannot record attribute creation order: \
906 the tracking flags exist only in the version-2 header prefix"
907 .into(),
908 ));
909 }
910 if self.times.is_some() {
911 return Err(FormatError::InvalidData(
912 "a version-1 object header cannot store access/modification/change/birth \
913 times: they are version-2 prefix fields, and version 1 carries only a \
914 modification time, as an H5O_MTIME_NEW message"
915 .into(),
916 ));
917 }
918 Ok(())
919 }
920
921 fn check_v1_message_sizes(messages: &[ObjectHeaderMessage]) -> FormatResult<()> {
925 for msg in messages {
926 let padded = align_old(msg.data.len());
927 if padded > MAX_MESSAGE_SIZE {
928 return Err(FormatError::InvalidData(format!(
929 "object header message type 0x{:02X} is {} bytes, {padded} once \
930 aligned to 8, over the {MAX_MESSAGE_SIZE}-byte limit the message \
931 size field can express",
932 msg.msg_type,
933 msg.data.len()
934 )));
935 }
936 }
937 Ok(())
938 }
939
940 fn write_messages_v1(buf: &mut Vec<u8>, messages: &[ObjectHeaderMessage]) {
946 for msg in messages {
947 let padded = align_old(msg.data.len());
948 buf.extend_from_slice(&u16::from(msg.msg_type).to_le_bytes());
949 buf.extend_from_slice(&(padded as u16).to_le_bytes());
950 buf.push(msg.flags);
951 buf.extend_from_slice(&[0u8; 3]); buf.extend_from_slice(&msg.data);
953 buf.resize(buf.len() + (padded - msg.data.len()), 0);
954 }
955 }
956
957 fn encode_v1_chunk0(
968 &self,
969 messages: &[ObjectHeaderMessage],
970 data_size: usize,
971 nlink: u32,
972 spilled: usize,
973 ) -> FormatResult<Vec<u8>> {
974 self.check_v1_encodable()?;
975 Self::check_v1_message_sizes(messages)?;
976 let used = self.messages_size_for(ObjectFormat::Legacy, messages);
977 debug_assert!(data_size >= used);
978 let spare = data_size - used;
979 debug_assert_eq!(spare % 8, 0);
982 let padded = spare >= V1_MSG_HEADER_SIZE;
983 let Ok(chunk0_size) = u32::try_from(data_size) else {
984 return Err(FormatError::InvalidData(format!(
985 "version-1 object header chunk 0 is {data_size} bytes, over the 4-byte \
986 size field's range"
987 )));
988 };
989 let total_messages = messages.len() + usize::from(padded) + spilled;
990 let Ok(nmesgs) = u16::try_from(total_messages) else {
991 return Err(FormatError::InvalidData(format!(
992 "version-1 object header holds {total_messages} messages, over the 2-byte \
993 count field's range"
994 )));
995 };
996
997 let total = V1_PREFIX_SIZE + data_size;
998 let mut buf = Vec::with_capacity(total);
999 buf.push(1); buf.push(0); buf.extend_from_slice(&nmesgs.to_le_bytes());
1002 buf.extend_from_slice(&nlink.to_le_bytes());
1003 buf.extend_from_slice(&chunk0_size.to_le_bytes());
1004 buf.extend_from_slice(&[0u8; 4]); Self::write_messages_v1(&mut buf, messages);
1007 if padded {
1008 buf.extend_from_slice(&u16::from(MSG_NIL).to_le_bytes());
1009 buf.extend_from_slice(&((spare - V1_MSG_HEADER_SIZE) as u16).to_le_bytes());
1010 buf.push(0);
1011 buf.extend_from_slice(&[0u8; 3]);
1012 buf.resize(total, 0);
1013 }
1014
1015 debug_assert_eq!(buf.len(), total);
1016 Ok(buf)
1017 }
1018
1019 fn encode_v1_continuation(&self, messages: &[ObjectHeaderMessage]) -> FormatResult<Vec<u8>> {
1023 Self::check_v1_message_sizes(messages)?;
1024 let mut buf = Vec::with_capacity(self.messages_size_for(ObjectFormat::Legacy, messages));
1025 Self::write_messages_v1(&mut buf, messages);
1026 Ok(buf)
1027 }
1028
1029 pub fn encode_v1(&self, nlink: u32) -> FormatResult<Vec<u8>> {
1039 let exact = self.messages_size_for(ObjectFormat::Legacy, &self.messages);
1040 self.encode_v1_chunk0(&self.messages, exact, nlink, 0)
1041 }
1042
1043 pub fn encode_for(&self, format: ObjectFormat, nlink: u32) -> FormatResult<Vec<u8>> {
1049 match format {
1050 ObjectFormat::Legacy => self.encode_v1(nlink),
1051 ObjectFormat::Modern => self.encode(),
1052 }
1053 }
1054}
1055
1056impl ObjectHeader {
1057 pub fn decode_v1(buf: &[u8]) -> FormatResult<(Self, usize)> {
1074 if buf.len() < 16 {
1077 return Err(FormatError::BufferTooShort {
1078 needed: 16,
1079 available: buf.len(),
1080 });
1081 }
1082
1083 let version = buf[0];
1084 if version != 1 {
1085 return Err(FormatError::InvalidVersion(version));
1086 }
1087
1088 let num_messages = u16::from_le_bytes([buf[2], buf[3]]) as usize;
1090 let _obj_ref_count = u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]);
1091 let header_data_size = u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]) as usize;
1092 let total_consumed = 16 + header_data_size;
1095 if buf.len() < total_consumed {
1096 return Err(FormatError::BufferTooShort {
1097 needed: total_consumed,
1098 available: buf.len(),
1099 });
1100 }
1101
1102 let msg_data_start = 16; let mut pos = msg_data_start;
1104 let messages_end = msg_data_start + header_data_size;
1105 let mut messages = Vec::with_capacity(num_messages);
1106
1107 for _ in 0..num_messages {
1108 if pos + 8 > messages_end {
1109 break; }
1111
1112 let msg_type = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
1113 let data_size = u16::from_le_bytes([buf[pos + 2], buf[pos + 3]]) as usize;
1114 let msg_flags = buf[pos + 4];
1115 pos += 8;
1117
1118 if pos + data_size > messages_end {
1119 return Err(FormatError::InvalidData(format!(
1120 "v1 message data ({} bytes) extends past header boundary",
1121 data_size
1122 )));
1123 }
1124
1125 let data = buf[pos..pos + data_size].to_vec();
1126 pos += data_size;
1127
1128 let rel = pos - msg_data_start;
1131 let aligned_rel = (rel + 7) & !7;
1132 let aligned_pos = msg_data_start + aligned_rel;
1133 if aligned_pos <= messages_end {
1134 pos = aligned_pos;
1135 }
1136
1137 if msg_type == 0 {
1139 continue;
1140 }
1141
1142 messages.push(ObjectHeaderMessage {
1143 msg_type: msg_type as u8,
1144 flags: msg_flags,
1145 creation_index: 0,
1147 data,
1148 });
1149 }
1150
1151 Ok((
1152 ObjectHeader {
1153 flags: 0,
1154 times: None,
1157 messages,
1158 },
1159 total_consumed,
1160 ))
1161 }
1162
1163 pub fn decode_any(buf: &[u8]) -> FormatResult<(Self, usize)> {
1167 if buf.len() >= 4 && buf[0..4] == OHDR_SIGNATURE {
1168 Self::decode(buf)
1169 } else if !buf.is_empty() && buf[0] == 1 {
1170 Self::decode_v1(buf)
1171 } else {
1172 Self::decode(buf)
1174 }
1175 }
1176}
1177
1178#[cfg(test)]
1179mod tests_v1 {
1180 use super::*;
1181
1182 fn build_v1_header(messages: &[(u16, u8, &[u8])]) -> Vec<u8> {
1184 let mut msg_data = Vec::new();
1185 for (msg_type, flags, data) in messages {
1186 msg_data.extend_from_slice(&msg_type.to_le_bytes());
1187 msg_data.extend_from_slice(&(data.len() as u16).to_le_bytes());
1188 msg_data.push(*flags);
1189 msg_data.extend_from_slice(&[0u8; 3]); msg_data.extend_from_slice(data);
1191 let aligned = (msg_data.len() + 7) & !7;
1193 msg_data.resize(aligned, 0);
1194 }
1195
1196 let mut buf = Vec::new();
1197 buf.push(1); buf.push(0); buf.extend_from_slice(&(messages.len() as u16).to_le_bytes());
1200 buf.extend_from_slice(&1u32.to_le_bytes()); buf.extend_from_slice(&(msg_data.len() as u32).to_le_bytes());
1202 buf.extend_from_slice(&[0u8; 4]); buf.extend_from_slice(&msg_data);
1204 buf
1205 }
1206
1207 #[test]
1208 fn test_decode_v1_empty() {
1209 let buf = build_v1_header(&[]);
1210 let (hdr, consumed) = ObjectHeader::decode_v1(&buf).unwrap();
1211 assert_eq!(consumed, 16); assert!(hdr.messages.is_empty());
1213 }
1214
1215 #[test]
1216 fn test_decode_v1_single_message() {
1217 let data = vec![0xAA, 0xBB, 0xCC];
1218 let buf = build_v1_header(&[(0x03, 0x00, &data)]);
1219 let (hdr, _consumed) = ObjectHeader::decode_v1(&buf).unwrap();
1220 assert_eq!(hdr.messages.len(), 1);
1221 assert_eq!(hdr.messages[0].msg_type, 0x03);
1222 assert_eq!(hdr.messages[0].data, data);
1223 }
1224
1225 #[test]
1226 fn test_decode_v1_multiple_messages() {
1227 let buf = build_v1_header(&[
1228 (0x01, 0x00, &[1, 2, 3, 4]),
1229 (0x03, 0x01, &[10, 20]),
1230 (0x08, 0x00, &[0xFF; 16]),
1231 ]);
1232 let (hdr, _) = ObjectHeader::decode_v1(&buf).unwrap();
1233 assert_eq!(hdr.messages.len(), 3);
1234 assert_eq!(hdr.messages[0].msg_type, 0x01);
1235 assert_eq!(hdr.messages[1].msg_type, 0x03);
1236 assert_eq!(hdr.messages[2].msg_type, 0x08);
1237 assert_eq!(hdr.messages[2].data, vec![0xFF; 16]);
1238 }
1239
1240 #[test]
1247 fn an_encoded_v1_header_matches_the_envelope_libhdf5_writes() {
1248 let dataspace = vec![
1249 0x01, 0x01, 0x01, 0x00, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0,
1250 ];
1251 let datatype = vec![0x10, 0x08, 0, 0, 0x04, 0, 0, 0, 0, 0, 0x20, 0, 0, 0, 0, 0];
1252 let fill = vec![0x02, 0x02, 0x02, 0x01, 0, 0, 0, 0];
1253 let layout = vec![
1255 0x03, 0x01, 0, 0x08, 0, 0, 0, 0, 0, 0, 0x18, 0, 0, 0, 0, 0, 0, 0,
1256 ];
1257 let mut header = ObjectHeader::new();
1258 header.add_message(0x01, 0x00, dataspace);
1259 header.add_message(0x03, 0x01, datatype);
1260 header.add_message(0x05, 0x01, fill);
1261 header.add_message(0x08, 0x00, layout);
1262
1263 let buf = header.encode_v1(1).unwrap();
1264 assert_eq!(buf[0], 1, "version");
1265 assert_eq!(u16::from_le_bytes([buf[2], buf[3]]), 4, "message count");
1266 assert_eq!(
1267 u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]),
1268 1,
1269 "reference count"
1270 );
1271 assert_eq!(
1274 u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]),
1275 104,
1276 "chunk 0 data size"
1277 );
1278 assert_eq!(buf.len(), 16 + 104);
1279 let layout_at = 16 + 32 + 24 + 16;
1281 assert_eq!(u16::from_le_bytes([buf[layout_at], buf[layout_at + 1]]), 8);
1282 assert_eq!(
1283 u16::from_le_bytes([buf[layout_at + 2], buf[layout_at + 3]]),
1284 24
1285 );
1286 assert_eq!(&buf[layout_at + 8 + 18..layout_at + 8 + 24], &[0u8; 6]);
1287 }
1288
1289 #[test]
1290 fn a_v1_header_round_trips_through_its_own_decoder() {
1291 let mut header = ObjectHeader::new();
1292 header.add_message(0x11, 0x00, vec![0xAB; 16]);
1293 header.add_message(0x0C, 0x00, vec![0xCD; 21]);
1294 let buf = header.encode_v1(3).unwrap();
1295 let (back, consumed) = ObjectHeader::decode_v1(&buf).unwrap();
1296 assert_eq!(consumed, buf.len());
1297 assert_eq!(back.messages.len(), 2);
1298 assert_eq!(back.messages[0].data, vec![0xAB; 16]);
1299 assert_eq!(back.messages[1].data.len(), 24);
1301 assert_eq!(back.encode_v1(3).unwrap(), buf);
1302 assert_eq!(ObjectHeader::decode_any(&buf).unwrap().1, buf.len());
1304 }
1305
1306 #[test]
1307 fn a_v1_header_refuses_to_drop_attribute_creation_order() {
1308 let mut header = ObjectHeader::new();
1309 header.set_attribute_creation_order(CreationOrder::Tracked);
1310 assert!(matches!(
1311 header.encode_v1(1).unwrap_err(),
1312 FormatError::InvalidData(_)
1313 ));
1314 }
1315
1316 #[test]
1320 fn a_v1_header_refuses_to_drop_its_stored_times() {
1321 let mut header = ObjectHeader::new();
1322 header.add_message(0x11, 0x00, vec![0u8; 16]);
1323 assert!(header.encode_v1(1).is_ok());
1324
1325 header.times = Some(ObjectTimes::created_at(0x1234_5678));
1326 assert!(matches!(
1327 header.encode_v1(1).unwrap_err(),
1328 FormatError::InvalidData(_)
1329 ));
1330 let v2 = header.encode().unwrap();
1332 assert_eq!(v2[5] & FLAG_STORE_TIMESTAMPS, FLAG_STORE_TIMESTAMPS);
1333 }
1334
1335 #[test]
1336 fn encode_for_picks_the_version_the_format_calls_for() {
1337 use crate::format::ObjectFormat;
1338 let mut header = ObjectHeader::new();
1339 header.add_message(0x11, 0x00, vec![0u8; 16]);
1340 assert_eq!(header.encode_for(ObjectFormat::Legacy, 1).unwrap()[0], 1);
1341 assert_eq!(
1342 &header.encode_for(ObjectFormat::Modern, 1).unwrap()[0..4],
1343 &OHDR_SIGNATURE
1344 );
1345 }
1346
1347 #[test]
1348 fn test_decode_v1_skips_null_messages() {
1349 let buf = build_v1_header(&[
1350 (0x00, 0x00, &[0; 8]), (0x03, 0x00, &[1, 2]),
1352 ]);
1353 let (hdr, _) = ObjectHeader::decode_v1(&buf).unwrap();
1354 assert_eq!(hdr.messages.len(), 1);
1355 assert_eq!(hdr.messages[0].msg_type, 0x03);
1356 }
1357
1358 #[test]
1359 fn test_decode_any_v2() {
1360 let mut hdr = ObjectHeader::new();
1361 hdr.add_message(0x01, 0x00, vec![1, 2, 3]);
1362 let encoded = hdr.encode().unwrap();
1363 let (decoded, _) = ObjectHeader::decode_any(&encoded).unwrap();
1364 assert_eq!(decoded.messages.len(), 1);
1365 }
1366
1367 #[test]
1368 fn test_decode_any_v1() {
1369 let buf = build_v1_header(&[(0x03, 0x00, &[1, 2])]);
1370 let (decoded, _) = ObjectHeader::decode_any(&buf).unwrap();
1371 assert_eq!(decoded.messages.len(), 1);
1372 assert_eq!(decoded.messages[0].msg_type, 0x03);
1373 }
1374
1375 #[test]
1376 fn test_decode_v1_bad_version() {
1377 let mut buf = build_v1_header(&[]);
1378 buf[0] = 5;
1379 assert!(matches!(
1380 ObjectHeader::decode_v1(&buf).unwrap_err(),
1381 FormatError::InvalidVersion(5)
1382 ));
1383 }
1384
1385 #[test]
1386 fn test_decode_v1_buffer_too_short() {
1387 assert!(matches!(
1388 ObjectHeader::decode_v1(&[1, 0, 0]).unwrap_err(),
1389 FormatError::BufferTooShort { .. }
1390 ));
1391 }
1392}
1393
1394#[cfg(test)]
1395mod tests {
1396 use super::*;
1397
1398 #[test]
1399 fn test_empty_header_roundtrip() {
1400 let hdr = ObjectHeader::new();
1401 let encoded = hdr.encode().unwrap();
1402
1403 assert_eq!(encoded.len(), 11);
1407 assert_eq!(&encoded[..4], b"OHDR");
1408 assert_eq!(encoded[4], 2); assert_eq!(encoded[5] & FLAG_SIZE_MASK, 0);
1410
1411 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1412 assert_eq!(consumed, encoded.len());
1413 assert_eq!(decoded, hdr);
1414 }
1415
1416 #[test]
1417 fn test_single_message_roundtrip() {
1418 let mut hdr = ObjectHeader::new();
1419 hdr.add_message(0x01, 0x00, vec![0xAA, 0xBB, 0xCC]);
1420
1421 let encoded = hdr.encode().unwrap();
1422 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1423 assert_eq!(consumed, encoded.len());
1424 assert_eq!(decoded.messages.len(), 1);
1425 assert_eq!(decoded.messages[0].msg_type, 0x01);
1426 assert_eq!(decoded.messages[0].flags, 0x00);
1427 assert_eq!(decoded.messages[0].data, vec![0xAA, 0xBB, 0xCC]);
1428 }
1429
1430 #[test]
1431 fn test_multiple_messages_roundtrip() {
1432 let mut hdr = ObjectHeader::new();
1433 hdr.add_message(0x01, 0x00, vec![1, 2, 3, 4]);
1434 hdr.add_message(0x03, 0x01, vec![10, 20]);
1435 hdr.add_message(0x0C, 0x00, vec![]);
1436
1437 let encoded = hdr.encode().unwrap();
1438 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1439 assert_eq!(consumed, encoded.len());
1440 assert_eq!(decoded.messages.len(), 3);
1441 assert_eq!(decoded, hdr);
1442 }
1443
1444 #[test]
1445 fn test_with_creation_order() {
1446 let mut hdr = ObjectHeader {
1447 flags: 0x02 | FLAG_ATTR_CREATION_ORDER_TRACKED,
1448 times: None,
1449 messages: Vec::new(),
1450 };
1451 hdr.add_message(0x01, 0x00, vec![0xFF; 8]);
1452 hdr.add_message(0x03, 0x00, vec![0xEE; 4]);
1453
1454 let encoded = hdr.encode().unwrap();
1455 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1456 assert_eq!(consumed, encoded.len());
1457 assert_eq!(decoded.messages.len(), 2);
1458 assert_eq!(decoded.messages[0].data, vec![0xFF; 8]);
1459 assert_eq!(decoded.messages[1].data, vec![0xEE; 4]);
1460 }
1461
1462 #[test]
1466 fn a_tracked_header_round_trips_each_message_creation_index() {
1467 let mut hdr = ObjectHeader::new();
1468 hdr.set_attribute_creation_order(CreationOrder::Indexed);
1469 hdr.add_message_indexed(0x0C, 0x00, vec![0xAA; 6], 0);
1470 hdr.add_message_indexed(0x0C, 0x00, vec![0xBB; 6], 1);
1471 hdr.add_message_indexed(0x0C, 0x00, vec![0xCC; 6], 2);
1472
1473 let encoded = hdr.encode().unwrap();
1474 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1475 assert_eq!(consumed, encoded.len());
1476 assert_eq!(decoded, hdr);
1477 let indices: Vec<u16> = decoded.messages.iter().map(|m| m.creation_index).collect();
1478 assert_eq!(indices, vec![0, 1, 2]);
1479
1480 let mut plain = ObjectHeader::new();
1482 plain.add_message(0x0C, 0x00, vec![0xAA; 6]);
1483 assert!(plain.encode().unwrap().len() < encoded.len());
1484 let (plain_back, _) = ObjectHeader::decode(&plain.encode().unwrap()).unwrap();
1485 assert_eq!(plain_back.messages[0].creation_index, 0);
1486 }
1487
1488 #[test]
1492 fn each_attribute_creation_order_state_round_trips_through_the_flags() {
1493 for order in [
1494 CreationOrder::Untracked,
1495 CreationOrder::Tracked,
1496 CreationOrder::Indexed,
1497 ] {
1498 let mut hdr = ObjectHeader::new();
1499 hdr.set_attribute_creation_order(order);
1500 hdr.add_message_indexed(0x0C, 0x00, vec![0xAA; 6], 3);
1501 let (decoded, _) = ObjectHeader::decode(&hdr.encode().unwrap()).unwrap();
1502 assert_eq!(decoded.attribute_creation_order(), order);
1503 let want_index = if order.is_tracked() { 3 } else { 0 };
1504 assert_eq!(decoded.messages[0].creation_index, want_index);
1505 }
1506 }
1507
1508 #[test]
1512 fn setting_a_weaker_policy_clears_the_stronger_one() {
1513 let mut hdr = ObjectHeader::new();
1514 hdr.set_attribute_creation_order(CreationOrder::Indexed);
1515 hdr.set_attribute_creation_order(CreationOrder::Untracked);
1516 assert_eq!(hdr.attribute_creation_order(), CreationOrder::Untracked);
1517 assert_eq!(hdr.flags, 0);
1518 }
1519
1520 #[test]
1523 fn stored_times_round_trip_and_set_the_flag() {
1524 let mut hdr = ObjectHeader::new();
1525 hdr.add_message(0x01, 0x00, vec![1, 2, 3]);
1526 let without = hdr.encode().unwrap();
1527
1528 hdr.times = Some(ObjectTimes {
1529 access: 0x0A0A_0A0A,
1530 modification: 0x0B0B_0B0B,
1531 change: 0x0C0C_0C0C,
1532 birth: 0x0D0D_0D0D,
1533 });
1534 let with = hdr.encode().unwrap();
1535
1536 assert_eq!(with.len(), without.len() + 16);
1537 assert_eq!(with[5] & FLAG_STORE_TIMESTAMPS, FLAG_STORE_TIMESTAMPS);
1538 assert_eq!(&with[6..10], &0x0A0A_0A0Au32.to_le_bytes());
1539 assert_eq!(&with[10..14], &0x0B0B_0B0Bu32.to_le_bytes());
1540 assert_eq!(&with[14..18], &0x0C0C_0C0Cu32.to_le_bytes());
1541 assert_eq!(&with[18..22], &0x0D0D_0D0Du32.to_le_bytes());
1542
1543 let (decoded, consumed) = ObjectHeader::decode(&with).expect("decode failed");
1544 assert_eq!(consumed, with.len());
1545 assert_eq!(decoded, hdr);
1546 }
1547
1548 #[test]
1553 fn the_timestamps_flag_is_never_written_without_times() {
1554 let mut hdr = ObjectHeader::new();
1555 hdr.flags |= FLAG_STORE_TIMESTAMPS;
1556 hdr.add_message(0x01, 0x00, vec![1, 2, 3]);
1557
1558 let encoded = hdr.encode().unwrap();
1559 assert_eq!(encoded[5] & FLAG_STORE_TIMESTAMPS, 0);
1560 let (decoded, _) = ObjectHeader::decode(&encoded).expect("decode failed");
1561 assert_eq!(decoded.times, None);
1562 assert_eq!(decoded.flags & FLAG_STORE_TIMESTAMPS, 0);
1563 }
1564
1565 #[test]
1568 fn touching_moves_access_and_change_time_only() {
1569 let before = ObjectTimes {
1570 access: 100,
1571 modification: 200,
1572 change: 300,
1573 birth: 400,
1574 };
1575 assert_eq!(
1576 before.touched(999),
1577 ObjectTimes {
1578 access: 999,
1579 modification: 200,
1580 change: 999,
1581 birth: 400,
1582 }
1583 );
1584 assert_eq!(
1585 ObjectTimes::created_at(7).touched(7),
1586 ObjectTimes::created_at(7)
1587 );
1588 }
1589
1590 #[test]
1591 fn test_chunk0_size_1byte() {
1592 let mut hdr = ObjectHeader {
1594 flags: 0x00,
1595 times: None,
1596 messages: Vec::new(),
1597 };
1598 hdr.add_message(0x01, 0x00, vec![42]);
1599
1600 let encoded = hdr.encode().unwrap();
1601 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1602 assert_eq!(consumed, encoded.len());
1603 assert_eq!(decoded.messages[0].data, vec![42]);
1604 }
1605
1606 #[test]
1607 fn test_chunk0_size_2byte() {
1608 let mut hdr = ObjectHeader {
1610 flags: 0x01,
1611 times: None,
1612 messages: Vec::new(),
1613 };
1614 hdr.add_message(0x01, 0x00, vec![1, 2, 3]);
1615
1616 let encoded = hdr.encode().unwrap();
1617 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1618 assert_eq!(consumed, encoded.len());
1619 assert_eq!(decoded.messages[0].data, vec![1, 2, 3]);
1620 }
1621
1622 #[test]
1623 fn test_chunk0_size_8byte() {
1624 let mut hdr = ObjectHeader {
1626 flags: 0x03,
1627 times: None,
1628 messages: Vec::new(),
1629 };
1630 hdr.add_message(0x01, 0x00, vec![0xDE, 0xAD]);
1631
1632 let encoded = hdr.encode().unwrap();
1633 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1634 assert_eq!(consumed, encoded.len());
1635 assert_eq!(decoded.messages[0].data, vec![0xDE, 0xAD]);
1636 }
1637
1638 #[test]
1639 fn test_decode_bad_signature() {
1640 let mut data = vec![0u8; 20];
1641 data[0..4].copy_from_slice(b"XHDR");
1642 let err = ObjectHeader::decode(&data).unwrap_err();
1643 assert!(matches!(err, FormatError::InvalidSignature));
1644 }
1645
1646 #[test]
1647 fn test_decode_bad_version() {
1648 let hdr = ObjectHeader::new();
1649 let mut encoded = hdr.encode().unwrap();
1650 encoded[4] = 99; let err = ObjectHeader::decode(&encoded).unwrap_err();
1652 assert!(matches!(err, FormatError::InvalidVersion(99)));
1653 }
1654
1655 #[test]
1656 fn test_decode_checksum_mismatch() {
1657 let mut hdr = ObjectHeader::new();
1658 hdr.add_message(0x01, 0x00, vec![1, 2, 3]);
1659 let mut encoded = hdr.encode().unwrap();
1660 let last_data = encoded.len() - 5;
1662 encoded[last_data] ^= 0xFF;
1663 let err = ObjectHeader::decode(&encoded).unwrap_err();
1664 assert!(matches!(err, FormatError::ChecksumMismatch { .. }));
1665 }
1666
1667 #[test]
1668 fn test_decode_buffer_too_short() {
1669 let err = ObjectHeader::decode(&[0u8; 5]).unwrap_err();
1670 assert!(matches!(err, FormatError::BufferTooShort { .. }));
1671 }
1672
1673 #[test]
1674 fn test_decode_with_trailing_data() {
1675 let mut hdr = ObjectHeader::new();
1676 hdr.add_message(0x01, 0x00, vec![7, 8, 9]);
1677 let mut encoded = hdr.encode().unwrap();
1678 let original_len = encoded.len();
1679 encoded.extend_from_slice(&[0xBB; 50]); let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1682 assert_eq!(consumed, original_len);
1683 assert_eq!(decoded, hdr);
1684 }
1685
1686 #[test]
1687 fn test_large_message_payload() {
1688 let mut hdr = ObjectHeader::new();
1689 let big_data = vec![0x42; 1000];
1690 hdr.add_message(0x0C, 0x00, big_data.clone());
1691
1692 let encoded = hdr.encode().unwrap();
1693 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1694 assert_eq!(consumed, encoded.len());
1695 assert_eq!(decoded.messages[0].data.len(), 1000);
1696 assert_eq!(decoded.messages[0].data, big_data);
1697 }
1698
1699 #[test]
1701 fn test_message_payload_at_size_limit() {
1702 let mut hdr = ObjectHeader::new();
1703 hdr.add_message(0x0C, 0x00, vec![0x42; MAX_MESSAGE_SIZE]);
1704
1705 let encoded = hdr.encode().expect("encode at the limit must succeed");
1706 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1707 assert_eq!(consumed, encoded.len());
1708 assert_eq!(decoded.messages[0].data.len(), MAX_MESSAGE_SIZE);
1709 }
1710
1711 #[test]
1715 fn test_message_payload_over_size_limit_is_refused() {
1716 let mut hdr = ObjectHeader::new();
1717 hdr.add_message(0x01, 0x00, vec![7; 4]);
1718 hdr.add_message(0x0C, 0x00, vec![0x42; MAX_MESSAGE_SIZE + 1]);
1719
1720 let err = hdr.encode().expect_err("over the limit must not encode");
1721 let msg = err.to_string();
1722 assert!(msg.contains("0x0C"), "{msg}");
1723 assert!(msg.contains(&(MAX_MESSAGE_SIZE + 1).to_string()), "{msg}");
1724 }
1725
1726 #[test]
1727 fn test_default() {
1728 let hdr = ObjectHeader::default();
1729 assert_eq!(hdr.flags, 0);
1730 assert!(hdr.messages.is_empty());
1731 }
1732
1733 fn chunked_header() -> ObjectHeader {
1735 let mut hdr = ObjectHeader::new();
1736 for (i, t) in [0x02u8, 0x0A, 0x0C].iter().enumerate() {
1737 hdr.add_message(*t, 0x00, vec![i as u8; 40]);
1738 }
1739 hdr
1740 }
1741
1742 #[test]
1745 fn a_capacity_that_fits_every_message_plans_one_chunk() {
1746 let hdr = chunked_header();
1747 let ctx = FormatContext::default_v3();
1748 let plan = hdr.plan_chunks(ObjectFormat::Modern, 1024, &ctx).unwrap();
1749 assert_eq!(plan.continuation_size, 0);
1750 let (chunk0, continuation) = hdr
1751 .encode_chunked(&plan, ObjectFormat::Modern, &ctx, 0x1000, 1)
1752 .unwrap();
1753 assert!(continuation.is_none());
1754 assert_eq!(chunk0, hdr.encode().unwrap());
1755 assert_eq!(chunk0.len(), plan.chunk0_size);
1756 }
1757
1758 #[test]
1762 fn messages_past_the_capacity_move_into_a_continuation_chunk() {
1763 let hdr = chunked_header();
1764 let ctx = FormatContext::default_v3();
1765 let plan = hdr.plan_chunks(ObjectFormat::Modern, 64, &ctx).unwrap();
1767 assert_eq!(plan.continuation_size, 4 + 2 * 44 + 4);
1768 let (chunk0, continuation) = hdr
1769 .encode_chunked(&plan, ObjectFormat::Modern, &ctx, 0x2000, 1)
1770 .unwrap();
1771 let continuation = continuation.unwrap();
1772 assert_eq!(chunk0.len(), plan.chunk0_size);
1773 assert_eq!(continuation.len(), plan.continuation_size);
1774 assert_eq!(&continuation[..4], &OCHK_SIGNATURE);
1775
1776 let (decoded, consumed) = ObjectHeader::decode(&chunk0).unwrap();
1777 assert_eq!(consumed, chunk0.len());
1778 assert_eq!(
1779 decoded.messages.len(),
1780 2,
1781 "the first message and the pointer"
1782 );
1783 assert_eq!(decoded.messages[0], hdr.messages[0]);
1784 let pointer = &decoded.messages[1];
1785 assert_eq!(pointer.msg_type, MSG_CONTINUATION);
1786 assert_eq!(
1787 u64::from_le_bytes(pointer.data[..8].try_into().unwrap()),
1788 0x2000
1789 );
1790 assert_eq!(
1791 u64::from_le_bytes(pointer.data[8..16].try_into().unwrap()),
1792 plan.continuation_size as u64
1793 );
1794
1795 let body = &continuation[..continuation.len() - 4];
1796 let stored = u32::from_le_bytes(continuation[continuation.len() - 4..].try_into().unwrap());
1797 assert_eq!(stored, checksum_metadata(body));
1798 }
1799
1800 #[test]
1803 fn leftover_chunk_zero_space_is_a_nil_message_or_a_gap() {
1804 let hdr = chunked_header();
1805 let ctx = FormatContext::default_v3();
1806
1807 let (chunk0, _) = hdr
1809 .encode_chunked(
1810 &hdr.plan_chunks(ObjectFormat::Modern, 72, &ctx).unwrap(),
1811 ObjectFormat::Modern,
1812 &ctx,
1813 0x2000,
1814 1,
1815 )
1816 .unwrap();
1817 let tail = &chunk0[chunk0.len() - 4 - 8..chunk0.len() - 4];
1818 assert_eq!(tail, [MSG_NIL, 4, 0, 0, 0, 0, 0, 0]);
1819
1820 let (chunk0, _) = hdr
1822 .encode_chunked(
1823 &hdr.plan_chunks(ObjectFormat::Modern, 67, &ctx).unwrap(),
1824 ObjectFormat::Modern,
1825 &ctx,
1826 0x2000,
1827 1,
1828 )
1829 .unwrap();
1830 assert_eq!(&chunk0[chunk0.len() - 4 - 3..chunk0.len() - 4], [0, 0, 0]);
1831 let (decoded, _) = ObjectHeader::decode(&chunk0).unwrap();
1833 assert_eq!(decoded.messages.len(), 2);
1834 }
1835
1836 #[test]
1839 fn a_capacity_below_the_continuation_message_is_refused() {
1840 let hdr = chunked_header();
1841 let err = hdr
1842 .plan_chunks(ObjectFormat::Modern, 19, &FormatContext::default_v3())
1843 .expect_err("19 bytes cannot hold a 20-byte continuation message");
1844 assert!(err.to_string().contains("continuation chunk"), "{err}");
1845 }
1846
1847 #[test]
1856 fn the_times_prefix_widens_chunk_zero_without_moving_the_split() {
1857 let ctx = FormatContext::default_v3();
1858 let plain = chunked_header();
1859 let mut timed = chunked_header();
1860 timed.times = Some(ObjectTimes::created_at(0x5EED_1234));
1861
1862 for capacity in [72usize, 120, 1024] {
1863 let a = plain
1864 .plan_chunks(ObjectFormat::Modern, capacity, &ctx)
1865 .unwrap();
1866 let b = timed
1867 .plan_chunks(ObjectFormat::Modern, capacity, &ctx)
1868 .unwrap();
1869 assert_eq!(a.split, b.split, "capacity {capacity}: same split");
1870 assert_eq!(
1871 a.continuation_size, b.continuation_size,
1872 "capacity {capacity}: same continuation"
1873 );
1874 assert_eq!(
1875 b.chunk0_size,
1876 a.chunk0_size + 16,
1877 "capacity {capacity}: four times of four bytes"
1878 );
1879 }
1880
1881 let plan = timed.plan_chunks(ObjectFormat::Modern, 72, &ctx).unwrap();
1884 let (chunk0, continuation) = timed
1885 .encode_chunked(&plan, ObjectFormat::Modern, &ctx, 0x2000, 1)
1886 .unwrap();
1887 assert_eq!(chunk0.len(), plan.chunk0_size);
1888 assert_eq!(continuation.unwrap().len(), plan.continuation_size);
1889 let (decoded, _) = ObjectHeader::decode(&chunk0).unwrap();
1890 assert_eq!(decoded.times, timed.times);
1891 }
1892
1893 #[test]
1897 fn a_version_one_plan_with_no_bound_is_one_chunk() {
1898 let hdr = chunked_header();
1899 let ctx = FormatContext::default_v3();
1900 let plan = hdr
1901 .plan_chunks(ObjectFormat::Legacy, usize::MAX, &ctx)
1902 .unwrap();
1903 assert_eq!(plan.continuation_size, 0);
1904 let (chunk0, continuation) = hdr
1905 .encode_chunked(&plan, ObjectFormat::Legacy, &ctx, 0x2000, 3)
1906 .unwrap();
1907 assert!(continuation.is_none());
1908 assert_eq!(chunk0, hdr.encode_v1(3).unwrap());
1909 assert_eq!(chunk0.len(), plan.chunk0_size);
1910 }
1911
1912 #[test]
1919 fn a_version_one_header_spills_into_a_bare_continuation_chunk() {
1920 let hdr = chunked_header();
1921 let ctx = FormatContext::default_v3();
1922 let plan = hdr.plan_chunks(ObjectFormat::Legacy, 80, &ctx).unwrap();
1925 assert_eq!(plan.chunk0_size, 16 + 80);
1926 assert_eq!(plan.continuation_size, 2 * 48);
1927 let (chunk0, continuation) = hdr
1928 .encode_chunked(&plan, ObjectFormat::Legacy, &ctx, 0x2000, 1)
1929 .unwrap();
1930 let continuation = continuation.unwrap();
1931 assert_eq!(chunk0.len(), plan.chunk0_size);
1932 assert_eq!(continuation.len(), plan.continuation_size);
1933 assert_eq!(u16::from_le_bytes([chunk0[2], chunk0[3]]), 5, "nmesgs");
1934 assert_eq!(&chunk0[8..12], &80u32.to_le_bytes());
1935 assert_eq!(&chunk0[16 + 48 + 24..], &[0u8; 8]);
1937
1938 let (decoded, consumed) = ObjectHeader::decode_v1(&chunk0).unwrap();
1939 assert_eq!(consumed, chunk0.len());
1940 assert_eq!(
1941 decoded.messages.len(),
1942 2,
1943 "the first message and the pointer"
1944 );
1945 assert_eq!(decoded.messages[0], hdr.messages[0]);
1946 let pointer = &decoded.messages[1];
1947 assert_eq!(pointer.msg_type, MSG_CONTINUATION);
1948 assert_eq!(
1949 u64::from_le_bytes(pointer.data[..8].try_into().unwrap()),
1950 0x2000
1951 );
1952 assert_eq!(
1953 u64::from_le_bytes(pointer.data[8..16].try_into().unwrap()),
1954 plan.continuation_size as u64
1955 );
1956 assert_eq!(&continuation[..4], &[0x0A, 0, 40, 0]);
1958 assert_eq!(&continuation[8..48], &hdr.messages[1].data[..]);
1959 assert_eq!(&continuation[48..52], &[0x0C, 0, 40, 0]);
1960 }
1961
1962 #[test]
1967 fn a_plan_into_a_block_holds_chunk_zero_to_it() {
1968 let hdr = chunked_header();
1969 let ctx = FormatContext::default_v3();
1970
1971 let block = 7 + 3 * 44 + 20 + 4;
1974 let plan = hdr
1975 .plan_chunks_in(ObjectFormat::Modern, block, &ctx)
1976 .unwrap();
1977 assert_eq!((plan.chunk0_size, plan.continuation_size), (block, 0));
1978 let (chunk0, continuation) = hdr
1979 .encode_chunked(&plan, ObjectFormat::Modern, &ctx, 0, 1)
1980 .unwrap();
1981 assert!(continuation.is_none());
1982 assert_eq!(chunk0.len(), block);
1983 let (decoded, consumed) = ObjectHeader::decode(&chunk0).unwrap();
1984 assert_eq!(consumed, block);
1985 assert_eq!(decoded.messages[..3], hdr.messages[..]);
1986 assert_eq!(decoded.messages[3].msg_type, MSG_NIL);
1987 assert_eq!(decoded.messages[3].data.len(), 20 - 4);
1988
1989 let plan = hdr
1991 .plan_chunks_in(ObjectFormat::Modern, 7 + 72 + 4, &ctx)
1992 .unwrap();
1993 assert_eq!(plan.chunk0_size, 7 + 72 + 4);
1994 assert_eq!(plan.continuation_size, 4 + 2 * 44 + 4);
1995
1996 let block = 16 + 3 * 48 + 16;
1999 let plan = hdr
2000 .plan_chunks_in(ObjectFormat::Legacy, block, &ctx)
2001 .unwrap();
2002 assert_eq!((plan.chunk0_size, plan.continuation_size), (block, 0));
2003 let (chunk0, _) = hdr
2004 .encode_chunked(&plan, ObjectFormat::Legacy, &ctx, 0, 1)
2005 .unwrap();
2006 assert_eq!(chunk0.len(), block);
2007 assert_eq!(u16::from_le_bytes([chunk0[2], chunk0[3]]), 4, "nmesgs");
2008 assert_eq!(&chunk0[block - 16..block - 12], &[0, 0, 8, 0]);
2009 let (decoded, consumed) = ObjectHeader::decode_v1(&chunk0).unwrap();
2010 assert_eq!(consumed, block);
2011 assert_eq!(decoded.messages, hdr.messages);
2012
2013 for (format, block) in [
2016 (ObjectFormat::Legacy, 16 + 3 * 48 + 12),
2017 (ObjectFormat::Legacy, 8),
2018 (ObjectFormat::Modern, 10),
2019 ] {
2020 hdr.plan_chunks_in(format, block, &ctx)
2021 .expect_err("an undescribable block");
2022 }
2023 }
2024
2025 #[test]
2031 fn a_plan_into_a_block_takes_the_narrowest_size_field() {
2032 let mut hdr = chunked_header();
2033 hdr.flags |= 3;
2035 let ctx = FormatContext::default_v3();
2036
2037 let exact = 7 + 3 * 44 + 4;
2039 let plan = hdr
2040 .plan_chunks_in(ObjectFormat::Modern, exact, &ctx)
2041 .unwrap();
2042 assert_eq!(plan.continuation_size, 0);
2043 let (chunk0, _) = hdr
2044 .encode_chunked(&plan, ObjectFormat::Modern, &ctx, 0, 1)
2045 .unwrap();
2046 assert_eq!(chunk0.len(), exact);
2047 assert_eq!(chunk0[5] & FLAG_SIZE_MASK, 0);
2048 let (decoded, _) = ObjectHeader::decode(&chunk0).unwrap();
2049 assert_eq!(decoded.messages, hdr.messages);
2050
2051 let plan = hdr
2053 .plan_chunks_in(ObjectFormat::Modern, 8 + 300 + 4, &ctx)
2054 .unwrap();
2055 let (chunk0, _) = hdr
2056 .encode_chunked(&plan, ObjectFormat::Modern, &ctx, 0, 1)
2057 .unwrap();
2058 assert_eq!(chunk0.len(), 8 + 300 + 4);
2059 assert_eq!(chunk0[5] & FLAG_SIZE_MASK, 1);
2060 let (decoded, consumed) = ObjectHeader::decode(&chunk0).unwrap();
2061 assert_eq!(consumed, chunk0.len());
2062 assert_eq!(decoded.messages[..3], hdr.messages[..]);
2063
2064 assert_eq!(hdr.flags & FLAG_SIZE_MASK, 3);
2066 }
2067}