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 MSG_TYPE_CONTINUATION: u16 = 0x0010;
32
33const MSG_TYPE_NIL: u16 = 0x0000;
35
36#[derive(Debug, Clone)]
38pub struct ObjectHeader {
39 pub version: u8,
41 pub messages: Vec<HdfMessage>,
43 pub reference_count: u32,
45 pub modification_time: Option<u32>,
48}
49
50impl ObjectHeader {
51 pub fn parse_at(data: &[u8], address: u64, offset_size: u8, length_size: u8) -> Result<Self> {
57 let mut cursor = Cursor::new(data);
58 cursor.set_position(address);
59
60 let sig = cursor.peek_bytes(4)?;
62 if sig == OHDR_SIGNATURE {
63 Self::parse_v2(&cursor, address, offset_size, length_size)
64 } else {
65 Self::parse_v1(&cursor, address, offset_size, length_size)
66 }
67 }
68
69 pub fn parse_at_storage(
71 storage: &dyn Storage,
72 address: u64,
73 offset_size: u8,
74 length_size: u8,
75 ) -> Result<Self> {
76 let prefix = storage.read_range(address, 64)?;
77 if prefix.len() < 5 {
78 return Err(Error::UnexpectedEof {
79 offset: address,
80 needed: 5,
81 available: prefix.len() as u64,
82 });
83 }
84
85 if prefix.as_ref()[..4] == OHDR_SIGNATURE {
86 Self::parse_v2_storage(storage, address, offset_size, length_size)
87 } else {
88 Self::parse_v1_storage(storage, address, offset_size, length_size)
89 }
90 }
91
92 pub fn resolve_shared_messages(
97 &mut self,
98 data: &[u8],
99 offset_size: u8,
100 length_size: u8,
101 ) -> Result<()> {
102 let old_messages = std::mem::take(&mut self.messages);
103 let mut resolved = Vec::with_capacity(old_messages.len());
104 for msg in old_messages {
105 match msg {
106 HdfMessage::Shared(SharedMessage::SharedInOhdr { address }) => {
107 match Self::parse_at(data, address, offset_size, length_size) {
108 Ok(target_header) => {
109 for target_msg in target_header.messages {
113 match target_msg {
114 HdfMessage::Nil
115 | HdfMessage::ObjectHeaderContinuation
116 | HdfMessage::Shared(_) => continue,
117 other => {
118 resolved.push(other);
119 break;
120 }
121 }
122 }
123 }
124 Err(_) => {
125 resolved
127 .push(HdfMessage::Shared(SharedMessage::SharedInOhdr { address }));
128 }
129 }
130 }
131 HdfMessage::Shared(SharedMessage::SharedInSohm { .. }) => {
132 self.messages = resolved;
133 return Err(Error::Other(
134 "SOHM table lookup not yet supported — file uses shared object header messages".to_string(),
135 ));
136 }
137 other => resolved.push(other),
138 }
139 }
140 self.messages = resolved;
141 Ok(())
142 }
143
144 pub fn resolve_shared_messages_storage(
146 &mut self,
147 storage: &dyn Storage,
148 offset_size: u8,
149 length_size: u8,
150 ) -> Result<()> {
151 let old_messages = std::mem::take(&mut self.messages);
152 let mut resolved = Vec::with_capacity(old_messages.len());
153 for msg in old_messages {
154 match msg {
155 HdfMessage::Shared(SharedMessage::SharedInOhdr { address }) => {
156 match Self::parse_at_storage(storage, address, offset_size, length_size) {
157 Ok(target_header) => {
158 for target_msg in target_header.messages {
159 match target_msg {
160 HdfMessage::Nil
161 | HdfMessage::ObjectHeaderContinuation
162 | HdfMessage::Shared(_) => continue,
163 other => {
164 resolved.push(other);
165 break;
166 }
167 }
168 }
169 }
170 Err(_) => {
171 resolved
172 .push(HdfMessage::Shared(SharedMessage::SharedInOhdr { address }));
173 }
174 }
175 }
176 HdfMessage::Shared(SharedMessage::SharedInSohm { .. }) => {
177 self.messages = resolved;
178 return Err(Error::Other(
179 "SOHM table lookup not yet supported — file uses shared object header messages".to_string(),
180 ));
181 }
182 other => resolved.push(other),
183 }
184 }
185 self.messages = resolved;
186 Ok(())
187 }
188
189 fn parse_v1(base: &Cursor<'_>, address: u64, offset_size: u8, length_size: u8) -> Result<Self> {
205 let mut cursor = base.at_offset(address)?;
206
207 let version = cursor.read_u8()?;
208 if version != 1 {
209 return Err(Error::UnsupportedObjectHeaderVersion(version));
210 }
211
212 let _reserved = cursor.read_u8()?;
213 let num_messages = cursor.read_u16_le()?;
214 let reference_count = cursor.read_u32_le()?;
215 let header_data_size = cursor.read_u32_le()? as u64;
216 let _reserved2 = cursor.read_u32_le()?; let messages_start = cursor.position();
220 let messages_end = messages_start + header_data_size;
221
222 let mut messages: Vec<HdfMessage> = Vec::with_capacity(num_messages as usize);
223 let mut continuations: Vec<(u64, u64)> = Vec::new();
224
225 Self::read_v1_messages(
226 base,
227 messages_start,
228 messages_end,
229 offset_size,
230 length_size,
231 &mut messages,
232 &mut continuations,
233 )?;
234
235 while let Some((cont_offset, cont_length)) = continuations.pop() {
237 let cont_end = cont_offset + cont_length;
238 Self::read_v1_messages(
239 base,
240 cont_offset,
241 cont_end,
242 offset_size,
243 length_size,
244 &mut messages,
245 &mut continuations,
246 )?;
247 }
248
249 Ok(ObjectHeader {
250 version: 1,
251 messages,
252 reference_count,
253 modification_time: None,
254 })
255 }
256
257 fn parse_v1_storage(
258 storage: &dyn Storage,
259 address: u64,
260 offset_size: u8,
261 length_size: u8,
262 ) -> Result<Self> {
263 let header = storage.read_range(address, 16)?;
264 let mut cursor = Cursor::new(header.as_ref());
265
266 let version = cursor.read_u8()?;
267 if version != 1 {
268 return Err(Error::UnsupportedObjectHeaderVersion(version));
269 }
270
271 let _reserved = cursor.read_u8()?;
272 let num_messages = cursor.read_u16_le()?;
273 let reference_count = cursor.read_u32_le()?;
274 let header_data_size = cursor.read_u32_le()? as u64;
275 let _reserved2 = cursor.read_u32_le()?;
276
277 let first_chunk = storage.read_range(address, (16 + header_data_size) as usize)?;
278 let mut messages = Vec::with_capacity(num_messages as usize);
279 let mut continuations = Vec::new();
280 Self::read_v1_messages_from_slice(
281 &first_chunk.as_ref()[16..],
282 offset_size,
283 length_size,
284 &mut messages,
285 &mut continuations,
286 )?;
287
288 while let Some((cont_offset, cont_length)) = continuations.pop() {
289 let chunk = storage.read_range(cont_offset, cont_length as usize)?;
290 Self::read_v1_messages_from_slice(
291 chunk.as_ref(),
292 offset_size,
293 length_size,
294 &mut messages,
295 &mut continuations,
296 )?;
297 }
298
299 Ok(ObjectHeader {
300 version: 1,
301 messages,
302 reference_count,
303 modification_time: None,
304 })
305 }
306
307 fn read_v1_messages(
311 base: &Cursor<'_>,
312 start: u64,
313 end: u64,
314 offset_size: u8,
315 length_size: u8,
316 messages: &mut Vec<HdfMessage>,
317 continuations: &mut Vec<(u64, u64)>,
318 ) -> Result<()> {
319 let mut cursor = base.at_offset(start)?;
320
321 while cursor.position() + 8 <= end {
322 let msg_type = cursor.read_u16_le()?;
323 let msg_data_size = cursor.read_u16_le()? as usize;
324 let msg_flags = cursor.read_u8()?;
325 let _reserved = cursor.read_bytes(3)?; if cursor.position() + msg_data_size as u64 > end {
329 return Err(Error::InvalidData(format!(
330 "v1 message data ({} bytes) extends past header chunk end",
331 msg_data_size
332 )));
333 }
334
335 if msg_type == MSG_TYPE_NIL {
336 cursor.skip(msg_data_size)?;
338 messages.push(HdfMessage::Nil);
339 continue;
340 }
341
342 let msg_data = cursor.read_bytes(msg_data_size)?;
343 let is_shared = (msg_flags & 0x02) != 0;
344
345 if is_shared {
346 let shared_msg = crate::messages::shared::parse(
349 &mut Cursor::new(msg_data),
350 offset_size,
351 length_size,
352 msg_data_size,
353 )?;
354 messages.push(HdfMessage::Shared(shared_msg));
355 } else if msg_type == MSG_TYPE_CONTINUATION {
356 let cont = crate::messages::continuation::parse(
359 &mut Cursor::new(msg_data),
360 offset_size,
361 length_size,
362 msg_data_size,
363 )?;
364 continuations.push((cont.offset, cont.length));
365 messages.push(HdfMessage::ObjectHeaderContinuation);
366 } else {
367 let parsed = parse_message(
368 msg_type,
369 msg_data.len(),
370 &mut Cursor::new(msg_data),
371 offset_size,
372 length_size,
373 )?;
374 messages.push(parsed);
375 }
376 }
377
378 Ok(())
379 }
380
381 fn parse_v2(base: &Cursor<'_>, address: u64, offset_size: u8, length_size: u8) -> Result<Self> {
399 let mut cursor = base.at_offset(address)?;
400
401 let sig = cursor.read_bytes(4)?;
403 if sig != OHDR_SIGNATURE {
404 return Err(Error::InvalidObjectHeaderSignature);
405 }
406 let version = cursor.read_u8()?;
407 if version != 2 {
408 return Err(Error::UnsupportedObjectHeaderVersion(version));
409 }
410 let flags = cursor.read_u8()?;
411
412 let modification_time = if (flags & 0x20) != 0 {
414 let _access_time = cursor.read_u32_le()?;
415 let mod_time = cursor.read_u32_le()?;
416 let _change_time = cursor.read_u32_le()?;
417 let _birth_time = cursor.read_u32_le()?;
418 Some(mod_time)
419 } else {
420 None
421 };
422
423 if (flags & 0x10) != 0 {
425 let _max_compact = cursor.read_u16_le()?;
426 let _min_dense = cursor.read_u16_le()?;
427 }
428
429 let size_field_width = 1usize << (flags & 0x03);
431 let chunk0_data_size = cursor.read_uvar(size_field_width)?;
432
433 let creation_order_tracked = (flags & 0x04) != 0;
435
436 let messages_start = cursor.position();
440 let chunk0_end = messages_start + chunk0_data_size;
441
442 let checksum_start = address as usize;
445 let checksum_end = chunk0_end as usize; let stored_checksum = {
447 let mut ck = base.at_offset(chunk0_end)?;
448 ck.read_u32_le()?
449 };
450 let computed = jenkins_lookup3(&base.data()[checksum_start..checksum_end]);
451 if computed != stored_checksum {
452 return Err(Error::ChecksumMismatch {
453 expected: stored_checksum,
454 actual: computed,
455 });
456 }
457
458 let mut messages: Vec<HdfMessage> = Vec::new();
459 let mut continuations: Vec<(u64, u64)> = Vec::new();
460
461 Self::read_v2_messages(
462 base,
463 messages_start,
464 chunk0_end,
465 offset_size,
466 length_size,
467 creation_order_tracked,
468 &mut messages,
469 &mut continuations,
470 )?;
471
472 while let Some((cont_offset, cont_length)) = continuations.pop() {
474 Self::read_v2_continuation_chunk(
475 base,
476 cont_offset,
477 cont_length,
478 offset_size,
479 length_size,
480 creation_order_tracked,
481 &mut messages,
482 &mut continuations,
483 )?;
484 }
485
486 Ok(ObjectHeader {
487 version: 2,
488 messages,
489 reference_count: 0, modification_time,
491 })
492 }
493
494 fn parse_v2_storage(
495 storage: &dyn Storage,
496 address: u64,
497 offset_size: u8,
498 length_size: u8,
499 ) -> Result<Self> {
500 let prefix = storage.read_range(address, 64)?;
501 let mut cursor = Cursor::new(prefix.as_ref());
502
503 let sig = cursor.read_bytes(4)?;
504 if sig != OHDR_SIGNATURE {
505 return Err(Error::InvalidObjectHeaderSignature);
506 }
507 let version = cursor.read_u8()?;
508 if version != 2 {
509 return Err(Error::UnsupportedObjectHeaderVersion(version));
510 }
511 let flags = cursor.read_u8()?;
512
513 let modification_time = if (flags & 0x20) != 0 {
514 let _access_time = cursor.read_u32_le()?;
515 let mod_time = cursor.read_u32_le()?;
516 let _change_time = cursor.read_u32_le()?;
517 let _birth_time = cursor.read_u32_le()?;
518 Some(mod_time)
519 } else {
520 None
521 };
522
523 if (flags & 0x10) != 0 {
524 let _max_compact = cursor.read_u16_le()?;
525 let _min_dense = cursor.read_u16_le()?;
526 }
527
528 let size_field_width = 1usize << (flags & 0x03);
529 let chunk0_data_size = cursor.read_uvar(size_field_width)?;
530 let creation_order_tracked = (flags & 0x04) != 0;
531 let messages_start = cursor.position() as usize;
532 let chunk0_end = messages_start + chunk0_data_size as usize;
533
534 let chunk = storage.read_range(address, chunk0_end + 4)?;
535 let stored_checksum = u32::from_le_bytes(
536 chunk.as_ref()[chunk0_end..chunk0_end + 4]
537 .try_into()
538 .unwrap(),
539 );
540 let computed = jenkins_lookup3(&chunk.as_ref()[..chunk0_end]);
541 if computed != stored_checksum {
542 return Err(Error::ChecksumMismatch {
543 expected: stored_checksum,
544 actual: computed,
545 });
546 }
547
548 let mut messages = Vec::new();
549 let mut continuations = Vec::new();
550 Self::read_v2_messages_from_slice(
551 &chunk.as_ref()[messages_start..chunk0_end],
552 offset_size,
553 length_size,
554 creation_order_tracked,
555 &mut messages,
556 &mut continuations,
557 )?;
558
559 while let Some((cont_offset, cont_length)) = continuations.pop() {
560 Self::read_v2_continuation_chunk_storage(
561 storage,
562 cont_offset,
563 cont_length,
564 offset_size,
565 length_size,
566 creation_order_tracked,
567 &mut messages,
568 &mut continuations,
569 )?;
570 }
571
572 Ok(ObjectHeader {
573 version: 2,
574 messages,
575 reference_count: 0,
576 modification_time,
577 })
578 }
579
580 #[allow(clippy::too_many_arguments)]
582 fn read_v2_messages(
583 base: &Cursor<'_>,
584 start: u64,
585 end: u64,
586 offset_size: u8,
587 length_size: u8,
588 creation_order_tracked: bool,
589 messages: &mut Vec<HdfMessage>,
590 continuations: &mut Vec<(u64, u64)>,
591 ) -> Result<()> {
592 let mut cursor = base.at_offset(start)?;
593
594 let min_envelope = if creation_order_tracked { 6 } else { 4 };
597
598 while cursor.position() + min_envelope as u64 <= end {
599 let msg_type = cursor.read_u8()? as u16;
600 let msg_data_size = cursor.read_u16_le()? as usize;
601 let msg_flags = cursor.read_u8()?;
602
603 if creation_order_tracked {
604 let _creation_order = cursor.read_u16_le()?;
605 }
606
607 if msg_type == MSG_TYPE_NIL {
608 if msg_data_size == 0
609 && base.data()[cursor.position() as usize..end as usize]
610 .iter()
611 .all(|byte| *byte == 0)
612 {
613 break;
614 }
615 cursor.skip(msg_data_size)?;
616 messages.push(HdfMessage::Nil);
617 continue;
618 }
619
620 if cursor.position() + msg_data_size as u64 > end {
621 return Err(Error::InvalidData(format!(
622 "v2 message data ({} bytes) extends past chunk end",
623 msg_data_size
624 )));
625 }
626
627 let msg_data = cursor.read_bytes(msg_data_size)?;
628 let is_shared = (msg_flags & 0x02) != 0;
629
630 if is_shared {
631 let shared_msg = crate::messages::shared::parse(
632 &mut Cursor::new(msg_data),
633 offset_size,
634 length_size,
635 msg_data_size,
636 )?;
637 messages.push(HdfMessage::Shared(shared_msg));
638 } else if msg_type == MSG_TYPE_CONTINUATION {
639 let cont = crate::messages::continuation::parse(
640 &mut Cursor::new(msg_data),
641 offset_size,
642 length_size,
643 msg_data_size,
644 )?;
645 continuations.push((cont.offset, cont.length));
646 messages.push(HdfMessage::ObjectHeaderContinuation);
647 } else {
648 let parsed = parse_message(
649 msg_type,
650 msg_data.len(),
651 &mut Cursor::new(msg_data),
652 offset_size,
653 length_size,
654 )?;
655 messages.push(parsed);
656 }
657 }
658
659 Ok(())
660 }
661
662 fn read_v1_messages_from_slice(
663 data: &[u8],
664 offset_size: u8,
665 length_size: u8,
666 messages: &mut Vec<HdfMessage>,
667 continuations: &mut Vec<(u64, u64)>,
668 ) -> Result<()> {
669 let mut cursor = Cursor::new(data);
670 while cursor.remaining() >= 8 {
671 let msg_type = cursor.read_u16_le()?;
672 let msg_data_size = cursor.read_u16_le()? as usize;
673 let msg_flags = cursor.read_u8()?;
674 let _reserved = cursor.read_bytes(3)?;
675
676 if cursor.remaining() < msg_data_size as u64 {
677 return Err(Error::InvalidData(format!(
678 "v1 message data ({} bytes) extends past header chunk end",
679 msg_data_size
680 )));
681 }
682
683 if msg_type == MSG_TYPE_NIL {
684 cursor.skip(msg_data_size)?;
685 messages.push(HdfMessage::Nil);
686 continue;
687 }
688
689 let msg_data = cursor.read_bytes(msg_data_size)?;
690 let is_shared = (msg_flags & 0x02) != 0;
691 if is_shared {
692 let shared_msg = crate::messages::shared::parse(
693 &mut Cursor::new(msg_data),
694 offset_size,
695 length_size,
696 msg_data_size,
697 )?;
698 messages.push(HdfMessage::Shared(shared_msg));
699 } else if msg_type == MSG_TYPE_CONTINUATION {
700 let cont = crate::messages::continuation::parse(
701 &mut Cursor::new(msg_data),
702 offset_size,
703 length_size,
704 msg_data_size,
705 )?;
706 continuations.push((cont.offset, cont.length));
707 messages.push(HdfMessage::ObjectHeaderContinuation);
708 } else {
709 let parsed = parse_message(
710 msg_type,
711 msg_data.len(),
712 &mut Cursor::new(msg_data),
713 offset_size,
714 length_size,
715 )?;
716 messages.push(parsed);
717 }
718 }
719 Ok(())
720 }
721
722 fn read_v2_messages_from_slice(
723 data: &[u8],
724 offset_size: u8,
725 length_size: u8,
726 creation_order_tracked: bool,
727 messages: &mut Vec<HdfMessage>,
728 continuations: &mut Vec<(u64, u64)>,
729 ) -> Result<()> {
730 let mut cursor = Cursor::new(data);
731 let min_envelope = if creation_order_tracked { 6 } else { 4 };
732
733 while cursor.remaining() >= min_envelope as u64 {
734 let msg_type = cursor.read_u8()? as u16;
735 let msg_data_size = cursor.read_u16_le()? as usize;
736 let msg_flags = cursor.read_u8()?;
737
738 if creation_order_tracked {
739 let _creation_order = cursor.read_u16_le()?;
740 }
741
742 if msg_type == MSG_TYPE_NIL {
743 if msg_data_size == 0
744 && data[cursor.position() as usize..]
745 .iter()
746 .all(|byte| *byte == 0)
747 {
748 break;
749 }
750 cursor.skip(msg_data_size)?;
751 messages.push(HdfMessage::Nil);
752 continue;
753 }
754
755 if cursor.remaining() < msg_data_size as u64 {
756 return Err(Error::InvalidData(format!(
757 "v2 message data ({} bytes) extends past chunk end",
758 msg_data_size
759 )));
760 }
761
762 let msg_data = cursor.read_bytes(msg_data_size)?;
763 let is_shared = (msg_flags & 0x02) != 0;
764 if is_shared {
765 let shared_msg = crate::messages::shared::parse(
766 &mut Cursor::new(msg_data),
767 offset_size,
768 length_size,
769 msg_data_size,
770 )?;
771 messages.push(HdfMessage::Shared(shared_msg));
772 } else if msg_type == MSG_TYPE_CONTINUATION {
773 let cont = crate::messages::continuation::parse(
774 &mut Cursor::new(msg_data),
775 offset_size,
776 length_size,
777 msg_data_size,
778 )?;
779 continuations.push((cont.offset, cont.length));
780 messages.push(HdfMessage::ObjectHeaderContinuation);
781 } else {
782 let parsed = parse_message(
783 msg_type,
784 msg_data.len(),
785 &mut Cursor::new(msg_data),
786 offset_size,
787 length_size,
788 )?;
789 messages.push(parsed);
790 }
791 }
792
793 Ok(())
794 }
795
796 #[allow(clippy::too_many_arguments)]
797 fn read_v2_continuation_chunk_storage(
798 storage: &dyn Storage,
799 cont_offset: u64,
800 cont_length: u64,
801 offset_size: u8,
802 length_size: u8,
803 creation_order_tracked: bool,
804 messages: &mut Vec<HdfMessage>,
805 continuations: &mut Vec<(u64, u64)>,
806 ) -> Result<()> {
807 let chunk = storage.read_range(cont_offset, cont_length as usize)?;
808 if chunk.len() < 8 || chunk.as_ref()[..4] != OCHK_SIGNATURE {
809 return Err(Error::InvalidObjectHeaderSignature);
810 }
811 let messages_end = chunk.len() - 4;
812 let stored_checksum = u32::from_le_bytes(
813 chunk.as_ref()[messages_end..messages_end + 4]
814 .try_into()
815 .unwrap(),
816 );
817 let computed = jenkins_lookup3(&chunk.as_ref()[..messages_end]);
818 if computed != stored_checksum {
819 return Err(Error::ChecksumMismatch {
820 expected: stored_checksum,
821 actual: computed,
822 });
823 }
824
825 Self::read_v2_messages_from_slice(
826 &chunk.as_ref()[4..messages_end],
827 offset_size,
828 length_size,
829 creation_order_tracked,
830 messages,
831 continuations,
832 )
833 }
834
835 #[allow(clippy::too_many_arguments)]
837 fn read_v2_continuation_chunk(
845 base: &Cursor<'_>,
846 cont_offset: u64,
847 cont_length: u64,
848 offset_size: u8,
849 length_size: u8,
850 creation_order_tracked: bool,
851 messages: &mut Vec<HdfMessage>,
852 continuations: &mut Vec<(u64, u64)>,
853 ) -> Result<()> {
854 let mut cursor = base.at_offset(cont_offset)?;
855
856 let sig = cursor.read_bytes(4)?;
857 if sig != OCHK_SIGNATURE {
858 return Err(Error::InvalidObjectHeaderSignature);
859 }
860
861 let chunk_end = cont_offset + cont_length;
862 let messages_end = chunk_end - 4;
864 let messages_start = cursor.position(); let checksum_start = cont_offset as usize;
868 let checksum_end = messages_end as usize;
869 let stored_checksum = {
870 let mut ck = base.at_offset(messages_end)?;
871 ck.read_u32_le()?
872 };
873 let computed = jenkins_lookup3(&base.data()[checksum_start..checksum_end]);
874 if computed != stored_checksum {
875 return Err(Error::ChecksumMismatch {
876 expected: stored_checksum,
877 actual: computed,
878 });
879 }
880
881 Self::read_v2_messages(
882 base,
883 messages_start,
884 messages_end,
885 offset_size,
886 length_size,
887 creation_order_tracked,
888 messages,
889 continuations,
890 )
891 }
892}
893
894#[cfg(test)]
895mod tests {
896 use super::*;
897 use crate::checksum::jenkins_lookup3;
898
899 fn build_v1_header(raw_messages: &[(u16, u8, &[u8])], ref_count: u32) -> Vec<u8> {
906 let data_size: usize = raw_messages
908 .iter()
909 .map(|(_, _, payload)| 8 + payload.len()) .sum();
911
912 let mut buf = Vec::new();
913 buf.push(1);
915 buf.push(0);
917 buf.extend_from_slice(&(raw_messages.len() as u16).to_le_bytes());
919 buf.extend_from_slice(&ref_count.to_le_bytes());
921 buf.extend_from_slice(&(data_size as u32).to_le_bytes());
923 buf.extend_from_slice(&[0u8; 4]);
925
926 for (type_id, flags, payload) in raw_messages {
928 buf.extend_from_slice(&type_id.to_le_bytes());
929 buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
930 buf.push(*flags);
931 buf.extend_from_slice(&[0u8; 3]); buf.extend_from_slice(payload);
933 }
934
935 buf
936 }
937
938 fn build_v2_header(
944 header_flags: u8,
945 raw_messages: &[(u8, u8, &[u8])],
946 timestamps: Option<[u32; 4]>,
947 phase_change: Option<(u16, u16)>,
948 ) -> Vec<u8> {
949 let creation_order = (header_flags & 0x04) != 0;
950
951 let envelope_size: usize = if creation_order { 6 } else { 4 };
953 let msg_data_size: usize = raw_messages
954 .iter()
955 .map(|(_, _, payload)| envelope_size + payload.len())
956 .sum();
957
958 let mut buf = Vec::new();
959 buf.extend_from_slice(&OHDR_SIGNATURE);
961 buf.push(2);
963 buf.push(header_flags);
965
966 if let Some(ts) = timestamps {
968 for &t in &ts {
969 buf.extend_from_slice(&t.to_le_bytes());
970 }
971 }
972
973 if let Some((max_compact, min_dense)) = phase_change {
975 buf.extend_from_slice(&max_compact.to_le_bytes());
976 buf.extend_from_slice(&min_dense.to_le_bytes());
977 }
978
979 let size_width = 1usize << (header_flags & 0x03);
981 match size_width {
982 1 => buf.push(msg_data_size as u8),
983 2 => buf.extend_from_slice(&(msg_data_size as u16).to_le_bytes()),
984 4 => buf.extend_from_slice(&(msg_data_size as u32).to_le_bytes()),
985 8 => buf.extend_from_slice(&(msg_data_size as u64).to_le_bytes()),
986 _ => unreachable!(),
987 }
988
989 for (type_id, mflags, payload) in raw_messages {
991 buf.push(*type_id);
992 buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
993 buf.push(*mflags);
994 if creation_order {
995 buf.extend_from_slice(&0u16.to_le_bytes());
996 }
997 buf.extend_from_slice(payload);
998 }
999
1000 let ck = jenkins_lookup3(&buf);
1002 buf.extend_from_slice(&ck.to_le_bytes());
1003
1004 buf
1005 }
1006
1007 fn build_v2_ochk(raw_messages: &[(u8, u8, &[u8])], creation_order: bool) -> Vec<u8> {
1009 let mut buf = Vec::new();
1010 buf.extend_from_slice(&OCHK_SIGNATURE);
1012
1013 for (type_id, mflags, payload) in raw_messages {
1015 buf.push(*type_id);
1016 buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
1017 buf.push(*mflags);
1018 if creation_order {
1019 buf.extend_from_slice(&0u16.to_le_bytes());
1020 }
1021 buf.extend_from_slice(payload);
1022 }
1023
1024 let ck = jenkins_lookup3(&buf);
1026 buf.extend_from_slice(&ck.to_le_bytes());
1027
1028 buf
1029 }
1030
1031 #[test]
1036 fn v1_empty_header() {
1037 let data = build_v1_header(&[], 1);
1038 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1039 assert_eq!(hdr.version, 1);
1040 assert_eq!(hdr.reference_count, 1);
1041 assert!(hdr.messages.is_empty());
1042 assert!(hdr.modification_time.is_none());
1043 }
1044
1045 #[test]
1046 fn v1_nil_message() {
1047 let data = build_v1_header(&[(0x0000, 0, &[0u8; 4])], 1);
1049 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1050 assert_eq!(hdr.messages.len(), 1);
1051 assert!(matches!(hdr.messages[0], HdfMessage::Nil));
1052 }
1053
1054 #[test]
1055 fn v1_unknown_message() {
1056 let payload = [0xAA, 0xBB, 0xCC];
1058 let data = build_v1_header(&[(0x00FF, 0, &payload)], 2);
1059 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1060 assert_eq!(hdr.reference_count, 2);
1061 assert_eq!(hdr.messages.len(), 1);
1062 match &hdr.messages[0] {
1063 HdfMessage::Unknown { type_id, data } => {
1064 assert_eq!(*type_id, 0x00FF);
1065 assert_eq!(data.as_slice(), &payload);
1066 }
1067 other => panic!("expected Unknown, got {:?}", other),
1068 }
1069 }
1070
1071 #[test]
1072 fn v1_symbol_table_message() {
1073 let mut payload = Vec::new();
1076 payload.extend_from_slice(&0x1000u64.to_le_bytes());
1077 payload.extend_from_slice(&0x2000u64.to_le_bytes());
1078
1079 let data = build_v1_header(&[(0x0011, 0, &payload)], 1);
1080 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1081 assert_eq!(hdr.messages.len(), 1);
1082 match &hdr.messages[0] {
1083 HdfMessage::SymbolTable(st) => {
1084 assert_eq!(st.btree_address, 0x1000);
1085 assert_eq!(st.heap_address, 0x2000);
1086 }
1087 other => panic!("expected SymbolTable, got {:?}", other),
1088 }
1089 }
1090
1091 #[test]
1092 fn v1_continuation_message() {
1093 let unknown_payload = [0xDD; 2];
1096
1097 let mut cont_chunk = Vec::new();
1099 cont_chunk.extend_from_slice(&0x00FEu16.to_le_bytes());
1101 cont_chunk.extend_from_slice(&(unknown_payload.len() as u16).to_le_bytes());
1103 cont_chunk.push(0);
1105 cont_chunk.extend_from_slice(&[0u8; 3]);
1107 cont_chunk.extend_from_slice(&unknown_payload);
1109
1110 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;
1117
1118 let mut cont_payload = Vec::new();
1119 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);
1123
1124 let mut file_data = main_header;
1126 assert_eq!(file_data.len() as u64, cont_chunk_offset);
1127 file_data.extend_from_slice(&cont_chunk);
1128
1129 let hdr = ObjectHeader::parse_at(&file_data, 0, 8, 8).unwrap();
1130 assert_eq!(hdr.messages.len(), 2);
1132 assert!(matches!(
1133 hdr.messages[0],
1134 HdfMessage::ObjectHeaderContinuation
1135 ));
1136 match &hdr.messages[1] {
1137 HdfMessage::Unknown { type_id, data } => {
1138 assert_eq!(*type_id, 0x00FE);
1139 assert_eq!(data.as_slice(), &unknown_payload);
1140 }
1141 other => panic!("expected Unknown from continuation, got {:?}", other),
1142 }
1143 }
1144
1145 #[test]
1146 fn v1_nonzero_address_offset() {
1147 let prefix_pad = vec![0xFFu8; 64];
1149 let header = build_v1_header(&[(0x00AA, 0, &[0x01])], 3);
1150
1151 let mut file_data = prefix_pad;
1152 file_data.extend_from_slice(&header);
1153
1154 let hdr = ObjectHeader::parse_at(&file_data, 64, 8, 8).unwrap();
1155 assert_eq!(hdr.version, 1);
1156 assert_eq!(hdr.reference_count, 3);
1157 assert_eq!(hdr.messages.len(), 1);
1158 }
1159
1160 #[test]
1161 fn v1_bad_version() {
1162 let mut data = build_v1_header(&[], 1);
1163 data[0] = 3; let err = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap_err();
1165 assert!(matches!(err, Error::UnsupportedObjectHeaderVersion(3)));
1166 }
1167
1168 #[test]
1173 fn v2_empty_header() {
1174 let data = build_v2_header(0x00, &[], None, None);
1176 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1177 assert_eq!(hdr.version, 2);
1178 assert!(hdr.messages.is_empty());
1179 assert!(hdr.modification_time.is_none());
1180 }
1181
1182 #[test]
1183 fn v2_nil_message() {
1184 let data = build_v2_header(0x00, &[(0x00, 0, &[0u8; 3])], None, None);
1185 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1186 assert_eq!(hdr.messages.len(), 1);
1187 assert!(matches!(hdr.messages[0], HdfMessage::Nil));
1188 }
1189
1190 #[test]
1191 fn v2_unknown_message() {
1192 let payload = [0x11, 0x22];
1193 let data = build_v2_header(0x00, &[(0xFE, 0, &payload)], None, None);
1194 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1195 assert_eq!(hdr.messages.len(), 1);
1196 match &hdr.messages[0] {
1197 HdfMessage::Unknown { type_id, data } => {
1198 assert_eq!(*type_id, 0x00FE);
1199 assert_eq!(data.as_slice(), &payload);
1200 }
1201 other => panic!("expected Unknown, got {:?}", other),
1202 }
1203 }
1204
1205 #[test]
1206 fn v2_with_timestamps() {
1207 let flags = 0x20;
1209 let ts = [1000u32, 2000, 3000, 4000]; let data = build_v2_header(flags, &[], Some(ts), None);
1211 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1212 assert_eq!(hdr.modification_time, Some(2000));
1213 }
1214
1215 #[test]
1216 fn v2_with_phase_change() {
1217 let flags = 0x10;
1219 let data = build_v2_header(flags, &[], None, Some((8, 6)));
1220 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1221 assert!(hdr.messages.is_empty());
1222 }
1223
1224 #[test]
1225 fn v2_with_creation_order() {
1226 let flags = 0x04;
1228 let payload = [0xAA];
1229 let data = build_v2_header(flags, &[(0xFE, 0, &payload)], None, None);
1230 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1231 assert_eq!(hdr.messages.len(), 1);
1232 match &hdr.messages[0] {
1233 HdfMessage::Unknown { type_id, .. } => assert_eq!(*type_id, 0x00FE),
1234 other => panic!("expected Unknown, got {:?}", other),
1235 }
1236 }
1237
1238 #[test]
1239 fn v2_2byte_size_field() {
1240 let flags = 0x01;
1242 let payload = [0x42; 5];
1243 let data = build_v2_header(flags, &[(0xFE, 0, &payload)], None, None);
1244 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1245 assert_eq!(hdr.messages.len(), 1);
1246 }
1247
1248 #[test]
1249 fn v2_4byte_size_field() {
1250 let flags = 0x02;
1252 let data = build_v2_header(flags, &[(0xFE, 0, &[0x01])], None, None);
1253 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1254 assert_eq!(hdr.messages.len(), 1);
1255 }
1256
1257 #[test]
1258 fn v2_8byte_size_field() {
1259 let flags = 0x03;
1261 let data = build_v2_header(flags, &[(0xFE, 0, &[0x01])], None, None);
1262 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1263 assert_eq!(hdr.messages.len(), 1);
1264 }
1265
1266 #[test]
1267 fn v2_checksum_mismatch() {
1268 let mut data = build_v2_header(0x00, &[(0xFE, 0, &[0x01])], None, None);
1269 let last = data.len() - 1;
1271 data[last] ^= 0xFF;
1272 let err = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap_err();
1273 assert!(matches!(err, Error::ChecksumMismatch { .. }));
1274 }
1275
1276 #[test]
1277 fn v2_continuation_chunk() {
1278 let unknown_payload = [0xCC; 3];
1280 let ochk = build_v2_ochk(&[(0xFD, 0, &unknown_payload)], false);
1281
1282 let mut cont_payload = vec![0u8; 16];
1289
1290 let ohdr_size = 4 + 1 + 1 + 1 + (4 + cont_payload.len()) + 4;
1299 let ochk_offset = ohdr_size as u64;
1300
1301 cont_payload.clear();
1303 cont_payload.extend_from_slice(&ochk_offset.to_le_bytes());
1304 cont_payload.extend_from_slice(&(ochk.len() as u64).to_le_bytes());
1305
1306 let ohdr = build_v2_header(0x00, &[(0x10, 0, &cont_payload)], None, None);
1307 assert_eq!(ohdr.len(), ohdr_size);
1308
1309 let mut file_data = ohdr;
1310 file_data.extend_from_slice(&ochk);
1311
1312 let hdr = ObjectHeader::parse_at(&file_data, 0, 8, 8).unwrap();
1313 assert_eq!(hdr.messages.len(), 2);
1315 assert!(matches!(
1316 hdr.messages[0],
1317 HdfMessage::ObjectHeaderContinuation
1318 ));
1319 match &hdr.messages[1] {
1320 HdfMessage::Unknown { type_id, data } => {
1321 assert_eq!(*type_id, 0x00FD);
1322 assert_eq!(data.as_slice(), &unknown_payload);
1323 }
1324 other => panic!("expected Unknown from OCHK, got {:?}", other),
1325 }
1326 }
1327
1328 #[test]
1329 fn v2_ochk_checksum_mismatch() {
1330 let unknown_payload = [0xCC; 3];
1331 let mut ochk = build_v2_ochk(&[(0xFD, 0, &unknown_payload)], false);
1332 let last = ochk.len() - 1;
1334 ochk[last] ^= 0xFF;
1335
1336 let ohdr_size = 4 + 1 + 1 + 1 + (4 + 16) + 4; let ochk_offset = ohdr_size as u64;
1338
1339 let mut cont_payload = Vec::new();
1340 cont_payload.extend_from_slice(&ochk_offset.to_le_bytes());
1341 cont_payload.extend_from_slice(&(ochk.len() as u64).to_le_bytes());
1342
1343 let ohdr = build_v2_header(0x00, &[(0x10, 0, &cont_payload)], None, None);
1344 let mut file_data = ohdr;
1345 file_data.extend_from_slice(&ochk);
1346
1347 let err = ObjectHeader::parse_at(&file_data, 0, 8, 8).unwrap_err();
1348 assert!(matches!(err, Error::ChecksumMismatch { .. }));
1349 }
1350
1351 #[test]
1352 fn v2_multiple_messages() {
1353 let p1 = [0x01, 0x02];
1355 let p2 = [0x03, 0x04, 0x05];
1356 let data = build_v2_header(0x00, &[(0xA0, 0, &p1), (0xA1, 0, &p2)], None, None);
1357 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1358 assert_eq!(hdr.messages.len(), 2);
1359 match &hdr.messages[0] {
1360 HdfMessage::Unknown { type_id, .. } => assert_eq!(*type_id, 0x00A0),
1361 other => panic!("expected Unknown 0xA0, got {:?}", other),
1362 }
1363 match &hdr.messages[1] {
1364 HdfMessage::Unknown { type_id, .. } => assert_eq!(*type_id, 0x00A1),
1365 other => panic!("expected Unknown 0xA1, got {:?}", other),
1366 }
1367 }
1368
1369 #[test]
1370 fn v2_zero_length_nil_before_more_messages() {
1371 let p1 = [0xAA];
1372 let p2 = [0xBB];
1373 let data = build_v2_header(
1374 0x04,
1375 &[(0xFE, 0, &p1), (0x00, 0, &[]), (0xFD, 0, &p2)],
1376 None,
1377 None,
1378 );
1379 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1380 assert_eq!(hdr.messages.len(), 3);
1381 assert!(matches!(hdr.messages[0], HdfMessage::Unknown { .. }));
1382 assert!(matches!(hdr.messages[1], HdfMessage::Nil));
1383 assert!(matches!(hdr.messages[2], HdfMessage::Unknown { .. }));
1384 }
1385
1386 #[test]
1387 fn v2_nonzero_address() {
1388 let prefix_pad = vec![0u8; 128];
1390 let ohdr = build_v2_header(0x00, &[(0xFE, 0, &[0x42])], None, None);
1391
1392 let mut file_data = prefix_pad;
1393 file_data.extend_from_slice(&ohdr);
1394
1395 let hdr = ObjectHeader::parse_at(&file_data, 128, 8, 8).unwrap();
1396 assert_eq!(hdr.version, 2);
1397 assert_eq!(hdr.messages.len(), 1);
1398 }
1399
1400 #[test]
1401 fn v2_all_flags_combined() {
1402 let flags = 0x20 | 0x10 | 0x04 | 0x01;
1404 let ts = [100u32, 200, 300, 400];
1405 let payload = [0xBB];
1406 let data = build_v2_header(flags, &[(0xFE, 0, &payload)], Some(ts), Some((12, 8)));
1407 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1408 assert_eq!(hdr.version, 2);
1409 assert_eq!(hdr.modification_time, Some(200));
1410 assert_eq!(hdr.messages.len(), 1);
1411 }
1412
1413 #[test]
1414 fn v1_multiple_messages() {
1415 let p1 = [0xAA; 4];
1417 let p2 = [0xBB; 8];
1418 let data = build_v1_header(&[(0x00FF, 0, &p1), (0x00FE, 0, &p2)], 5);
1419 let hdr = ObjectHeader::parse_at(&data, 0, 8, 8).unwrap();
1420 assert_eq!(hdr.version, 1);
1421 assert_eq!(hdr.reference_count, 5);
1422 assert_eq!(hdr.messages.len(), 2);
1423 }
1424
1425 #[test]
1426 fn v1_4byte_offsets() {
1427 let mut payload = Vec::new();
1430 payload.extend_from_slice(&0x1000u32.to_le_bytes());
1431 payload.extend_from_slice(&0x2000u32.to_le_bytes());
1432
1433 let data = build_v1_header(&[(0x0011, 0, &payload)], 1);
1434 let hdr = ObjectHeader::parse_at(&data, 0, 4, 4).unwrap();
1435 assert_eq!(hdr.messages.len(), 1);
1436 match &hdr.messages[0] {
1437 HdfMessage::SymbolTable(st) => {
1438 assert_eq!(st.btree_address, 0x1000);
1439 assert_eq!(st.heap_address, 0x2000);
1440 }
1441 other => panic!("expected SymbolTable, got {:?}", other),
1442 }
1443 }
1444}