1use crate::format::checksum::checksum_metadata;
33use crate::format::creation_order::CreationOrder;
34use crate::format::{FormatContext, FormatError, FormatResult};
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}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
147pub struct ObjectHeader {
148 pub flags: u8,
156 pub times: Option<ObjectTimes>,
158 pub messages: Vec<ObjectHeaderMessage>,
160}
161
162impl ObjectHeader {
163 pub fn new() -> Self {
168 Self {
169 flags: 0x02, times: None,
171 messages: Vec::new(),
172 }
173 }
174
175 pub fn recorded_times(&self) -> Option<ObjectTimes> {
192 if let Some(times) = self.times {
193 return Some(times);
194 }
195 self.messages
196 .iter()
197 .find(|m| m.msg_type == crate::format::messages::MSG_MOD_TIME)
198 .and_then(|m| crate::format::messages::mod_time::ModificationTime::decode(&m.data).ok())
199 .map(|t| ObjectTimes::created_at(t.0))
200 }
201
202 pub fn add_message(&mut self, msg_type: u8, flags: u8, data: Vec<u8>) {
204 self.add_message_indexed(msg_type, flags, data, 0);
205 }
206
207 pub fn add_message_indexed(
213 &mut self,
214 msg_type: u8,
215 flags: u8,
216 data: Vec<u8>,
217 creation_index: u16,
218 ) {
219 self.messages.push(ObjectHeaderMessage {
220 msg_type,
221 flags,
222 creation_index,
223 data,
224 });
225 }
226
227 pub fn set_attribute_creation_order(&mut self, order: CreationOrder) {
235 self.flags &= !(FLAG_ATTR_CREATION_ORDER_TRACKED | FLAG_ATTR_CREATION_ORDER_INDEXED);
236 if order.is_tracked() {
237 self.flags |= FLAG_ATTR_CREATION_ORDER_TRACKED;
238 }
239 if order.is_indexed() {
240 self.flags |= FLAG_ATTR_CREATION_ORDER_INDEXED;
241 }
242 }
243
244 pub fn attribute_creation_order(&self) -> CreationOrder {
250 CreationOrder::from_flags(
251 self.flags & FLAG_ATTR_CREATION_ORDER_TRACKED != 0,
252 self.flags & FLAG_ATTR_CREATION_ORDER_INDEXED != 0,
253 )
254 }
255
256 fn encoded_flags(&self) -> u8 {
262 let base = self.flags & !FLAG_STORE_TIMESTAMPS;
263 match self.times {
264 Some(_) => base | FLAG_STORE_TIMESTAMPS,
265 None => base,
266 }
267 }
268
269 fn chunk0_size_bytes(&self) -> usize {
272 match self.flags & FLAG_SIZE_MASK {
273 0 => 1,
274 1 => 2,
275 2 => 4,
276 3 => 8,
277 _ => unreachable!(),
278 }
279 }
280
281 pub fn has_creation_order(&self) -> bool {
283 self.flags & FLAG_ATTR_CREATION_ORDER_TRACKED != 0
284 }
285
286 pub fn message_envelope_size(&self) -> usize {
289 if self.has_creation_order() {
290 1 + 2 + 1 + 2 } else {
292 1 + 2 + 1 }
294 }
295
296 fn messages_size(&self, messages: &[ObjectHeaderMessage]) -> usize {
298 messages
299 .iter()
300 .map(|m| self.message_envelope_size() + m.data.len())
301 .sum()
302 }
303
304 fn messages_data_size(&self) -> usize {
306 self.messages_size(&self.messages)
307 }
308
309 fn prefix_size(&self) -> usize {
312 let mut size = 4 + 1 + 1; if self.times.is_some() {
314 size += 16; }
316 if self.flags & FLAG_NON_DEFAULT_ATTR_THRESHOLDS != 0 {
317 size += 4; }
319 size + self.chunk0_size_bytes()
320 }
321
322 fn check_message_sizes(messages: &[ObjectHeaderMessage]) -> FormatResult<()> {
330 for msg in messages {
331 if msg.data.len() > MAX_MESSAGE_SIZE {
332 return Err(FormatError::InvalidData(format!(
333 "object header message type 0x{:02X} is {} bytes, over the \
334 {MAX_MESSAGE_SIZE}-byte limit the message size field can express",
335 msg.msg_type,
336 msg.data.len()
337 )));
338 }
339 }
340 Ok(())
341 }
342
343 fn write_messages(
350 &self,
351 buf: &mut Vec<u8>,
352 messages: &[ObjectHeaderMessage],
353 data_size: usize,
354 ) {
355 let envelope = self.message_envelope_size();
356 let mut write = |msg_type: u8, flags: u8, creation_index: u16, data: &[u8]| {
357 buf.push(msg_type);
358 buf.extend_from_slice(&(data.len() as u16).to_le_bytes());
360 buf.push(flags);
361 if self.has_creation_order() {
362 buf.extend_from_slice(&creation_index.to_le_bytes());
363 }
364 buf.extend_from_slice(data);
365 };
366 for msg in messages {
367 write(msg.msg_type, msg.flags, msg.creation_index, &msg.data);
368 }
369 let spare = data_size - self.messages_size(messages);
370 if spare >= envelope {
371 write(MSG_NIL, 0x00, 0, &vec![0u8; spare - envelope]);
372 } else {
373 buf.extend(std::iter::repeat_n(0u8, spare));
374 }
375 }
376
377 pub fn encode(&self) -> FormatResult<Vec<u8>> {
383 self.encode_chunk0(&self.messages, self.messages_data_size())
384 }
385
386 fn encode_chunk0(
389 &self,
390 messages: &[ObjectHeaderMessage],
391 data_size: usize,
392 ) -> FormatResult<Vec<u8>> {
393 Self::check_message_sizes(messages)?;
394 debug_assert!(data_size >= self.messages_size(messages));
395 let total = self.prefix_size() + data_size + 4; let mut buf = Vec::with_capacity(total);
397
398 buf.extend_from_slice(&OHDR_SIGNATURE);
399 buf.push(OHDR_VERSION);
400 buf.push(self.encoded_flags());
401
402 if let Some(t) = self.times {
404 for field in [t.access, t.modification, t.change, t.birth] {
405 buf.extend_from_slice(&field.to_le_bytes());
406 }
407 }
408
409 if self.flags & FLAG_NON_DEFAULT_ATTR_THRESHOLDS != 0 {
411 buf.extend_from_slice(&8u16.to_le_bytes());
413 buf.extend_from_slice(&6u16.to_le_bytes());
414 }
415
416 let csb = self.chunk0_size_bytes();
417 buf.extend_from_slice(&(data_size as u64).to_le_bytes()[..csb]);
418
419 self.write_messages(&mut buf, messages, data_size);
420
421 let cksum = checksum_metadata(&buf);
423 buf.extend_from_slice(&cksum.to_le_bytes());
424
425 debug_assert_eq!(buf.len(), total);
426 Ok(buf)
427 }
428
429 fn encode_continuation(&self, messages: &[ObjectHeaderMessage]) -> FormatResult<Vec<u8>> {
434 Self::check_message_sizes(messages)?;
435 let data_size = self.messages_size(messages);
436 let mut buf = Vec::with_capacity(OCHK_SIGNATURE.len() + data_size + 4);
437 buf.extend_from_slice(&OCHK_SIGNATURE);
438 self.write_messages(&mut buf, messages, data_size);
439 let cksum = checksum_metadata(&buf);
440 buf.extend_from_slice(&cksum.to_le_bytes());
441 Ok(buf)
442 }
443
444 pub fn plan_chunks(&self, capacity: usize, ctx: &FormatContext) -> FormatResult<ChunkPlan> {
456 let exact = self.messages_data_size();
457 if exact <= capacity {
458 return Ok(ChunkPlan {
459 split: self.messages.len(),
460 chunk0_size: self.prefix_size() + exact + 4,
461 continuation_size: 0,
462 });
463 }
464 let envelope = self.message_envelope_size();
467 let continuation_message = envelope + ctx.sizeof_addr as usize + ctx.sizeof_size as usize;
468 if capacity < continuation_message {
469 return Err(FormatError::InvalidData(format!(
470 "an object header chunk-0 capacity of {capacity} bytes cannot hold the \
471 {continuation_message}-byte message naming its continuation chunk"
472 )));
473 }
474 let mut used = continuation_message;
475 let mut split = 0;
476 for msg in &self.messages {
477 let size = envelope + msg.data.len();
478 if used + size > capacity {
479 break;
480 }
481 used += size;
482 split += 1;
483 }
484 let spilled = self.messages_size(&self.messages[split..]);
485 Ok(ChunkPlan {
486 split,
487 chunk0_size: self.prefix_size() + capacity + 4,
488 continuation_size: OCHK_SIGNATURE.len() + spilled + 4,
489 })
490 }
491
492 pub fn encode_chunked(
498 &self,
499 plan: &ChunkPlan,
500 ctx: &FormatContext,
501 continuation_addr: u64,
502 ) -> FormatResult<(Vec<u8>, Option<Vec<u8>>)> {
503 if plan.continuation_size == 0 {
504 return Ok((self.encode()?, None));
505 }
506 let mut chunk0: Vec<ObjectHeaderMessage> = self.messages[..plan.split].to_vec();
507 let sa = ctx.sizeof_addr as usize;
508 let ss = ctx.sizeof_size as usize;
509 let mut body = Vec::with_capacity(sa + ss);
510 body.extend_from_slice(&continuation_addr.to_le_bytes()[..sa]);
511 body.extend_from_slice(&(plan.continuation_size as u64).to_le_bytes()[..ss]);
512 chunk0.push(ObjectHeaderMessage {
513 msg_type: MSG_CONTINUATION,
514 flags: 0x00,
515 creation_index: 0,
516 data: body,
517 });
518 let data_size = plan.chunk0_size - self.prefix_size() - 4;
519 let continuation = self.encode_continuation(&self.messages[plan.split..])?;
520 debug_assert_eq!(continuation.len(), plan.continuation_size);
521 Ok((self.encode_chunk0(&chunk0, data_size)?, Some(continuation)))
522 }
523
524 pub fn decode(buf: &[u8]) -> FormatResult<(Self, usize)> {
527 if buf.len() < 11 {
529 return Err(FormatError::BufferTooShort {
530 needed: 11,
531 available: buf.len(),
532 });
533 }
534
535 if buf[0..4] != OHDR_SIGNATURE {
537 return Err(FormatError::InvalidSignature);
538 }
539
540 let version = buf[4];
542 if version != OHDR_VERSION {
543 return Err(FormatError::InvalidVersion(version));
544 }
545
546 let flags = buf[5] & !FLAG_STORE_TIMESTAMPS;
549 let mut pos: usize = 6;
550
551 let times = if buf[5] & FLAG_STORE_TIMESTAMPS != 0 {
553 if buf.len() < pos + 16 {
554 return Err(FormatError::BufferTooShort {
555 needed: pos + 16,
556 available: buf.len(),
557 });
558 }
559 let read = |off: usize| {
560 u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]])
561 };
562 let t = ObjectTimes {
563 access: read(pos),
564 modification: read(pos + 4),
565 change: read(pos + 8),
566 birth: read(pos + 12),
567 };
568 pos += 16;
569 Some(t)
570 } else {
571 None
572 };
573
574 if flags & FLAG_NON_DEFAULT_ATTR_THRESHOLDS != 0 {
576 if buf.len() < pos + 4 {
577 return Err(FormatError::BufferTooShort {
578 needed: pos + 4,
579 available: buf.len(),
580 });
581 }
582 pos += 4;
584 }
585
586 let chunk0_size_bytes = match flags & FLAG_SIZE_MASK {
588 0 => 1,
589 1 => 2,
590 2 => 4,
591 3 => 8,
592 _ => unreachable!(),
593 };
594
595 if buf.len() < pos + chunk0_size_bytes {
596 return Err(FormatError::BufferTooShort {
597 needed: pos + chunk0_size_bytes,
598 available: buf.len(),
599 });
600 }
601
602 let chunk0_data_size =
603 crate::format::bytes::read_le_uint(&buf[pos..], chunk0_size_bytes) as usize;
604 pos += chunk0_size_bytes;
605
606 let total_consumed = pos
611 .checked_add(chunk0_data_size)
612 .and_then(|x| x.checked_add(4))
613 .ok_or_else(|| {
614 FormatError::InvalidData("object header chunk-0 size overflows usize".into())
615 })?;
616 if buf.len() < total_consumed {
617 return Err(FormatError::BufferTooShort {
618 needed: total_consumed,
619 available: buf.len(),
620 });
621 }
622
623 let data_end = total_consumed - 4;
626 let stored_cksum = u32::from_le_bytes([
627 buf[data_end],
628 buf[data_end + 1],
629 buf[data_end + 2],
630 buf[data_end + 3],
631 ]);
632 let computed_cksum = checksum_metadata(&buf[..data_end]);
633 if stored_cksum != computed_cksum {
634 return Err(FormatError::ChecksumMismatch {
635 expected: stored_cksum,
636 computed: computed_cksum,
637 });
638 }
639
640 let has_creation_order = flags & FLAG_ATTR_CREATION_ORDER_TRACKED != 0;
642 let messages_end = pos + chunk0_data_size;
643 let mut messages = Vec::new();
644
645 while pos < messages_end {
646 let msg_header_size = if has_creation_order { 6 } else { 4 };
648 if pos + msg_header_size > messages_end {
649 break;
653 }
654
655 let msg_type = buf[pos];
656 let msg_data_size = u16::from_le_bytes([buf[pos + 1], buf[pos + 2]]) as usize;
657 let msg_flags = buf[pos + 3];
658 pos += 4;
659
660 let creation_index = if has_creation_order {
661 let v = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
662 pos += 2;
663 v
664 } else {
665 0
666 };
667
668 if pos + msg_data_size > messages_end {
669 return Err(FormatError::InvalidData(format!(
670 "message data ({} bytes) extends past chunk0 boundary",
671 msg_data_size
672 )));
673 }
674
675 let data = buf[pos..pos + msg_data_size].to_vec();
676 pos += msg_data_size;
677
678 messages.push(ObjectHeaderMessage {
679 msg_type,
680 flags: msg_flags,
681 creation_index,
682 data,
683 });
684 }
685
686 Ok((
687 ObjectHeader {
688 flags,
689 times,
690 messages,
691 },
692 total_consumed,
693 ))
694 }
695}
696
697impl Default for ObjectHeader {
698 fn default() -> Self {
699 Self::new()
700 }
701}
702
703fn align_old(n: usize) -> usize {
710 (n + 7) & !7
711}
712
713const V1_PREFIX_SIZE: usize = 16;
716
717const V1_MSG_HEADER_SIZE: usize = 8;
720
721impl ObjectHeader {
722 pub fn encode_v1(&self, nlink: u32) -> FormatResult<Vec<u8>> {
746 if self.flags & (FLAG_ATTR_CREATION_ORDER_TRACKED | FLAG_ATTR_CREATION_ORDER_INDEXED) != 0 {
747 return Err(FormatError::InvalidData(
748 "a version-1 object header cannot record attribute creation order: \
749 the tracking flags exist only in the version-2 header prefix"
750 .into(),
751 ));
752 }
753 if self.times.is_some() {
754 return Err(FormatError::InvalidData(
755 "a version-1 object header cannot store access/modification/change/birth \
756 times: they are version-2 prefix fields, and version 1 carries only a \
757 modification time, as an H5O_MTIME_NEW message"
758 .into(),
759 ));
760 }
761 let mut data_size = 0usize;
762 for msg in &self.messages {
763 let padded = align_old(msg.data.len());
764 if padded > MAX_MESSAGE_SIZE {
765 return Err(FormatError::InvalidData(format!(
766 "object header message type 0x{:02X} is {} bytes, {padded} once \
767 aligned to 8, over the {MAX_MESSAGE_SIZE}-byte limit the message \
768 size field can express",
769 msg.msg_type,
770 msg.data.len()
771 )));
772 }
773 data_size += V1_MSG_HEADER_SIZE + padded;
774 }
775 let Ok(chunk0_size) = u32::try_from(data_size) else {
776 return Err(FormatError::InvalidData(format!(
777 "version-1 object header chunk 0 is {data_size} bytes, over the 4-byte \
778 size field's range"
779 )));
780 };
781 let Ok(nmesgs) = u16::try_from(self.messages.len()) else {
782 return Err(FormatError::InvalidData(format!(
783 "version-1 object header holds {} messages, over the 2-byte count \
784 field's range",
785 self.messages.len()
786 )));
787 };
788
789 let total = V1_PREFIX_SIZE + data_size;
790 let mut buf = Vec::with_capacity(total);
791 buf.push(1); buf.push(0); buf.extend_from_slice(&nmesgs.to_le_bytes());
794 buf.extend_from_slice(&nlink.to_le_bytes());
795 buf.extend_from_slice(&chunk0_size.to_le_bytes());
796 buf.extend_from_slice(&[0u8; 4]); for msg in &self.messages {
799 let padded = align_old(msg.data.len());
800 buf.extend_from_slice(&u16::from(msg.msg_type).to_le_bytes());
801 buf.extend_from_slice(&(padded as u16).to_le_bytes());
802 buf.push(msg.flags);
803 buf.extend_from_slice(&[0u8; 3]); buf.extend_from_slice(&msg.data);
805 buf.resize(buf.len() + (padded - msg.data.len()), 0);
806 }
807
808 debug_assert_eq!(buf.len(), total);
809 Ok(buf)
810 }
811
812 pub fn encode_for(
818 &self,
819 format: crate::format::ObjectFormat,
820 nlink: u32,
821 ) -> FormatResult<Vec<u8>> {
822 match format {
823 crate::format::ObjectFormat::Legacy => self.encode_v1(nlink),
824 crate::format::ObjectFormat::Modern => self.encode(),
825 }
826 }
827}
828
829impl ObjectHeader {
830 pub fn decode_v1(buf: &[u8]) -> FormatResult<(Self, usize)> {
847 if buf.len() < 16 {
850 return Err(FormatError::BufferTooShort {
851 needed: 16,
852 available: buf.len(),
853 });
854 }
855
856 let version = buf[0];
857 if version != 1 {
858 return Err(FormatError::InvalidVersion(version));
859 }
860
861 let num_messages = u16::from_le_bytes([buf[2], buf[3]]) as usize;
863 let _obj_ref_count = u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]);
864 let header_data_size = u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]) as usize;
865 let total_consumed = 16 + header_data_size;
868 if buf.len() < total_consumed {
869 return Err(FormatError::BufferTooShort {
870 needed: total_consumed,
871 available: buf.len(),
872 });
873 }
874
875 let msg_data_start = 16; let mut pos = msg_data_start;
877 let messages_end = msg_data_start + header_data_size;
878 let mut messages = Vec::with_capacity(num_messages);
879
880 for _ in 0..num_messages {
881 if pos + 8 > messages_end {
882 break; }
884
885 let msg_type = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
886 let data_size = u16::from_le_bytes([buf[pos + 2], buf[pos + 3]]) as usize;
887 let msg_flags = buf[pos + 4];
888 pos += 8;
890
891 if pos + data_size > messages_end {
892 return Err(FormatError::InvalidData(format!(
893 "v1 message data ({} bytes) extends past header boundary",
894 data_size
895 )));
896 }
897
898 let data = buf[pos..pos + data_size].to_vec();
899 pos += data_size;
900
901 let rel = pos - msg_data_start;
904 let aligned_rel = (rel + 7) & !7;
905 let aligned_pos = msg_data_start + aligned_rel;
906 if aligned_pos <= messages_end {
907 pos = aligned_pos;
908 }
909
910 if msg_type == 0 {
912 continue;
913 }
914
915 messages.push(ObjectHeaderMessage {
916 msg_type: msg_type as u8,
917 flags: msg_flags,
918 creation_index: 0,
920 data,
921 });
922 }
923
924 Ok((
925 ObjectHeader {
926 flags: 0x02, times: None,
930 messages,
931 },
932 total_consumed,
933 ))
934 }
935
936 pub fn decode_any(buf: &[u8]) -> FormatResult<(Self, usize)> {
940 if buf.len() >= 4 && buf[0..4] == OHDR_SIGNATURE {
941 Self::decode(buf)
942 } else if !buf.is_empty() && buf[0] == 1 {
943 Self::decode_v1(buf)
944 } else {
945 Self::decode(buf)
947 }
948 }
949}
950
951#[cfg(test)]
952mod tests_v1 {
953 use super::*;
954
955 fn build_v1_header(messages: &[(u16, u8, &[u8])]) -> Vec<u8> {
957 let mut msg_data = Vec::new();
958 for (msg_type, flags, data) in messages {
959 msg_data.extend_from_slice(&msg_type.to_le_bytes());
960 msg_data.extend_from_slice(&(data.len() as u16).to_le_bytes());
961 msg_data.push(*flags);
962 msg_data.extend_from_slice(&[0u8; 3]); msg_data.extend_from_slice(data);
964 let aligned = (msg_data.len() + 7) & !7;
966 msg_data.resize(aligned, 0);
967 }
968
969 let mut buf = Vec::new();
970 buf.push(1); buf.push(0); buf.extend_from_slice(&(messages.len() as u16).to_le_bytes());
973 buf.extend_from_slice(&1u32.to_le_bytes()); buf.extend_from_slice(&(msg_data.len() as u32).to_le_bytes());
975 buf.extend_from_slice(&[0u8; 4]); buf.extend_from_slice(&msg_data);
977 buf
978 }
979
980 #[test]
981 fn test_decode_v1_empty() {
982 let buf = build_v1_header(&[]);
983 let (hdr, consumed) = ObjectHeader::decode_v1(&buf).unwrap();
984 assert_eq!(consumed, 16); assert!(hdr.messages.is_empty());
986 }
987
988 #[test]
989 fn test_decode_v1_single_message() {
990 let data = vec![0xAA, 0xBB, 0xCC];
991 let buf = build_v1_header(&[(0x03, 0x00, &data)]);
992 let (hdr, _consumed) = ObjectHeader::decode_v1(&buf).unwrap();
993 assert_eq!(hdr.messages.len(), 1);
994 assert_eq!(hdr.messages[0].msg_type, 0x03);
995 assert_eq!(hdr.messages[0].data, data);
996 }
997
998 #[test]
999 fn test_decode_v1_multiple_messages() {
1000 let buf = build_v1_header(&[
1001 (0x01, 0x00, &[1, 2, 3, 4]),
1002 (0x03, 0x01, &[10, 20]),
1003 (0x08, 0x00, &[0xFF; 16]),
1004 ]);
1005 let (hdr, _) = ObjectHeader::decode_v1(&buf).unwrap();
1006 assert_eq!(hdr.messages.len(), 3);
1007 assert_eq!(hdr.messages[0].msg_type, 0x01);
1008 assert_eq!(hdr.messages[1].msg_type, 0x03);
1009 assert_eq!(hdr.messages[2].msg_type, 0x08);
1010 assert_eq!(hdr.messages[2].data, vec![0xFF; 16]);
1011 }
1012
1013 #[test]
1020 fn an_encoded_v1_header_matches_the_envelope_libhdf5_writes() {
1021 let dataspace = vec![
1022 0x01, 0x01, 0x01, 0x00, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0,
1023 ];
1024 let datatype = vec![0x10, 0x08, 0, 0, 0x04, 0, 0, 0, 0, 0, 0x20, 0, 0, 0, 0, 0];
1025 let fill = vec![0x02, 0x02, 0x02, 0x01, 0, 0, 0, 0];
1026 let layout = vec![
1028 0x03, 0x01, 0, 0x08, 0, 0, 0, 0, 0, 0, 0x18, 0, 0, 0, 0, 0, 0, 0,
1029 ];
1030 let mut header = ObjectHeader::new();
1031 header.add_message(0x01, 0x00, dataspace);
1032 header.add_message(0x03, 0x01, datatype);
1033 header.add_message(0x05, 0x01, fill);
1034 header.add_message(0x08, 0x00, layout);
1035
1036 let buf = header.encode_v1(1).unwrap();
1037 assert_eq!(buf[0], 1, "version");
1038 assert_eq!(u16::from_le_bytes([buf[2], buf[3]]), 4, "message count");
1039 assert_eq!(
1040 u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]),
1041 1,
1042 "reference count"
1043 );
1044 assert_eq!(
1047 u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]),
1048 104,
1049 "chunk 0 data size"
1050 );
1051 assert_eq!(buf.len(), 16 + 104);
1052 let layout_at = 16 + 32 + 24 + 16;
1054 assert_eq!(u16::from_le_bytes([buf[layout_at], buf[layout_at + 1]]), 8);
1055 assert_eq!(
1056 u16::from_le_bytes([buf[layout_at + 2], buf[layout_at + 3]]),
1057 24
1058 );
1059 assert_eq!(&buf[layout_at + 8 + 18..layout_at + 8 + 24], &[0u8; 6]);
1060 }
1061
1062 #[test]
1063 fn a_v1_header_round_trips_through_its_own_decoder() {
1064 let mut header = ObjectHeader::new();
1065 header.add_message(0x11, 0x00, vec![0xAB; 16]);
1066 header.add_message(0x0C, 0x00, vec![0xCD; 21]);
1067 let buf = header.encode_v1(3).unwrap();
1068 let (back, consumed) = ObjectHeader::decode_v1(&buf).unwrap();
1069 assert_eq!(consumed, buf.len());
1070 assert_eq!(back.messages.len(), 2);
1071 assert_eq!(back.messages[0].data, vec![0xAB; 16]);
1072 assert_eq!(back.messages[1].data.len(), 24);
1074 assert_eq!(back.encode_v1(3).unwrap(), buf);
1075 assert_eq!(ObjectHeader::decode_any(&buf).unwrap().1, buf.len());
1077 }
1078
1079 #[test]
1080 fn a_v1_header_refuses_to_drop_attribute_creation_order() {
1081 let mut header = ObjectHeader::new();
1082 header.set_attribute_creation_order(CreationOrder::Tracked);
1083 assert!(matches!(
1084 header.encode_v1(1).unwrap_err(),
1085 FormatError::InvalidData(_)
1086 ));
1087 }
1088
1089 #[test]
1093 fn a_v1_header_refuses_to_drop_its_stored_times() {
1094 let mut header = ObjectHeader::new();
1095 header.add_message(0x11, 0x00, vec![0u8; 16]);
1096 assert!(header.encode_v1(1).is_ok());
1097
1098 header.times = Some(ObjectTimes::created_at(0x1234_5678));
1099 assert!(matches!(
1100 header.encode_v1(1).unwrap_err(),
1101 FormatError::InvalidData(_)
1102 ));
1103 let v2 = header.encode().unwrap();
1105 assert_eq!(v2[5] & FLAG_STORE_TIMESTAMPS, FLAG_STORE_TIMESTAMPS);
1106 }
1107
1108 #[test]
1109 fn encode_for_picks_the_version_the_format_calls_for() {
1110 use crate::format::ObjectFormat;
1111 let mut header = ObjectHeader::new();
1112 header.add_message(0x11, 0x00, vec![0u8; 16]);
1113 assert_eq!(header.encode_for(ObjectFormat::Legacy, 1).unwrap()[0], 1);
1114 assert_eq!(
1115 &header.encode_for(ObjectFormat::Modern, 1).unwrap()[0..4],
1116 &OHDR_SIGNATURE
1117 );
1118 }
1119
1120 #[test]
1121 fn test_decode_v1_skips_null_messages() {
1122 let buf = build_v1_header(&[
1123 (0x00, 0x00, &[0; 8]), (0x03, 0x00, &[1, 2]),
1125 ]);
1126 let (hdr, _) = ObjectHeader::decode_v1(&buf).unwrap();
1127 assert_eq!(hdr.messages.len(), 1);
1128 assert_eq!(hdr.messages[0].msg_type, 0x03);
1129 }
1130
1131 #[test]
1132 fn test_decode_any_v2() {
1133 let mut hdr = ObjectHeader::new();
1134 hdr.add_message(0x01, 0x00, vec![1, 2, 3]);
1135 let encoded = hdr.encode().unwrap();
1136 let (decoded, _) = ObjectHeader::decode_any(&encoded).unwrap();
1137 assert_eq!(decoded.messages.len(), 1);
1138 }
1139
1140 #[test]
1141 fn test_decode_any_v1() {
1142 let buf = build_v1_header(&[(0x03, 0x00, &[1, 2])]);
1143 let (decoded, _) = ObjectHeader::decode_any(&buf).unwrap();
1144 assert_eq!(decoded.messages.len(), 1);
1145 assert_eq!(decoded.messages[0].msg_type, 0x03);
1146 }
1147
1148 #[test]
1149 fn test_decode_v1_bad_version() {
1150 let mut buf = build_v1_header(&[]);
1151 buf[0] = 5;
1152 assert!(matches!(
1153 ObjectHeader::decode_v1(&buf).unwrap_err(),
1154 FormatError::InvalidVersion(5)
1155 ));
1156 }
1157
1158 #[test]
1159 fn test_decode_v1_buffer_too_short() {
1160 assert!(matches!(
1161 ObjectHeader::decode_v1(&[1, 0, 0]).unwrap_err(),
1162 FormatError::BufferTooShort { .. }
1163 ));
1164 }
1165}
1166
1167#[cfg(test)]
1168mod tests {
1169 use super::*;
1170
1171 #[test]
1172 fn test_empty_header_roundtrip() {
1173 let hdr = ObjectHeader::new();
1174 let encoded = hdr.encode().unwrap();
1175
1176 assert_eq!(encoded.len(), 14);
1178 assert_eq!(&encoded[..4], b"OHDR");
1179 assert_eq!(encoded[4], 2); let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1182 assert_eq!(consumed, encoded.len());
1183 assert_eq!(decoded, hdr);
1184 }
1185
1186 #[test]
1187 fn test_single_message_roundtrip() {
1188 let mut hdr = ObjectHeader::new();
1189 hdr.add_message(0x01, 0x00, vec![0xAA, 0xBB, 0xCC]);
1190
1191 let encoded = hdr.encode().unwrap();
1192 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1193 assert_eq!(consumed, encoded.len());
1194 assert_eq!(decoded.messages.len(), 1);
1195 assert_eq!(decoded.messages[0].msg_type, 0x01);
1196 assert_eq!(decoded.messages[0].flags, 0x00);
1197 assert_eq!(decoded.messages[0].data, vec![0xAA, 0xBB, 0xCC]);
1198 }
1199
1200 #[test]
1201 fn test_multiple_messages_roundtrip() {
1202 let mut hdr = ObjectHeader::new();
1203 hdr.add_message(0x01, 0x00, vec![1, 2, 3, 4]);
1204 hdr.add_message(0x03, 0x01, vec![10, 20]);
1205 hdr.add_message(0x0C, 0x00, vec![]);
1206
1207 let encoded = hdr.encode().unwrap();
1208 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1209 assert_eq!(consumed, encoded.len());
1210 assert_eq!(decoded.messages.len(), 3);
1211 assert_eq!(decoded, hdr);
1212 }
1213
1214 #[test]
1215 fn test_with_creation_order() {
1216 let mut hdr = ObjectHeader {
1217 flags: 0x02 | FLAG_ATTR_CREATION_ORDER_TRACKED,
1218 times: None,
1219 messages: Vec::new(),
1220 };
1221 hdr.add_message(0x01, 0x00, vec![0xFF; 8]);
1222 hdr.add_message(0x03, 0x00, vec![0xEE; 4]);
1223
1224 let encoded = hdr.encode().unwrap();
1225 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1226 assert_eq!(consumed, encoded.len());
1227 assert_eq!(decoded.messages.len(), 2);
1228 assert_eq!(decoded.messages[0].data, vec![0xFF; 8]);
1229 assert_eq!(decoded.messages[1].data, vec![0xEE; 4]);
1230 }
1231
1232 #[test]
1236 fn a_tracked_header_round_trips_each_message_creation_index() {
1237 let mut hdr = ObjectHeader::new();
1238 hdr.set_attribute_creation_order(CreationOrder::Indexed);
1239 hdr.add_message_indexed(0x0C, 0x00, vec![0xAA; 6], 0);
1240 hdr.add_message_indexed(0x0C, 0x00, vec![0xBB; 6], 1);
1241 hdr.add_message_indexed(0x0C, 0x00, vec![0xCC; 6], 2);
1242
1243 let encoded = hdr.encode().unwrap();
1244 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1245 assert_eq!(consumed, encoded.len());
1246 assert_eq!(decoded, hdr);
1247 let indices: Vec<u16> = decoded.messages.iter().map(|m| m.creation_index).collect();
1248 assert_eq!(indices, vec![0, 1, 2]);
1249
1250 let mut plain = ObjectHeader::new();
1252 plain.add_message(0x0C, 0x00, vec![0xAA; 6]);
1253 assert!(plain.encode().unwrap().len() < encoded.len());
1254 let (plain_back, _) = ObjectHeader::decode(&plain.encode().unwrap()).unwrap();
1255 assert_eq!(plain_back.messages[0].creation_index, 0);
1256 }
1257
1258 #[test]
1262 fn each_attribute_creation_order_state_round_trips_through_the_flags() {
1263 for order in [
1264 CreationOrder::Untracked,
1265 CreationOrder::Tracked,
1266 CreationOrder::Indexed,
1267 ] {
1268 let mut hdr = ObjectHeader::new();
1269 hdr.set_attribute_creation_order(order);
1270 hdr.add_message_indexed(0x0C, 0x00, vec![0xAA; 6], 3);
1271 let (decoded, _) = ObjectHeader::decode(&hdr.encode().unwrap()).unwrap();
1272 assert_eq!(decoded.attribute_creation_order(), order);
1273 let want_index = if order.is_tracked() { 3 } else { 0 };
1274 assert_eq!(decoded.messages[0].creation_index, want_index);
1275 }
1276 }
1277
1278 #[test]
1282 fn setting_a_weaker_policy_clears_the_stronger_one() {
1283 let mut hdr = ObjectHeader::new();
1284 hdr.set_attribute_creation_order(CreationOrder::Indexed);
1285 hdr.set_attribute_creation_order(CreationOrder::Untracked);
1286 assert_eq!(hdr.attribute_creation_order(), CreationOrder::Untracked);
1287 assert_eq!(hdr.flags, 0x02);
1289 }
1290
1291 #[test]
1294 fn stored_times_round_trip_and_set_the_flag() {
1295 let mut hdr = ObjectHeader::new();
1296 hdr.add_message(0x01, 0x00, vec![1, 2, 3]);
1297 let without = hdr.encode().unwrap();
1298
1299 hdr.times = Some(ObjectTimes {
1300 access: 0x0A0A_0A0A,
1301 modification: 0x0B0B_0B0B,
1302 change: 0x0C0C_0C0C,
1303 birth: 0x0D0D_0D0D,
1304 });
1305 let with = hdr.encode().unwrap();
1306
1307 assert_eq!(with.len(), without.len() + 16);
1308 assert_eq!(with[5] & FLAG_STORE_TIMESTAMPS, FLAG_STORE_TIMESTAMPS);
1309 assert_eq!(&with[6..10], &0x0A0A_0A0Au32.to_le_bytes());
1310 assert_eq!(&with[10..14], &0x0B0B_0B0Bu32.to_le_bytes());
1311 assert_eq!(&with[14..18], &0x0C0C_0C0Cu32.to_le_bytes());
1312 assert_eq!(&with[18..22], &0x0D0D_0D0Du32.to_le_bytes());
1313
1314 let (decoded, consumed) = ObjectHeader::decode(&with).expect("decode failed");
1315 assert_eq!(consumed, with.len());
1316 assert_eq!(decoded, hdr);
1317 }
1318
1319 #[test]
1324 fn the_timestamps_flag_is_never_written_without_times() {
1325 let mut hdr = ObjectHeader::new();
1326 hdr.flags |= FLAG_STORE_TIMESTAMPS;
1327 hdr.add_message(0x01, 0x00, vec![1, 2, 3]);
1328
1329 let encoded = hdr.encode().unwrap();
1330 assert_eq!(encoded[5] & FLAG_STORE_TIMESTAMPS, 0);
1331 let (decoded, _) = ObjectHeader::decode(&encoded).expect("decode failed");
1332 assert_eq!(decoded.times, None);
1333 assert_eq!(decoded.flags & FLAG_STORE_TIMESTAMPS, 0);
1334 }
1335
1336 #[test]
1339 fn touching_moves_access_and_change_time_only() {
1340 let before = ObjectTimes {
1341 access: 100,
1342 modification: 200,
1343 change: 300,
1344 birth: 400,
1345 };
1346 assert_eq!(
1347 before.touched(999),
1348 ObjectTimes {
1349 access: 999,
1350 modification: 200,
1351 change: 999,
1352 birth: 400,
1353 }
1354 );
1355 assert_eq!(
1356 ObjectTimes::created_at(7).touched(7),
1357 ObjectTimes::created_at(7)
1358 );
1359 }
1360
1361 #[test]
1362 fn test_chunk0_size_1byte() {
1363 let mut hdr = ObjectHeader {
1365 flags: 0x00,
1366 times: None,
1367 messages: Vec::new(),
1368 };
1369 hdr.add_message(0x01, 0x00, vec![42]);
1370
1371 let encoded = hdr.encode().unwrap();
1372 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1373 assert_eq!(consumed, encoded.len());
1374 assert_eq!(decoded.messages[0].data, vec![42]);
1375 }
1376
1377 #[test]
1378 fn test_chunk0_size_2byte() {
1379 let mut hdr = ObjectHeader {
1381 flags: 0x01,
1382 times: None,
1383 messages: Vec::new(),
1384 };
1385 hdr.add_message(0x01, 0x00, vec![1, 2, 3]);
1386
1387 let encoded = hdr.encode().unwrap();
1388 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1389 assert_eq!(consumed, encoded.len());
1390 assert_eq!(decoded.messages[0].data, vec![1, 2, 3]);
1391 }
1392
1393 #[test]
1394 fn test_chunk0_size_8byte() {
1395 let mut hdr = ObjectHeader {
1397 flags: 0x03,
1398 times: None,
1399 messages: Vec::new(),
1400 };
1401 hdr.add_message(0x01, 0x00, vec![0xDE, 0xAD]);
1402
1403 let encoded = hdr.encode().unwrap();
1404 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1405 assert_eq!(consumed, encoded.len());
1406 assert_eq!(decoded.messages[0].data, vec![0xDE, 0xAD]);
1407 }
1408
1409 #[test]
1410 fn test_decode_bad_signature() {
1411 let mut data = vec![0u8; 20];
1412 data[0..4].copy_from_slice(b"XHDR");
1413 let err = ObjectHeader::decode(&data).unwrap_err();
1414 assert!(matches!(err, FormatError::InvalidSignature));
1415 }
1416
1417 #[test]
1418 fn test_decode_bad_version() {
1419 let hdr = ObjectHeader::new();
1420 let mut encoded = hdr.encode().unwrap();
1421 encoded[4] = 99; let err = ObjectHeader::decode(&encoded).unwrap_err();
1423 assert!(matches!(err, FormatError::InvalidVersion(99)));
1424 }
1425
1426 #[test]
1427 fn test_decode_checksum_mismatch() {
1428 let mut hdr = ObjectHeader::new();
1429 hdr.add_message(0x01, 0x00, vec![1, 2, 3]);
1430 let mut encoded = hdr.encode().unwrap();
1431 let last_data = encoded.len() - 5;
1433 encoded[last_data] ^= 0xFF;
1434 let err = ObjectHeader::decode(&encoded).unwrap_err();
1435 assert!(matches!(err, FormatError::ChecksumMismatch { .. }));
1436 }
1437
1438 #[test]
1439 fn test_decode_buffer_too_short() {
1440 let err = ObjectHeader::decode(&[0u8; 5]).unwrap_err();
1441 assert!(matches!(err, FormatError::BufferTooShort { .. }));
1442 }
1443
1444 #[test]
1445 fn test_decode_with_trailing_data() {
1446 let mut hdr = ObjectHeader::new();
1447 hdr.add_message(0x01, 0x00, vec![7, 8, 9]);
1448 let mut encoded = hdr.encode().unwrap();
1449 let original_len = encoded.len();
1450 encoded.extend_from_slice(&[0xBB; 50]); let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1453 assert_eq!(consumed, original_len);
1454 assert_eq!(decoded, hdr);
1455 }
1456
1457 #[test]
1458 fn test_large_message_payload() {
1459 let mut hdr = ObjectHeader::new();
1460 let big_data = vec![0x42; 1000];
1461 hdr.add_message(0x0C, 0x00, big_data.clone());
1462
1463 let encoded = hdr.encode().unwrap();
1464 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1465 assert_eq!(consumed, encoded.len());
1466 assert_eq!(decoded.messages[0].data.len(), 1000);
1467 assert_eq!(decoded.messages[0].data, big_data);
1468 }
1469
1470 #[test]
1472 fn test_message_payload_at_size_limit() {
1473 let mut hdr = ObjectHeader::new();
1474 hdr.add_message(0x0C, 0x00, vec![0x42; MAX_MESSAGE_SIZE]);
1475
1476 let encoded = hdr.encode().expect("encode at the limit must succeed");
1477 let (decoded, consumed) = ObjectHeader::decode(&encoded).expect("decode failed");
1478 assert_eq!(consumed, encoded.len());
1479 assert_eq!(decoded.messages[0].data.len(), MAX_MESSAGE_SIZE);
1480 }
1481
1482 #[test]
1486 fn test_message_payload_over_size_limit_is_refused() {
1487 let mut hdr = ObjectHeader::new();
1488 hdr.add_message(0x01, 0x00, vec![7; 4]);
1489 hdr.add_message(0x0C, 0x00, vec![0x42; MAX_MESSAGE_SIZE + 1]);
1490
1491 let err = hdr.encode().expect_err("over the limit must not encode");
1492 let msg = err.to_string();
1493 assert!(msg.contains("0x0C"), "{msg}");
1494 assert!(msg.contains(&(MAX_MESSAGE_SIZE + 1).to_string()), "{msg}");
1495 }
1496
1497 #[test]
1498 fn test_default() {
1499 let hdr = ObjectHeader::default();
1500 assert_eq!(hdr.flags, 0x02);
1501 assert!(hdr.messages.is_empty());
1502 }
1503
1504 fn chunked_header() -> ObjectHeader {
1506 let mut hdr = ObjectHeader::new();
1507 for (i, t) in [0x02u8, 0x0A, 0x0C].iter().enumerate() {
1508 hdr.add_message(*t, 0x00, vec![i as u8; 40]);
1509 }
1510 hdr
1511 }
1512
1513 #[test]
1516 fn a_capacity_that_fits_every_message_plans_one_chunk() {
1517 let hdr = chunked_header();
1518 let ctx = FormatContext::default_v3();
1519 let plan = hdr.plan_chunks(1024, &ctx).unwrap();
1520 assert_eq!(plan.continuation_size, 0);
1521 let (chunk0, continuation) = hdr.encode_chunked(&plan, &ctx, 0x1000).unwrap();
1522 assert!(continuation.is_none());
1523 assert_eq!(chunk0, hdr.encode().unwrap());
1524 assert_eq!(chunk0.len(), plan.chunk0_size);
1525 }
1526
1527 #[test]
1531 fn messages_past_the_capacity_move_into_a_continuation_chunk() {
1532 let hdr = chunked_header();
1533 let ctx = FormatContext::default_v3();
1534 let plan = hdr.plan_chunks(64, &ctx).unwrap();
1536 assert_eq!(plan.continuation_size, 4 + 2 * 44 + 4);
1537 let (chunk0, continuation) = hdr.encode_chunked(&plan, &ctx, 0x2000).unwrap();
1538 let continuation = continuation.unwrap();
1539 assert_eq!(chunk0.len(), plan.chunk0_size);
1540 assert_eq!(continuation.len(), plan.continuation_size);
1541 assert_eq!(&continuation[..4], &OCHK_SIGNATURE);
1542
1543 let (decoded, consumed) = ObjectHeader::decode(&chunk0).unwrap();
1544 assert_eq!(consumed, chunk0.len());
1545 assert_eq!(
1546 decoded.messages.len(),
1547 2,
1548 "the first message and the pointer"
1549 );
1550 assert_eq!(decoded.messages[0], hdr.messages[0]);
1551 let pointer = &decoded.messages[1];
1552 assert_eq!(pointer.msg_type, MSG_CONTINUATION);
1553 assert_eq!(
1554 u64::from_le_bytes(pointer.data[..8].try_into().unwrap()),
1555 0x2000
1556 );
1557 assert_eq!(
1558 u64::from_le_bytes(pointer.data[8..16].try_into().unwrap()),
1559 plan.continuation_size as u64
1560 );
1561
1562 let body = &continuation[..continuation.len() - 4];
1563 let stored = u32::from_le_bytes(continuation[continuation.len() - 4..].try_into().unwrap());
1564 assert_eq!(stored, checksum_metadata(body));
1565 }
1566
1567 #[test]
1570 fn leftover_chunk_zero_space_is_a_nil_message_or_a_gap() {
1571 let hdr = chunked_header();
1572 let ctx = FormatContext::default_v3();
1573
1574 let (chunk0, _) = hdr
1576 .encode_chunked(&hdr.plan_chunks(72, &ctx).unwrap(), &ctx, 0x2000)
1577 .unwrap();
1578 let tail = &chunk0[chunk0.len() - 4 - 8..chunk0.len() - 4];
1579 assert_eq!(tail, [MSG_NIL, 4, 0, 0, 0, 0, 0, 0]);
1580
1581 let (chunk0, _) = hdr
1583 .encode_chunked(&hdr.plan_chunks(67, &ctx).unwrap(), &ctx, 0x2000)
1584 .unwrap();
1585 assert_eq!(&chunk0[chunk0.len() - 4 - 3..chunk0.len() - 4], [0, 0, 0]);
1586 let (decoded, _) = ObjectHeader::decode(&chunk0).unwrap();
1588 assert_eq!(decoded.messages.len(), 2);
1589 }
1590
1591 #[test]
1594 fn a_capacity_below_the_continuation_message_is_refused() {
1595 let hdr = chunked_header();
1596 let err = hdr
1597 .plan_chunks(19, &FormatContext::default_v3())
1598 .expect_err("19 bytes cannot hold a 20-byte continuation message");
1599 assert!(err.to_string().contains("continuation chunk"), "{err}");
1600 }
1601
1602 #[test]
1611 fn the_times_prefix_widens_chunk_zero_without_moving_the_split() {
1612 let ctx = FormatContext::default_v3();
1613 let plain = chunked_header();
1614 let mut timed = chunked_header();
1615 timed.times = Some(ObjectTimes::created_at(0x5EED_1234));
1616
1617 for capacity in [72usize, 120, 1024] {
1618 let a = plain.plan_chunks(capacity, &ctx).unwrap();
1619 let b = timed.plan_chunks(capacity, &ctx).unwrap();
1620 assert_eq!(a.split, b.split, "capacity {capacity}: same split");
1621 assert_eq!(
1622 a.continuation_size, b.continuation_size,
1623 "capacity {capacity}: same continuation"
1624 );
1625 assert_eq!(
1626 b.chunk0_size,
1627 a.chunk0_size + 16,
1628 "capacity {capacity}: four times of four bytes"
1629 );
1630 }
1631
1632 let plan = timed.plan_chunks(72, &ctx).unwrap();
1635 let (chunk0, continuation) = timed.encode_chunked(&plan, &ctx, 0x2000).unwrap();
1636 assert_eq!(chunk0.len(), plan.chunk0_size);
1637 assert_eq!(continuation.unwrap().len(), plan.continuation_size);
1638 let (decoded, _) = ObjectHeader::decode(&chunk0).unwrap();
1639 assert_eq!(decoded.times, timed.times);
1640 }
1641
1642 #[test]
1651 fn a_version_one_header_is_never_planned_into_chunks() {
1652 let hdr = chunked_header();
1653 let v1 = hdr
1655 .encode_for(crate::format::ObjectFormat::Legacy, 1)
1656 .unwrap();
1657 assert_eq!(v1[0], 1, "version-1 prefix");
1658 assert!(
1659 !v1.windows(4).any(|w| w == OCHK_SIGNATURE),
1660 "a version-1 header holds no OCHK chunk"
1661 );
1662 let (decoded, _) = ObjectHeader::decode_v1(&v1).unwrap();
1663 assert_eq!(decoded.messages.len(), hdr.messages.len());
1664 assert!(
1665 !decoded
1666 .messages
1667 .iter()
1668 .any(|m| m.msg_type == MSG_CONTINUATION),
1669 "no continuation message"
1670 );
1671 }
1672}