1use crate::checksum::jenkins_lookup3;
18use crate::error::{Error, Result};
19use crate::io::Cursor;
20use crate::messages::shared::SharedMessage;
21use crate::messages::{parse_message, HdfMessage};
22use crate::storage::Storage;
23
24const OHDR_SIGNATURE: [u8; 4] = *b"OHDR";
26
27const OCHK_SIGNATURE: [u8; 4] = *b"OCHK";
29
30const MAX_HEADER_CONTINUATIONS: usize = 1024;
33
34fn enter_continuation(visited: &mut std::collections::HashSet<u64>, offset: u64) -> Result<()> {
37 if !visited.insert(offset) {
38 return Err(Error::InvalidData(format!(
39 "object header continuation cycle at offset {offset:#x}"
40 )));
41 }
42 if visited.len() > MAX_HEADER_CONTINUATIONS {
43 return Err(Error::InvalidData(format!(
44 "object header exceeds {MAX_HEADER_CONTINUATIONS} continuation chunks"
45 )));
46 }
47 Ok(())
48}
49
50const MSG_TYPE_CONTINUATION: u16 = 0x0010;
52
53const MSG_TYPE_NIL: u16 = 0x0000;
55
56fn checked_usize(value: u64, context: &str) -> Result<usize> {
57 usize::try_from(value).map_err(|_| {
58 Error::InvalidData(format!(
59 "{context} value {value} exceeds platform usize capacity"
60 ))
61 })
62}
63
64fn checked_add_usize(lhs: usize, rhs: usize, context: &str) -> Result<usize> {
65 lhs.checked_add(rhs)
66 .ok_or_else(|| Error::InvalidData(format!("{context} exceeds platform usize capacity")))
67}
68
69#[derive(Debug, Clone)]
71pub struct ObjectHeader {
72 pub version: u8,
74 pub messages: Vec<HdfMessage>,
76 pub reference_count: u32,
78 pub modification_time: Option<u32>,
81}
82
83impl ObjectHeader {
84 pub fn parse_at(data: &[u8], address: u64, offset_size: u8, length_size: u8) -> Result<Self> {
90 let mut cursor = Cursor::new(data);
91 cursor.set_position(address);
92
93 let sig = cursor.peek_bytes(4)?;
95 if sig == OHDR_SIGNATURE {
96 Self::parse_v2(&cursor, address, offset_size, length_size)
97 } else {
98 Self::parse_v1(&cursor, address, offset_size, length_size)
99 }
100 }
101
102 pub fn parse_at_storage(
104 storage: &dyn Storage,
105 address: u64,
106 offset_size: u8,
107 length_size: u8,
108 ) -> Result<Self> {
109 let prefix = storage.read_range(address, 4)?;
110 if prefix.len() < 4 {
111 return Err(Error::UnexpectedEof {
112 offset: address,
113 needed: 4,
114 available: prefix.len() as u64,
115 });
116 }
117
118 if prefix.as_ref()[..4] == OHDR_SIGNATURE {
119 Self::parse_v2_storage(storage, address, offset_size, length_size)
120 } else {
121 Self::parse_v1_storage(storage, address, offset_size, length_size)
122 }
123 }
124
125 pub fn resolve_shared_messages(
131 &mut self,
132 data: &[u8],
133 offset_size: u8,
134 length_size: u8,
135 ) -> Result<()> {
136 let old_messages = std::mem::take(&mut self.messages);
137 let mut resolved = Vec::with_capacity(old_messages.len());
138 for msg in old_messages {
139 match msg {
140 HdfMessage::Shared(SharedMessage::SharedInOhdr {
141 message_type,
142 address,
143 }) => {
144 match Self::parse_at(data, address, offset_size, length_size) {
145 Ok(target_header) => {
146 if let Some(target_msg) =
147 select_shared_message(target_header, message_type)
148 {
149 resolved.push(target_msg);
150 }
151 }
152 Err(_) => {
153 resolved.push(HdfMessage::Shared(SharedMessage::SharedInOhdr {
155 message_type,
156 address,
157 }));
158 }
159 }
160 }
161 HdfMessage::Shared(SharedMessage::SharedInSohm { .. }) => {
162 self.messages = resolved;
163 return Err(Error::Other(
164 "SOHM table lookup not yet supported — file uses shared object header messages".to_string(),
165 ));
166 }
167 other => resolved.push(other),
168 }
169 }
170 self.messages = resolved;
171 Ok(())
172 }
173
174 pub(crate) fn resolve_shared_messages_storage_with_sohm<F>(
176 &mut self,
177 storage: &dyn Storage,
178 offset_size: u8,
179 length_size: u8,
180 mut resolve_sohm: F,
181 ) -> Result<()>
182 where
183 F: FnMut(&[u8], u16) -> Result<Option<HdfMessage>>,
184 {
185 let old_messages = std::mem::take(&mut self.messages);
186 let mut resolved = Vec::with_capacity(old_messages.len());
187 for msg in old_messages {
188 match msg {
189 HdfMessage::Shared(SharedMessage::SharedInOhdr {
190 message_type,
191 address,
192 }) => match Self::parse_at_storage(storage, address, offset_size, length_size) {
193 Ok(target_header) => {
194 if let Some(target_msg) = select_shared_message(target_header, message_type)
195 {
196 resolved.push(target_msg);
197 }
198 }
199 Err(_) => {
200 resolved.push(HdfMessage::Shared(SharedMessage::SharedInOhdr {
201 message_type,
202 address,
203 }));
204 }
205 },
206 HdfMessage::Shared(SharedMessage::SharedInSohm {
207 message_type,
208 heap_id,
209 }) => {
210 if let Some(message) = resolve_sohm(&heap_id, message_type)? {
211 resolved.push(message);
212 } else {
213 self.messages = resolved;
214 return Err(Error::Other(format!(
215 "SOHM entry for message type {message_type:#x} not found"
216 )));
217 }
218 }
219 other => resolved.push(other),
220 }
221 }
222 self.messages = resolved;
223 Ok(())
224 }
225
226 fn parse_v1(base: &Cursor<'_>, address: u64, offset_size: u8, length_size: u8) -> Result<Self> {
242 let mut cursor = base.at_offset(address)?;
243
244 let version = cursor.read_u8()?;
245 if version != 1 {
246 return Err(Error::UnsupportedObjectHeaderVersion(version));
247 }
248
249 let _reserved = cursor.read_u8()?;
250 let num_messages = cursor.read_u16_le()?;
251 let reference_count = cursor.read_u32_le()?;
252 let header_data_size = cursor.read_u32_le()? as u64;
253 let _reserved2 = cursor.read_u32_le()?; let messages_start = cursor.position();
257 let messages_end = messages_start + header_data_size;
258
259 let mut messages: Vec<HdfMessage> = Vec::with_capacity(num_messages as usize);
260 let mut continuations: Vec<(u64, u64)> = Vec::new();
261
262 Self::read_v1_messages(
263 base,
264 messages_start,
265 messages_end,
266 offset_size,
267 length_size,
268 &mut messages,
269 &mut continuations,
270 )?;
271
272 let mut visited = std::collections::HashSet::new();
274 while let Some((cont_offset, cont_length)) = continuations.pop() {
275 enter_continuation(&mut visited, cont_offset)?;
276 let cont_end = cont_offset.checked_add(cont_length).ok_or_else(|| {
277 Error::InvalidData("v1 object header continuation end overflows u64".to_string())
278 })?;
279 Self::read_v1_messages(
280 base,
281 cont_offset,
282 cont_end,
283 offset_size,
284 length_size,
285 &mut messages,
286 &mut continuations,
287 )?;
288 }
289
290 Ok(ObjectHeader {
291 version: 1,
292 messages,
293 reference_count,
294 modification_time: None,
295 })
296 }
297
298 fn parse_v1_storage(
299 storage: &dyn Storage,
300 address: u64,
301 offset_size: u8,
302 length_size: u8,
303 ) -> Result<Self> {
304 let header = storage.read_range(address, 16)?;
305 let mut cursor = Cursor::new(header.as_ref());
306
307 let version = cursor.read_u8()?;
308 if version != 1 {
309 return Err(Error::UnsupportedObjectHeaderVersion(version));
310 }
311
312 let _reserved = cursor.read_u8()?;
313 let num_messages = cursor.read_u16_le()?;
314 let reference_count = cursor.read_u32_le()?;
315 let header_data_size = cursor.read_u32_le()? as u64;
316 let _reserved2 = cursor.read_u32_le()?;
317
318 let header_data_size = checked_usize(header_data_size, "v1 object header data size")?;
319 let first_chunk_len =
320 checked_add_usize(16, header_data_size, "v1 object header chunk length")?;
321 let first_chunk = storage.read_range(address, first_chunk_len)?;
322 let mut messages = Vec::with_capacity(num_messages as usize);
323 let mut continuations = Vec::new();
324 Self::read_v1_messages_from_slice(
325 &first_chunk.as_ref()[16..],
326 offset_size,
327 length_size,
328 &mut messages,
329 &mut continuations,
330 )?;
331
332 let mut visited = std::collections::HashSet::new();
333 while let Some((cont_offset, cont_length)) = continuations.pop() {
334 enter_continuation(&mut visited, cont_offset)?;
335 let cont_length = checked_usize(cont_length, "v1 object header continuation length")?;
336 let chunk = storage.read_range(cont_offset, cont_length)?;
337 Self::read_v1_messages_from_slice(
338 chunk.as_ref(),
339 offset_size,
340 length_size,
341 &mut messages,
342 &mut continuations,
343 )?;
344 }
345
346 Ok(ObjectHeader {
347 version: 1,
348 messages,
349 reference_count,
350 modification_time: None,
351 })
352 }
353
354 fn read_v1_messages(
358 base: &Cursor<'_>,
359 start: u64,
360 end: u64,
361 offset_size: u8,
362 length_size: u8,
363 messages: &mut Vec<HdfMessage>,
364 continuations: &mut Vec<(u64, u64)>,
365 ) -> Result<()> {
366 let mut cursor = base.at_offset(start)?;
367
368 while cursor.position() + 8 <= end {
369 let msg_type = cursor.read_u16_le()?;
370 let msg_data_size = cursor.read_u16_le()? as usize;
371 let msg_flags = cursor.read_u8()?;
372 let _reserved = cursor.read_bytes(3)?; if cursor.position() + msg_data_size as u64 > end {
376 return Err(Error::InvalidData(format!(
377 "v1 message data ({} bytes) extends past header chunk end",
378 msg_data_size
379 )));
380 }
381
382 if msg_type == MSG_TYPE_NIL {
383 cursor.skip(msg_data_size)?;
385 messages.push(HdfMessage::Nil);
386 continue;
387 }
388
389 let msg_data = cursor.read_bytes(msg_data_size)?;
390 let is_shared = (msg_flags & 0x02) != 0;
391
392 if is_shared {
393 let shared_msg = crate::messages::shared::parse(
396 &mut Cursor::new(msg_data),
397 msg_type,
398 offset_size,
399 length_size,
400 msg_data_size,
401 )?;
402 messages.push(HdfMessage::Shared(shared_msg));
403 } else if msg_type == MSG_TYPE_CONTINUATION {
404 let cont = crate::messages::continuation::parse(
407 &mut Cursor::new(msg_data),
408 offset_size,
409 length_size,
410 msg_data_size,
411 )?;
412 continuations.push((cont.offset, cont.length));
413 messages.push(HdfMessage::ObjectHeaderContinuation);
414 } else {
415 let parsed = parse_message(
416 msg_type,
417 msg_data.len(),
418 &mut Cursor::new(msg_data),
419 offset_size,
420 length_size,
421 )?;
422 messages.push(parsed);
423 }
424 }
425
426 Ok(())
427 }
428
429 fn parse_v2(base: &Cursor<'_>, address: u64, offset_size: u8, length_size: u8) -> Result<Self> {
447 let mut cursor = base.at_offset(address)?;
448
449 let sig = cursor.read_bytes(4)?;
451 if sig != OHDR_SIGNATURE {
452 return Err(Error::InvalidObjectHeaderSignature);
453 }
454 let version = cursor.read_u8()?;
455 if version != 2 {
456 return Err(Error::UnsupportedObjectHeaderVersion(version));
457 }
458 let flags = cursor.read_u8()?;
459
460 let modification_time = if (flags & 0x20) != 0 {
462 let _access_time = cursor.read_u32_le()?;
463 let mod_time = cursor.read_u32_le()?;
464 let _change_time = cursor.read_u32_le()?;
465 let _birth_time = cursor.read_u32_le()?;
466 Some(mod_time)
467 } else {
468 None
469 };
470
471 if (flags & 0x10) != 0 {
473 let _max_compact = cursor.read_u16_le()?;
474 let _min_dense = cursor.read_u16_le()?;
475 }
476
477 let size_field_width = 1usize << (flags & 0x03);
479 let chunk0_data_size = cursor.read_uvar(size_field_width)?;
480
481 let creation_order_tracked = (flags & 0x04) != 0;
483
484 let messages_start = cursor.position();
488 let chunk0_end = messages_start + chunk0_data_size;
489
490 let checksum_start = address as usize;
493 let checksum_end = chunk0_end as usize; let stored_checksum = {
495 let mut ck = base.at_offset(chunk0_end)?;
496 ck.read_u32_le()?
497 };
498 let computed = jenkins_lookup3(&base.data()[checksum_start..checksum_end]);
499 if computed != stored_checksum {
500 return Err(Error::ChecksumMismatch {
501 expected: stored_checksum,
502 actual: computed,
503 });
504 }
505
506 let mut messages: Vec<HdfMessage> = Vec::new();
507 let mut continuations: Vec<(u64, u64)> = Vec::new();
508
509 Self::read_v2_messages(
510 base,
511 messages_start,
512 chunk0_end,
513 offset_size,
514 length_size,
515 creation_order_tracked,
516 &mut messages,
517 &mut continuations,
518 )?;
519
520 let mut visited = std::collections::HashSet::new();
522 while let Some((cont_offset, cont_length)) = continuations.pop() {
523 enter_continuation(&mut visited, cont_offset)?;
524 Self::read_v2_continuation_chunk(
525 base,
526 cont_offset,
527 cont_length,
528 offset_size,
529 length_size,
530 creation_order_tracked,
531 &mut messages,
532 &mut continuations,
533 )?;
534 }
535
536 Ok(ObjectHeader {
537 version: 2,
538 messages,
539 reference_count: 0, modification_time,
541 })
542 }
543
544 fn parse_v2_storage(
545 storage: &dyn Storage,
546 address: u64,
547 offset_size: u8,
548 length_size: u8,
549 ) -> Result<Self> {
550 let fixed_prefix = storage.read_range(address, 6)?;
551 let mut cursor = Cursor::new(fixed_prefix.as_ref());
552
553 let sig = cursor.read_bytes(4)?;
554 if sig != OHDR_SIGNATURE {
555 return Err(Error::InvalidObjectHeaderSignature);
556 }
557 let version = cursor.read_u8()?;
558 if version != 2 {
559 return Err(Error::UnsupportedObjectHeaderVersion(version));
560 }
561 let flags = cursor.read_u8()?;
562
563 let size_field_width = 1usize << (flags & 0x03);
564 let mut prefix_len = 6usize;
565 if (flags & 0x20) != 0 {
566 prefix_len = checked_add_usize(prefix_len, 16, "v2 object header prefix length")?;
567 }
568 if (flags & 0x10) != 0 {
569 prefix_len = checked_add_usize(prefix_len, 4, "v2 object header prefix length")?;
570 }
571 prefix_len = checked_add_usize(
572 prefix_len,
573 size_field_width,
574 "v2 object header prefix length",
575 )?;
576
577 let prefix = storage.read_range(address, prefix_len)?;
578 let mut cursor = Cursor::new(prefix.as_ref());
579 let sig = cursor.read_bytes(4)?;
580 if sig != OHDR_SIGNATURE {
581 return Err(Error::InvalidObjectHeaderSignature);
582 }
583 let version = cursor.read_u8()?;
584 if version != 2 {
585 return Err(Error::UnsupportedObjectHeaderVersion(version));
586 }
587 let flags = cursor.read_u8()?;
588
589 let modification_time = if (flags & 0x20) != 0 {
590 let _access_time = cursor.read_u32_le()?;
591 let mod_time = cursor.read_u32_le()?;
592 let _change_time = cursor.read_u32_le()?;
593 let _birth_time = cursor.read_u32_le()?;
594 Some(mod_time)
595 } else {
596 None
597 };
598
599 if (flags & 0x10) != 0 {
600 let _max_compact = cursor.read_u16_le()?;
601 let _min_dense = cursor.read_u16_le()?;
602 }
603
604 let chunk0_data_size = cursor.read_uvar(size_field_width)?;
605 let creation_order_tracked = (flags & 0x04) != 0;
606 let messages_start = checked_usize(cursor.position(), "v2 object header message start")?;
607 let chunk0_data_size = checked_usize(chunk0_data_size, "v2 object header chunk0 size")?;
608 let chunk0_end = checked_add_usize(
609 messages_start,
610 chunk0_data_size,
611 "v2 object header chunk0 end",
612 )?;
613
614 let chunk_with_checksum_len =
615 checked_add_usize(chunk0_end, 4, "v2 object header chunk0 checksum end")?;
616 let chunk = storage.read_range(address, chunk_with_checksum_len)?;
617 let stored_checksum = u32::from_le_bytes(
618 chunk.as_ref()[chunk0_end..chunk0_end + 4]
619 .try_into()
620 .unwrap(),
621 );
622 let computed = jenkins_lookup3(&chunk.as_ref()[..chunk0_end]);
623 if computed != stored_checksum {
624 return Err(Error::ChecksumMismatch {
625 expected: stored_checksum,
626 actual: computed,
627 });
628 }
629
630 let mut messages = Vec::new();
631 let mut continuations = Vec::new();
632 Self::read_v2_messages_from_slice(
633 &chunk.as_ref()[messages_start..chunk0_end],
634 offset_size,
635 length_size,
636 creation_order_tracked,
637 &mut messages,
638 &mut continuations,
639 )?;
640
641 let mut visited = std::collections::HashSet::new();
642 while let Some((cont_offset, cont_length)) = continuations.pop() {
643 enter_continuation(&mut visited, cont_offset)?;
644 Self::read_v2_continuation_chunk_storage(
645 storage,
646 cont_offset,
647 cont_length,
648 offset_size,
649 length_size,
650 creation_order_tracked,
651 &mut messages,
652 &mut continuations,
653 )?;
654 }
655
656 Ok(ObjectHeader {
657 version: 2,
658 messages,
659 reference_count: 0,
660 modification_time,
661 })
662 }
663
664 #[allow(clippy::too_many_arguments)]
666 fn read_v2_messages(
667 base: &Cursor<'_>,
668 start: u64,
669 end: u64,
670 offset_size: u8,
671 length_size: u8,
672 creation_order_tracked: bool,
673 messages: &mut Vec<HdfMessage>,
674 continuations: &mut Vec<(u64, u64)>,
675 ) -> Result<()> {
676 let mut cursor = base.at_offset(start)?;
677
678 let min_envelope = if creation_order_tracked { 6 } else { 4 };
681
682 while cursor.position() + min_envelope as u64 <= end {
683 let msg_type = cursor.read_u8()? as u16;
684 let msg_data_size = cursor.read_u16_le()? as usize;
685 let msg_flags = cursor.read_u8()?;
686
687 if creation_order_tracked {
688 let _creation_order = cursor.read_u16_le()?;
689 }
690
691 if msg_type == MSG_TYPE_NIL {
692 if msg_data_size == 0
693 && base.data()[cursor.position() as usize..end as usize]
694 .iter()
695 .all(|byte| *byte == 0)
696 {
697 break;
698 }
699 cursor.skip(msg_data_size)?;
700 messages.push(HdfMessage::Nil);
701 continue;
702 }
703
704 if cursor.position() + msg_data_size as u64 > end {
705 return Err(Error::InvalidData(format!(
706 "v2 message data ({} bytes) extends past chunk end",
707 msg_data_size
708 )));
709 }
710
711 let msg_data = cursor.read_bytes(msg_data_size)?;
712 let is_shared = (msg_flags & 0x02) != 0;
713
714 if is_shared {
715 let shared_msg = crate::messages::shared::parse(
716 &mut Cursor::new(msg_data),
717 msg_type,
718 offset_size,
719 length_size,
720 msg_data_size,
721 )?;
722 messages.push(HdfMessage::Shared(shared_msg));
723 } else if msg_type == MSG_TYPE_CONTINUATION {
724 let cont = crate::messages::continuation::parse(
725 &mut Cursor::new(msg_data),
726 offset_size,
727 length_size,
728 msg_data_size,
729 )?;
730 continuations.push((cont.offset, cont.length));
731 messages.push(HdfMessage::ObjectHeaderContinuation);
732 } else {
733 let parsed = parse_message(
734 msg_type,
735 msg_data.len(),
736 &mut Cursor::new(msg_data),
737 offset_size,
738 length_size,
739 )?;
740 messages.push(parsed);
741 }
742 }
743
744 Ok(())
745 }
746
747 fn read_v1_messages_from_slice(
748 data: &[u8],
749 offset_size: u8,
750 length_size: u8,
751 messages: &mut Vec<HdfMessage>,
752 continuations: &mut Vec<(u64, u64)>,
753 ) -> Result<()> {
754 let mut cursor = Cursor::new(data);
755 while cursor.remaining() >= 8 {
756 let msg_type = cursor.read_u16_le()?;
757 let msg_data_size = cursor.read_u16_le()? as usize;
758 let msg_flags = cursor.read_u8()?;
759 let _reserved = cursor.read_bytes(3)?;
760
761 if cursor.remaining() < msg_data_size as u64 {
762 return Err(Error::InvalidData(format!(
763 "v1 message data ({} bytes) extends past header chunk end",
764 msg_data_size
765 )));
766 }
767
768 if msg_type == MSG_TYPE_NIL {
769 cursor.skip(msg_data_size)?;
770 messages.push(HdfMessage::Nil);
771 continue;
772 }
773
774 let msg_data = cursor.read_bytes(msg_data_size)?;
775 let is_shared = (msg_flags & 0x02) != 0;
776 if is_shared {
777 let shared_msg = crate::messages::shared::parse(
778 &mut Cursor::new(msg_data),
779 msg_type,
780 offset_size,
781 length_size,
782 msg_data_size,
783 )?;
784 messages.push(HdfMessage::Shared(shared_msg));
785 } else if msg_type == MSG_TYPE_CONTINUATION {
786 let cont = crate::messages::continuation::parse(
787 &mut Cursor::new(msg_data),
788 offset_size,
789 length_size,
790 msg_data_size,
791 )?;
792 continuations.push((cont.offset, cont.length));
793 messages.push(HdfMessage::ObjectHeaderContinuation);
794 } else {
795 let parsed = parse_message(
796 msg_type,
797 msg_data.len(),
798 &mut Cursor::new(msg_data),
799 offset_size,
800 length_size,
801 )?;
802 messages.push(parsed);
803 }
804 }
805 Ok(())
806 }
807
808 fn read_v2_messages_from_slice(
809 data: &[u8],
810 offset_size: u8,
811 length_size: u8,
812 creation_order_tracked: bool,
813 messages: &mut Vec<HdfMessage>,
814 continuations: &mut Vec<(u64, u64)>,
815 ) -> Result<()> {
816 let mut cursor = Cursor::new(data);
817 let min_envelope = if creation_order_tracked { 6 } else { 4 };
818
819 while cursor.remaining() >= min_envelope as u64 {
820 let msg_type = cursor.read_u8()? as u16;
821 let msg_data_size = cursor.read_u16_le()? as usize;
822 let msg_flags = cursor.read_u8()?;
823
824 if creation_order_tracked {
825 let _creation_order = cursor.read_u16_le()?;
826 }
827
828 if msg_type == MSG_TYPE_NIL {
829 if msg_data_size == 0
830 && data[cursor.position() as usize..]
831 .iter()
832 .all(|byte| *byte == 0)
833 {
834 break;
835 }
836 cursor.skip(msg_data_size)?;
837 messages.push(HdfMessage::Nil);
838 continue;
839 }
840
841 if cursor.remaining() < msg_data_size as u64 {
842 return Err(Error::InvalidData(format!(
843 "v2 message data ({} bytes) extends past chunk end",
844 msg_data_size
845 )));
846 }
847
848 let msg_data = cursor.read_bytes(msg_data_size)?;
849 let is_shared = (msg_flags & 0x02) != 0;
850 if is_shared {
851 let shared_msg = crate::messages::shared::parse(
852 &mut Cursor::new(msg_data),
853 msg_type,
854 offset_size,
855 length_size,
856 msg_data_size,
857 )?;
858 messages.push(HdfMessage::Shared(shared_msg));
859 } else if msg_type == MSG_TYPE_CONTINUATION {
860 let cont = crate::messages::continuation::parse(
861 &mut Cursor::new(msg_data),
862 offset_size,
863 length_size,
864 msg_data_size,
865 )?;
866 continuations.push((cont.offset, cont.length));
867 messages.push(HdfMessage::ObjectHeaderContinuation);
868 } else {
869 let parsed = parse_message(
870 msg_type,
871 msg_data.len(),
872 &mut Cursor::new(msg_data),
873 offset_size,
874 length_size,
875 )?;
876 messages.push(parsed);
877 }
878 }
879
880 Ok(())
881 }
882
883 #[allow(clippy::too_many_arguments)]
884 fn read_v2_continuation_chunk_storage(
885 storage: &dyn Storage,
886 cont_offset: u64,
887 cont_length: u64,
888 offset_size: u8,
889 length_size: u8,
890 creation_order_tracked: bool,
891 messages: &mut Vec<HdfMessage>,
892 continuations: &mut Vec<(u64, u64)>,
893 ) -> Result<()> {
894 let cont_length = checked_usize(cont_length, "v2 object header continuation length")?;
895 let chunk = storage.read_range(cont_offset, cont_length)?;
896 if chunk.len() < 8 || chunk.as_ref()[..4] != OCHK_SIGNATURE {
897 return Err(Error::InvalidObjectHeaderSignature);
898 }
899 let messages_end = chunk.len() - 4;
900 let stored_checksum = u32::from_le_bytes(
901 chunk.as_ref()[messages_end..messages_end + 4]
902 .try_into()
903 .unwrap(),
904 );
905 let computed = jenkins_lookup3(&chunk.as_ref()[..messages_end]);
906 if computed != stored_checksum {
907 return Err(Error::ChecksumMismatch {
908 expected: stored_checksum,
909 actual: computed,
910 });
911 }
912
913 Self::read_v2_messages_from_slice(
914 &chunk.as_ref()[4..messages_end],
915 offset_size,
916 length_size,
917 creation_order_tracked,
918 messages,
919 continuations,
920 )
921 }
922
923 #[allow(clippy::too_many_arguments)]
925 fn read_v2_continuation_chunk(
933 base: &Cursor<'_>,
934 cont_offset: u64,
935 cont_length: u64,
936 offset_size: u8,
937 length_size: u8,
938 creation_order_tracked: bool,
939 messages: &mut Vec<HdfMessage>,
940 continuations: &mut Vec<(u64, u64)>,
941 ) -> Result<()> {
942 let mut cursor = base.at_offset(cont_offset)?;
943
944 let sig = cursor.read_bytes(4)?;
945 if sig != OCHK_SIGNATURE {
946 return Err(Error::InvalidObjectHeaderSignature);
947 }
948
949 let chunk_end = cont_offset + cont_length;
950 let messages_end = chunk_end - 4;
952 let messages_start = cursor.position(); let checksum_start = cont_offset as usize;
956 let checksum_end = messages_end as usize;
957 let stored_checksum = {
958 let mut ck = base.at_offset(messages_end)?;
959 ck.read_u32_le()?
960 };
961 let computed = jenkins_lookup3(&base.data()[checksum_start..checksum_end]);
962 if computed != stored_checksum {
963 return Err(Error::ChecksumMismatch {
964 expected: stored_checksum,
965 actual: computed,
966 });
967 }
968
969 Self::read_v2_messages(
970 base,
971 messages_start,
972 messages_end,
973 offset_size,
974 length_size,
975 creation_order_tracked,
976 messages,
977 continuations,
978 )
979 }
980}
981
982fn select_shared_message(header: ObjectHeader, message_type: u16) -> Option<HdfMessage> {
983 let mut first_real_message = None;
984 for message in header.messages {
985 match message {
986 HdfMessage::Nil | HdfMessage::ObjectHeaderContinuation | HdfMessage::Shared(_) => {
987 continue;
988 }
989 other if message_matches_type(&other, message_type) => return Some(other),
990 other => {
991 if first_real_message.is_none() {
992 first_real_message = Some(other);
993 }
994 }
995 }
996 }
997 first_real_message
998}
999
1000fn message_matches_type(message: &HdfMessage, message_type: u16) -> bool {
1001 use crate::messages::*;
1002
1003 matches!(
1004 (message_type, message),
1005 (MSG_DATASPACE, HdfMessage::Dataspace(_))
1006 | (MSG_DATATYPE, HdfMessage::Datatype(_))
1007 | (MSG_FILL_VALUE, HdfMessage::FillValue(_))
1008 | (MSG_FILL_VALUE_OLD, HdfMessage::FillValue(_))
1009 | (MSG_DATA_LAYOUT, HdfMessage::DataLayout(_))
1010 | (MSG_FILTER_PIPELINE, HdfMessage::FilterPipeline(_))
1011 | (MSG_ATTRIBUTE, HdfMessage::Attribute(_))
1012 | (MSG_ATTRIBUTE_INFO, HdfMessage::AttributeInfo(_))
1013 | (MSG_LINK, HdfMessage::Link(_))
1014 | (MSG_LINK_INFO, HdfMessage::LinkInfo(_))
1015 | (MSG_GROUP_INFO, HdfMessage::GroupInfo(_))
1016 | (MSG_SYMBOL_TABLE, HdfMessage::SymbolTable(_))
1017 | (MSG_CONTINUATION, HdfMessage::Continuation(_))
1018 | (MSG_MODIFICATION_TIME, HdfMessage::ModificationTime(_))
1019 | (MSG_MODIFICATION_TIME_OLD, HdfMessage::ModificationTime(_))
1020 | (MSG_BTREE_K, HdfMessage::BTreeK(_))
1021 | (MSG_EXTERNAL_FILES, HdfMessage::ExternalFiles(_))
1022 | (MSG_SHARED_TABLE, HdfMessage::SharedTable(_))
1023 | (MSG_COMMENT, HdfMessage::Comment(_))
1024 | (MSG_REFERENCE_COUNT, HdfMessage::ReferenceCount(_))
1025 )
1026}
1027
1028#[cfg(test)]
1029mod tests {
1030 use super::*;
1031 use crate::checksum::jenkins_lookup3;
1032 use crate::storage::BytesStorage;
1033
1034 fn build_v1_header(raw_messages: &[(u16, u8, &[u8])], ref_count: u32) -> Vec<u8> {
1041 let data_size: usize = raw_messages
1043 .iter()
1044 .map(|(_, _, payload)| 8 + payload.len()) .sum();
1046
1047 let mut buf = Vec::new();
1048 buf.push(1);
1050 buf.push(0);
1052 buf.extend_from_slice(&(raw_messages.len() as u16).to_le_bytes());
1054 buf.extend_from_slice(&ref_count.to_le_bytes());
1056 buf.extend_from_slice(&(data_size as u32).to_le_bytes());
1058 buf.extend_from_slice(&[0u8; 4]);
1060
1061 for (type_id, flags, payload) in raw_messages {
1063 buf.extend_from_slice(&type_id.to_le_bytes());
1064 buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
1065 buf.push(*flags);
1066 buf.extend_from_slice(&[0u8; 3]); buf.extend_from_slice(payload);
1068 }
1069
1070 buf
1071 }
1072
1073 fn build_v2_header(
1079 header_flags: u8,
1080 raw_messages: &[(u8, u8, &[u8])],
1081 timestamps: Option<[u32; 4]>,
1082 phase_change: Option<(u16, u16)>,
1083 ) -> Vec<u8> {
1084 let creation_order = (header_flags & 0x04) != 0;
1085
1086 let envelope_size: usize = if creation_order { 6 } else { 4 };
1088 let msg_data_size: usize = raw_messages
1089 .iter()
1090 .map(|(_, _, payload)| envelope_size + payload.len())
1091 .sum();
1092
1093 let mut buf = Vec::new();
1094 buf.extend_from_slice(&OHDR_SIGNATURE);
1096 buf.push(2);
1098 buf.push(header_flags);
1100
1101 if let Some(ts) = timestamps {
1103 for &t in &ts {
1104 buf.extend_from_slice(&t.to_le_bytes());
1105 }
1106 }
1107
1108 if let Some((max_compact, min_dense)) = phase_change {
1110 buf.extend_from_slice(&max_compact.to_le_bytes());
1111 buf.extend_from_slice(&min_dense.to_le_bytes());
1112 }
1113
1114 let size_width = 1usize << (header_flags & 0x03);
1116 match size_width {
1117 1 => buf.push(msg_data_size as u8),
1118 2 => buf.extend_from_slice(&(msg_data_size as u16).to_le_bytes()),
1119 4 => buf.extend_from_slice(&(msg_data_size as u32).to_le_bytes()),
1120 8 => buf.extend_from_slice(&(msg_data_size as u64).to_le_bytes()),
1121 _ => unreachable!(),
1122 }
1123
1124 for (type_id, mflags, payload) in raw_messages {
1126 buf.push(*type_id);
1127 buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
1128 buf.push(*mflags);
1129 if creation_order {
1130 buf.extend_from_slice(&0u16.to_le_bytes());
1131 }
1132 buf.extend_from_slice(payload);
1133 }
1134
1135 let ck = jenkins_lookup3(&buf);
1137 buf.extend_from_slice(&ck.to_le_bytes());
1138
1139 buf
1140 }
1141
1142 fn build_v2_ochk(raw_messages: &[(u8, u8, &[u8])], creation_order: bool) -> Vec<u8> {
1144 let mut buf = Vec::new();
1145 buf.extend_from_slice(&OCHK_SIGNATURE);
1147
1148 for (type_id, mflags, payload) in raw_messages {
1150 buf.push(*type_id);
1151 buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
1152 buf.push(*mflags);
1153 if creation_order {
1154 buf.extend_from_slice(&0u16.to_le_bytes());
1155 }
1156 buf.extend_from_slice(payload);
1157 }
1158
1159 let ck = jenkins_lookup3(&buf);
1161 buf.extend_from_slice(&ck.to_le_bytes());
1162
1163 buf
1164 }
1165
1166 #[test]
1167 fn parse_v1_storage_accepts_header_near_eof() {
1168 let header = build_v1_header(&[], 7);
1169 let mut file_data = vec![0xAA; 3];
1170 let address = file_data.len() as u64;
1171 file_data.extend_from_slice(&header);
1172 let storage = BytesStorage::new(file_data);
1173
1174 let hdr = ObjectHeader::parse_at_storage(&storage, address, 8, 8).unwrap();
1175
1176 assert_eq!(hdr.version, 1);
1177 assert_eq!(hdr.reference_count, 7);
1178 assert!(hdr.messages.is_empty());
1179 }
1180
1181 #[test]
1182 fn parse_v2_storage_accepts_header_near_eof() {
1183 let header = build_v2_header(0x00, &[], None, None);
1184 let mut file_data = vec![0xAA; 5];
1185 let address = file_data.len() as u64;
1186 file_data.extend_from_slice(&header);
1187 let storage = BytesStorage::new(file_data);
1188
1189 let hdr = ObjectHeader::parse_at_storage(&storage, address, 8, 8).unwrap();
1190
1191 assert_eq!(hdr.version, 2);
1192 assert!(hdr.messages.is_empty());
1193 }
1194
1195 #[test]
1196 fn parse_v2_storage_rejects_oversized_chunk0_size() {
1197 let mut header = Vec::new();
1198 header.extend_from_slice(&OHDR_SIGNATURE);
1199 header.push(2);
1200 header.push(0x03);
1201 header.extend_from_slice(&u64::MAX.to_le_bytes());
1202 let storage = BytesStorage::new(header);
1203
1204 let err = ObjectHeader::parse_at_storage(&storage, 0, 8, 8).unwrap_err();
1205
1206 assert!(matches!(err, Error::InvalidData(_)));
1207 }
1208
1209 #[test]
1214 fn v1_empty_header() {
1215 let data = build_v1_header(&[], 1);
1216 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1217 assert_eq!(hdr.version, 1);
1218 assert_eq!(hdr.reference_count, 1);
1219 assert!(hdr.messages.is_empty());
1220 assert!(hdr.modification_time.is_none());
1221 }
1222
1223 #[test]
1224 fn v1_nil_message() {
1225 let data = build_v1_header(&[(0x0000, 0, &[0u8; 4])], 1);
1227 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1228 assert_eq!(hdr.messages.len(), 1);
1229 assert!(matches!(hdr.messages[0], HdfMessage::Nil));
1230 }
1231
1232 #[test]
1233 fn v1_unknown_message() {
1234 let payload = [0xAA, 0xBB, 0xCC];
1236 let data = build_v1_header(&[(0x00FF, 0, &payload)], 2);
1237 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1238 assert_eq!(hdr.reference_count, 2);
1239 assert_eq!(hdr.messages.len(), 1);
1240 match &hdr.messages[0] {
1241 HdfMessage::Unknown { type_id, data } => {
1242 assert_eq!(*type_id, 0x00FF);
1243 assert_eq!(data.as_slice(), &payload);
1244 }
1245 other => panic!("expected Unknown, got {:?}", other),
1246 }
1247 }
1248
1249 #[test]
1250 fn v1_symbol_table_message() {
1251 let mut payload = Vec::new();
1254 payload.extend_from_slice(&0x1000u64.to_le_bytes());
1255 payload.extend_from_slice(&0x2000u64.to_le_bytes());
1256
1257 let data = build_v1_header(&[(0x0011, 0, &payload)], 1);
1258 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1259 assert_eq!(hdr.messages.len(), 1);
1260 match &hdr.messages[0] {
1261 HdfMessage::SymbolTable(st) => {
1262 assert_eq!(st.btree_address, 0x1000);
1263 assert_eq!(st.heap_address, 0x2000);
1264 }
1265 other => panic!("expected SymbolTable, got {:?}", other),
1266 }
1267 }
1268
1269 #[test]
1270 fn v1_continuation_message() {
1271 let unknown_payload = [0xDD; 2];
1274
1275 let mut cont_chunk = Vec::new();
1277 cont_chunk.extend_from_slice(&0x00FEu16.to_le_bytes());
1279 cont_chunk.extend_from_slice(&(unknown_payload.len() as u16).to_le_bytes());
1281 cont_chunk.push(0);
1283 cont_chunk.extend_from_slice(&[0u8; 3]);
1285 cont_chunk.extend_from_slice(&unknown_payload);
1287
1288 let main_header_base_size = 16; let cont_msg_envelope_size = 8 + 16; let cont_chunk_offset = (main_header_base_size + cont_msg_envelope_size) as u64;
1295
1296 let mut cont_payload = Vec::new();
1297 cont_payload.extend_from_slice(&cont_chunk_offset.to_le_bytes()); cont_payload.extend_from_slice(&(cont_chunk.len() as u64).to_le_bytes()); let main_header = build_v1_header(&[(MSG_TYPE_CONTINUATION, 0, &cont_payload)], 1);
1301
1302 let mut file_data = main_header;
1304 assert_eq!(file_data.len() as u64, cont_chunk_offset);
1305 file_data.extend_from_slice(&cont_chunk);
1306
1307 let hdr = ObjectHeader::parse_at(&file_data, 0, 8, 8).unwrap();
1308 assert_eq!(hdr.messages.len(), 2);
1310 assert!(matches!(
1311 hdr.messages[0],
1312 HdfMessage::ObjectHeaderContinuation
1313 ));
1314 match &hdr.messages[1] {
1315 HdfMessage::Unknown { type_id, data } => {
1316 assert_eq!(*type_id, 0x00FE);
1317 assert_eq!(data.as_slice(), &unknown_payload);
1318 }
1319 other => panic!("expected Unknown from continuation, got {:?}", other),
1320 }
1321 }
1322
1323 #[test]
1324 fn v1_nonzero_address_offset() {
1325 let prefix_pad = vec![0xFFu8; 64];
1327 let header = build_v1_header(&[(0x00AA, 0, &[0x01])], 3);
1328
1329 let mut file_data = prefix_pad;
1330 file_data.extend_from_slice(&header);
1331
1332 let hdr = ObjectHeader::parse_at(&file_data, 64, 8, 8).unwrap();
1333 assert_eq!(hdr.version, 1);
1334 assert_eq!(hdr.reference_count, 3);
1335 assert_eq!(hdr.messages.len(), 1);
1336 }
1337
1338 #[test]
1339 fn v1_bad_version() {
1340 let mut data = build_v1_header(&[], 1);
1341 data[0] = 3; let err = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap_err();
1343 assert!(matches!(err, Error::UnsupportedObjectHeaderVersion(3)));
1344 }
1345
1346 #[test]
1351 fn v2_empty_header() {
1352 let data = build_v2_header(0x00, &[], None, None);
1354 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1355 assert_eq!(hdr.version, 2);
1356 assert!(hdr.messages.is_empty());
1357 assert!(hdr.modification_time.is_none());
1358 }
1359
1360 #[test]
1361 fn v2_nil_message() {
1362 let data = build_v2_header(0x00, &[(0x00, 0, &[0u8; 3])], None, None);
1363 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1364 assert_eq!(hdr.messages.len(), 1);
1365 assert!(matches!(hdr.messages[0], HdfMessage::Nil));
1366 }
1367
1368 #[test]
1369 fn v2_unknown_message() {
1370 let payload = [0x11, 0x22];
1371 let data = build_v2_header(0x00, &[(0xFE, 0, &payload)], None, None);
1372 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1373 assert_eq!(hdr.messages.len(), 1);
1374 match &hdr.messages[0] {
1375 HdfMessage::Unknown { type_id, data } => {
1376 assert_eq!(*type_id, 0x00FE);
1377 assert_eq!(data.as_slice(), &payload);
1378 }
1379 other => panic!("expected Unknown, got {:?}", other),
1380 }
1381 }
1382
1383 #[test]
1384 fn v2_with_timestamps() {
1385 let flags = 0x20;
1387 let ts = [1000u32, 2000, 3000, 4000]; let data = build_v2_header(flags, &[], Some(ts), None);
1389 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1390 assert_eq!(hdr.modification_time, Some(2000));
1391 }
1392
1393 #[test]
1394 fn v2_with_phase_change() {
1395 let flags = 0x10;
1397 let data = build_v2_header(flags, &[], None, Some((8, 6)));
1398 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1399 assert!(hdr.messages.is_empty());
1400 }
1401
1402 #[test]
1403 fn v2_with_creation_order() {
1404 let flags = 0x04;
1406 let payload = [0xAA];
1407 let data = build_v2_header(flags, &[(0xFE, 0, &payload)], None, None);
1408 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1409 assert_eq!(hdr.messages.len(), 1);
1410 match &hdr.messages[0] {
1411 HdfMessage::Unknown { type_id, .. } => assert_eq!(*type_id, 0x00FE),
1412 other => panic!("expected Unknown, got {:?}", other),
1413 }
1414 }
1415
1416 #[test]
1417 fn v2_2byte_size_field() {
1418 let flags = 0x01;
1420 let payload = [0x42; 5];
1421 let data = build_v2_header(flags, &[(0xFE, 0, &payload)], None, None);
1422 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1423 assert_eq!(hdr.messages.len(), 1);
1424 }
1425
1426 #[test]
1427 fn v2_4byte_size_field() {
1428 let flags = 0x02;
1430 let data = build_v2_header(flags, &[(0xFE, 0, &[0x01])], None, None);
1431 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1432 assert_eq!(hdr.messages.len(), 1);
1433 }
1434
1435 #[test]
1436 fn v2_8byte_size_field() {
1437 let flags = 0x03;
1439 let data = build_v2_header(flags, &[(0xFE, 0, &[0x01])], None, None);
1440 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1441 assert_eq!(hdr.messages.len(), 1);
1442 }
1443
1444 #[test]
1445 fn v2_checksum_mismatch() {
1446 let mut data = build_v2_header(0x00, &[(0xFE, 0, &[0x01])], None, None);
1447 let last = data.len() - 1;
1449 data[last] ^= 0xFF;
1450 let err = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap_err();
1451 assert!(matches!(err, Error::ChecksumMismatch { .. }));
1452 }
1453
1454 #[test]
1455 fn v2_continuation_chunk() {
1456 let unknown_payload = [0xCC; 3];
1458 let ochk = build_v2_ochk(&[(0xFD, 0, &unknown_payload)], false);
1459
1460 let mut cont_payload = vec![0u8; 16];
1467
1468 let ohdr_size = 4 + 1 + 1 + 1 + (4 + cont_payload.len()) + 4;
1477 let ochk_offset = ohdr_size as u64;
1478
1479 cont_payload.clear();
1481 cont_payload.extend_from_slice(&ochk_offset.to_le_bytes());
1482 cont_payload.extend_from_slice(&(ochk.len() as u64).to_le_bytes());
1483
1484 let ohdr = build_v2_header(0x00, &[(0x10, 0, &cont_payload)], None, None);
1485 assert_eq!(ohdr.len(), ohdr_size);
1486
1487 let mut file_data = ohdr;
1488 file_data.extend_from_slice(&ochk);
1489
1490 let hdr = ObjectHeader::parse_at(&file_data, 0, 8, 8).unwrap();
1491 assert_eq!(hdr.messages.len(), 2);
1493 assert!(matches!(
1494 hdr.messages[0],
1495 HdfMessage::ObjectHeaderContinuation
1496 ));
1497 match &hdr.messages[1] {
1498 HdfMessage::Unknown { type_id, data } => {
1499 assert_eq!(*type_id, 0x00FD);
1500 assert_eq!(data.as_slice(), &unknown_payload);
1501 }
1502 other => panic!("expected Unknown from OCHK, got {:?}", other),
1503 }
1504 }
1505
1506 #[test]
1507 fn v1_continuation_cycle_is_rejected() {
1508 let main_header_base_size = 16;
1511 let cont_msg_envelope_size = 8 + 16;
1512 let cont_chunk_offset = (main_header_base_size + cont_msg_envelope_size) as u64;
1513
1514 let mut cont_chunk = Vec::new();
1517 cont_chunk.extend_from_slice(&MSG_TYPE_CONTINUATION.to_le_bytes());
1518 cont_chunk.extend_from_slice(&16u16.to_le_bytes());
1519 cont_chunk.push(0);
1520 cont_chunk.extend_from_slice(&[0u8; 3]);
1521 let cont_chunk_len = (cont_chunk.len() + 16) as u64;
1522 cont_chunk.extend_from_slice(&cont_chunk_offset.to_le_bytes());
1523 cont_chunk.extend_from_slice(&cont_chunk_len.to_le_bytes());
1524
1525 let mut cont_payload = Vec::new();
1526 cont_payload.extend_from_slice(&cont_chunk_offset.to_le_bytes());
1527 cont_payload.extend_from_slice(&cont_chunk_len.to_le_bytes());
1528
1529 let main_header = build_v1_header(&[(MSG_TYPE_CONTINUATION, 0, &cont_payload)], 1);
1530 let mut file_data = main_header;
1531 assert_eq!(file_data.len() as u64, cont_chunk_offset);
1532 file_data.extend_from_slice(&cont_chunk);
1533
1534 let err = ObjectHeader::parse_at(&file_data, 0, 8, 8).unwrap_err();
1535 assert!(
1536 err.to_string().contains("continuation cycle"),
1537 "unexpected error: {err}"
1538 );
1539 }
1540
1541 #[test]
1542 fn v2_continuation_cycle_is_rejected() {
1543 let mut cont_payload = vec![0u8; 16];
1546 let ohdr_size = 4 + 1 + 1 + 1 + (4 + cont_payload.len()) + 4;
1547 let ochk_offset = ohdr_size as u64;
1548
1549 let placeholder = build_v2_ochk(&[(0x10, 0, &[0u8; 16])], false);
1552 let ochk_len = placeholder.len() as u64;
1553 let mut self_payload = Vec::new();
1554 self_payload.extend_from_slice(&ochk_offset.to_le_bytes());
1555 self_payload.extend_from_slice(&ochk_len.to_le_bytes());
1556 let ochk = build_v2_ochk(&[(0x10, 0, &self_payload)], false);
1557 assert_eq!(ochk.len() as u64, ochk_len);
1558
1559 cont_payload.clear();
1560 cont_payload.extend_from_slice(&ochk_offset.to_le_bytes());
1561 cont_payload.extend_from_slice(&ochk_len.to_le_bytes());
1562 let ohdr = build_v2_header(0x00, &[(0x10, 0, &cont_payload)], None, None);
1563 assert_eq!(ohdr.len(), ohdr_size);
1564
1565 let mut file_data = ohdr;
1566 file_data.extend_from_slice(&ochk);
1567
1568 let err = ObjectHeader::parse_at(&file_data, 0, 8, 8).unwrap_err();
1569 assert!(
1570 err.to_string().contains("continuation cycle"),
1571 "unexpected error: {err}"
1572 );
1573 }
1574
1575 #[test]
1576 fn v1_storage_continuation_cycle_is_rejected() {
1577 let main_header_base_size = 16;
1578 let cont_msg_envelope_size = 8 + 16;
1579 let cont_chunk_offset = (main_header_base_size + cont_msg_envelope_size) as u64;
1580
1581 let mut cont_chunk = Vec::new();
1582 cont_chunk.extend_from_slice(&MSG_TYPE_CONTINUATION.to_le_bytes());
1583 cont_chunk.extend_from_slice(&16u16.to_le_bytes());
1584 cont_chunk.push(0);
1585 cont_chunk.extend_from_slice(&[0u8; 3]);
1586 let cont_chunk_len = (cont_chunk.len() + 16) as u64;
1587 cont_chunk.extend_from_slice(&cont_chunk_offset.to_le_bytes());
1588 cont_chunk.extend_from_slice(&cont_chunk_len.to_le_bytes());
1589
1590 let mut cont_payload = Vec::new();
1591 cont_payload.extend_from_slice(&cont_chunk_offset.to_le_bytes());
1592 cont_payload.extend_from_slice(&cont_chunk_len.to_le_bytes());
1593
1594 let mut file_data = build_v1_header(&[(MSG_TYPE_CONTINUATION, 0, &cont_payload)], 1);
1595 assert_eq!(file_data.len() as u64, cont_chunk_offset);
1596 file_data.extend_from_slice(&cont_chunk);
1597 let storage = BytesStorage::new(file_data);
1598
1599 let err = ObjectHeader::parse_at_storage(&storage, 0, 8, 8).unwrap_err();
1600 assert!(
1601 err.to_string().contains("continuation cycle"),
1602 "unexpected error: {err}"
1603 );
1604 }
1605
1606 #[test]
1607 fn v2_storage_continuation_cycle_is_rejected() {
1608 let mut cont_payload = vec![0u8; 16];
1609 let ohdr_size = 4 + 1 + 1 + 1 + (4 + cont_payload.len()) + 4;
1610 let ochk_offset = ohdr_size as u64;
1611
1612 let placeholder = build_v2_ochk(&[(0x10, 0, &[0u8; 16])], false);
1613 let ochk_len = placeholder.len() as u64;
1614 let mut self_payload = Vec::new();
1615 self_payload.extend_from_slice(&ochk_offset.to_le_bytes());
1616 self_payload.extend_from_slice(&ochk_len.to_le_bytes());
1617 let ochk = build_v2_ochk(&[(0x10, 0, &self_payload)], false);
1618
1619 cont_payload.clear();
1620 cont_payload.extend_from_slice(&ochk_offset.to_le_bytes());
1621 cont_payload.extend_from_slice(&ochk_len.to_le_bytes());
1622 let mut file_data = build_v2_header(0x00, &[(0x10, 0, &cont_payload)], None, None);
1623 file_data.extend_from_slice(&ochk);
1624 let storage = BytesStorage::new(file_data);
1625
1626 let err = ObjectHeader::parse_at_storage(&storage, 0, 8, 8).unwrap_err();
1627 assert!(
1628 err.to_string().contains("continuation cycle"),
1629 "unexpected error: {err}"
1630 );
1631 }
1632
1633 #[test]
1634 fn v2_ochk_checksum_mismatch() {
1635 let unknown_payload = [0xCC; 3];
1636 let mut ochk = build_v2_ochk(&[(0xFD, 0, &unknown_payload)], false);
1637 let last = ochk.len() - 1;
1639 ochk[last] ^= 0xFF;
1640
1641 let ohdr_size = 4 + 1 + 1 + 1 + (4 + 16) + 4; let ochk_offset = ohdr_size as u64;
1643
1644 let mut cont_payload = Vec::new();
1645 cont_payload.extend_from_slice(&ochk_offset.to_le_bytes());
1646 cont_payload.extend_from_slice(&(ochk.len() as u64).to_le_bytes());
1647
1648 let ohdr = build_v2_header(0x00, &[(0x10, 0, &cont_payload)], None, None);
1649 let mut file_data = ohdr;
1650 file_data.extend_from_slice(&ochk);
1651
1652 let err = ObjectHeader::parse_at(&file_data, 0, 8, 8).unwrap_err();
1653 assert!(matches!(err, Error::ChecksumMismatch { .. }));
1654 }
1655
1656 #[test]
1657 fn v2_multiple_messages() {
1658 let p1 = [0x01, 0x02];
1660 let p2 = [0x03, 0x04, 0x05];
1661 let data = build_v2_header(0x00, &[(0xA0, 0, &p1), (0xA1, 0, &p2)], None, None);
1662 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1663 assert_eq!(hdr.messages.len(), 2);
1664 match &hdr.messages[0] {
1665 HdfMessage::Unknown { type_id, .. } => assert_eq!(*type_id, 0x00A0),
1666 other => panic!("expected Unknown 0xA0, got {:?}", other),
1667 }
1668 match &hdr.messages[1] {
1669 HdfMessage::Unknown { type_id, .. } => assert_eq!(*type_id, 0x00A1),
1670 other => panic!("expected Unknown 0xA1, got {:?}", other),
1671 }
1672 }
1673
1674 #[test]
1675 fn v2_zero_length_nil_before_more_messages() {
1676 let p1 = [0xAA];
1677 let p2 = [0xBB];
1678 let data = build_v2_header(
1679 0x04,
1680 &[(0xFE, 0, &p1), (0x00, 0, &[]), (0xFD, 0, &p2)],
1681 None,
1682 None,
1683 );
1684 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1685 assert_eq!(hdr.messages.len(), 3);
1686 assert!(matches!(hdr.messages[0], HdfMessage::Unknown { .. }));
1687 assert!(matches!(hdr.messages[1], HdfMessage::Nil));
1688 assert!(matches!(hdr.messages[2], HdfMessage::Unknown { .. }));
1689 }
1690
1691 #[test]
1692 fn v2_nonzero_address() {
1693 let prefix_pad = vec![0u8; 128];
1695 let ohdr = build_v2_header(0x00, &[(0xFE, 0, &[0x42])], None, None);
1696
1697 let mut file_data = prefix_pad;
1698 file_data.extend_from_slice(&ohdr);
1699
1700 let hdr = ObjectHeader::parse_at(&file_data, 128, 8, 8).unwrap();
1701 assert_eq!(hdr.version, 2);
1702 assert_eq!(hdr.messages.len(), 1);
1703 }
1704
1705 #[test]
1706 fn v2_all_flags_combined() {
1707 let flags = 0x20 | 0x10 | 0x04 | 0x01;
1709 let ts = [100u32, 200, 300, 400];
1710 let payload = [0xBB];
1711 let data = build_v2_header(flags, &[(0xFE, 0, &payload)], Some(ts), Some((12, 8)));
1712 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1713 assert_eq!(hdr.version, 2);
1714 assert_eq!(hdr.modification_time, Some(200));
1715 assert_eq!(hdr.messages.len(), 1);
1716 }
1717
1718 #[test]
1719 fn v1_multiple_messages() {
1720 let p1 = [0xAA; 4];
1722 let p2 = [0xBB; 8];
1723 let data = build_v1_header(&[(0x00FF, 0, &p1), (0x00FE, 0, &p2)], 5);
1724 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1725 assert_eq!(hdr.version, 1);
1726 assert_eq!(hdr.reference_count, 5);
1727 assert_eq!(hdr.messages.len(), 2);
1728 }
1729
1730 #[test]
1731 fn v1_4byte_offsets() {
1732 let mut payload = Vec::new();
1735 payload.extend_from_slice(&0x1000u32.to_le_bytes());
1736 payload.extend_from_slice(&0x2000u32.to_le_bytes());
1737
1738 let data = build_v1_header(&[(0x0011, 0, &payload)], 1);
1739 let hdr = ObjectHeader::parse_at(&data, 0, 4, 4).unwrap();
1740 assert_eq!(hdr.messages.len(), 1);
1741 match &hdr.messages[0] {
1742 HdfMessage::SymbolTable(st) => {
1743 assert_eq!(st.btree_address, 0x1000);
1744 assert_eq!(st.heap_address, 0x2000);
1745 }
1746 other => panic!("expected SymbolTable, got {:?}", other),
1747 }
1748 }
1749}