1#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5
6use byteorder::{ByteOrder, LittleEndian};
7
8use crate::error::FormatError;
9use crate::message_type::MessageType;
10
11const OHDR_SIGNATURE: [u8; 4] = [b'O', b'H', b'D', b'R'];
13
14const OCHK_SIGNATURE: [u8; 4] = [b'O', b'C', b'H', b'K'];
16
17#[derive(Debug, Clone)]
19pub struct HeaderMessage {
20 pub msg_type: MessageType,
22 pub size: usize,
24 pub flags: u8,
26 pub creation_order: Option<u16>,
28 pub data: Vec<u8>,
30}
31
32#[derive(Debug, Clone)]
34pub struct ObjectHeader {
35 pub version: u8,
37 pub messages: Vec<HeaderMessage>,
39 pub reference_count: Option<u32>,
41 pub flags: u8,
43 pub access_time: Option<u32>,
45 pub modification_time: Option<u32>,
47 pub change_time: Option<u32>,
49 pub birth_time: Option<u32>,
51}
52
53fn ensure_len(data: &[u8], offset: usize, needed: usize) -> Result<(), FormatError> {
54 match offset.checked_add(needed) {
55 Some(end) if end <= data.len() => Ok(()),
56 _ => Err(FormatError::UnexpectedEof {
57 expected: offset.saturating_add(needed),
58 available: data.len(),
59 }),
60 }
61}
62
63fn read_offset(data: &[u8], pos: usize, size: u8) -> Result<u64, FormatError> {
64 let s = size as usize;
65 ensure_len(data, pos, s)?;
66 let slice = &data[pos..pos + s];
67 Ok(match size {
68 2 => LittleEndian::read_u16(slice) as u64,
69 4 => LittleEndian::read_u32(slice) as u64,
70 8 => LittleEndian::read_u64(slice),
71 1 => slice[0] as u64,
72 _ => {
73 return Err(FormatError::InvalidOffsetSize(size));
74 }
75 })
76}
77
78impl ObjectHeader {
79 pub fn parse(
83 data: &[u8],
84 offset: usize,
85 offset_size: u8,
86 length_size: u8,
87 ) -> Result<ObjectHeader, FormatError> {
88 ensure_len(data, offset, 4)?;
89 if data[offset..offset + 4] == OHDR_SIGNATURE {
90 Self::parse_v2(data, offset, offset_size, length_size)
91 } else {
92 Self::parse_v1(data, offset, offset_size, length_size)
93 }
94 }
95
96 fn parse_v1(
97 data: &[u8],
98 offset: usize,
99 offset_size: u8,
100 length_size: u8,
101 ) -> Result<ObjectHeader, FormatError> {
102 ensure_len(data, offset, 12)?;
105
106 let version = data[offset];
107 if version != 1 {
108 return Err(FormatError::InvalidObjectHeaderVersion(version));
109 }
110
111 let num_messages = LittleEndian::read_u16(&data[offset + 2..offset + 4]);
112 let reference_count = LittleEndian::read_u32(&data[offset + 4..offset + 8]);
113 let header_data_size = LittleEndian::read_u32(&data[offset + 8..offset + 12]) as usize;
114
115 let padding = 4; let msg_start = offset.checked_add(12 + padding).ok_or(FormatError::UnexpectedEof {
118 expected: usize::MAX,
119 available: data.len(),
120 })?;
121
122 ensure_len(data, msg_start, header_data_size)?;
123
124 let mut messages = Vec::new();
125 let mut pos = msg_start;
126 let msg_end = msg_start.checked_add(header_data_size).ok_or(FormatError::UnexpectedEof {
127 expected: usize::MAX,
128 available: data.len(),
129 })?;
130
131 for _ in 0..num_messages {
132 if pos + 8 > msg_end {
133 break;
134 }
135 let msg_type_raw = LittleEndian::read_u16(&data[pos..pos + 2]);
136 let msg_data_size = LittleEndian::read_u16(&data[pos + 2..pos + 4]) as usize;
137 let msg_flags = data[pos + 4];
138 pos += 8;
140
141 ensure_len(data, pos, msg_data_size)?;
142 let msg_type = MessageType::from_u16(msg_type_raw);
143
144 if let MessageType::Unknown(id) = msg_type {
146 if msg_flags & 0x08 != 0 {
147 return Err(FormatError::UnsupportedMessage(id));
148 }
149 }
150
151 if msg_type != MessageType::Nil {
152 messages.push(HeaderMessage {
153 msg_type,
154 size: msg_data_size,
155 flags: msg_flags,
156 creation_order: None,
157 data: data[pos..pos + msg_data_size].to_vec(),
158 });
159 }
160
161 pos += msg_data_size;
162
163 if msg_type == MessageType::ObjectHeaderContinuation {
165 let cont_msg_data = &messages.last()
166 .ok_or(FormatError::InvalidObjectHeaderSignature)?.data;
167 if cont_msg_data.len() >= (offset_size as usize + length_size as usize) {
168 let cont_offset =
169 read_offset(cont_msg_data, 0, offset_size)? as usize;
170 let cont_length =
171 read_offset(cont_msg_data, offset_size as usize, length_size)?
172 as usize;
173 let cont_msgs = Self::parse_v1_continuation(
175 data,
176 cont_offset,
177 cont_length,
178 offset_size,
179 length_size,
180 32, )?;
182 messages.extend(cont_msgs);
183 }
184 }
185 }
186
187 Ok(ObjectHeader {
188 version: 1,
189 messages,
190 reference_count: Some(reference_count),
191 flags: 0,
192 access_time: None,
193 modification_time: None,
194 change_time: None,
195 birth_time: None,
196 })
197 }
198
199 fn parse_v1_continuation(
200 data: &[u8],
201 offset: usize,
202 length: usize,
203 offset_size: u8,
204 length_size: u8,
205 depth_remaining: u16,
206 ) -> Result<Vec<HeaderMessage>, FormatError> {
207 if depth_remaining == 0 {
208 return Err(FormatError::NestingDepthExceeded);
209 }
210 ensure_len(data, offset, length)?;
211 let mut messages = Vec::new();
212 let mut pos = offset;
213 let end = offset.saturating_add(length);
214
215 while pos + 8 <= end {
216 let msg_type_raw = LittleEndian::read_u16(&data[pos..pos + 2]);
217 let msg_data_size = LittleEndian::read_u16(&data[pos + 2..pos + 4]) as usize;
218 let msg_flags = data[pos + 4];
219 pos += 8;
220
221 if pos + msg_data_size > end {
222 break;
223 }
224
225 let msg_type = MessageType::from_u16(msg_type_raw);
226
227 if let MessageType::Unknown(id) = msg_type {
228 if msg_flags & 0x08 != 0 {
229 return Err(FormatError::UnsupportedMessage(id));
230 }
231 }
232
233 if msg_type != MessageType::Nil {
234 messages.push(HeaderMessage {
235 msg_type,
236 size: msg_data_size,
237 flags: msg_flags,
238 creation_order: None,
239 data: data[pos..pos + msg_data_size].to_vec(),
240 });
241 }
242
243 pos += msg_data_size;
244
245 if msg_type == MessageType::ObjectHeaderContinuation {
247 let cont_msg_data = &messages.last()
248 .ok_or(FormatError::InvalidObjectHeaderSignature)?.data;
249 if cont_msg_data.len() >= (offset_size as usize + length_size as usize) {
250 let cont_offset =
251 read_offset(cont_msg_data, 0, offset_size)? as usize;
252 let cont_length =
253 read_offset(cont_msg_data, offset_size as usize, length_size)?
254 as usize;
255 let cont_msgs = Self::parse_v1_continuation(
256 data, cont_offset, cont_length, offset_size, length_size,
257 depth_remaining - 1,
258 )?;
259 messages.extend(cont_msgs);
260 }
261 }
262 }
263
264 Ok(messages)
265 }
266
267 fn parse_v2(
268 data: &[u8],
269 offset: usize,
270 offset_size: u8,
271 length_size: u8,
272 ) -> Result<ObjectHeader, FormatError> {
273 ensure_len(data, offset, 6)?;
275
276 let version = data[offset + 4];
277 if version != 2 {
278 return Err(FormatError::InvalidObjectHeaderVersion(version));
279 }
280 let flags = data[offset + 5];
281
282 let mut pos = offset + 6;
283
284 let (access_time, modification_time, change_time, birth_time) = if flags & 0x20 != 0 {
286 ensure_len(data, pos, 16)?;
287 let at = LittleEndian::read_u32(&data[pos..pos + 4]);
288 let mt = LittleEndian::read_u32(&data[pos + 4..pos + 8]);
289 let ct = LittleEndian::read_u32(&data[pos + 8..pos + 12]);
290 let bt = LittleEndian::read_u32(&data[pos + 12..pos + 16]);
291 pos += 16;
292 (Some(at), Some(mt), Some(ct), Some(bt))
293 } else {
294 (None, None, None, None)
295 };
296
297 if flags & 0x10 != 0 {
299 ensure_len(data, pos, 4)?;
300 pos += 4;
302 }
303
304 let chunk_size_width = match flags & 0x03 {
306 0 => 1u8,
307 1 => 2,
308 2 => 4,
309 3 => 8,
310 _ => unreachable!(),
311 };
312 ensure_len(data, pos, chunk_size_width as usize)?;
313 let chunk0_size = read_offset(data, pos, chunk_size_width)? as usize;
314 pos += chunk_size_width as usize;
315
316 let chunk0_msg_start = pos;
317 let chunk0_msg_end = pos.checked_add(chunk0_size).ok_or(FormatError::UnexpectedEof {
318 expected: usize::MAX,
319 available: data.len(),
320 })?;
321
322 ensure_len(data, chunk0_msg_end, 4)?;
324 #[cfg(feature = "checksum")]
325 {
326 let stored = LittleEndian::read_u32(&data[chunk0_msg_end..chunk0_msg_end + 4]);
327 let computed = crate::checksum::jenkins_lookup3(&data[offset..chunk0_msg_end]);
328 if computed != stored {
329 return Err(FormatError::ChecksumMismatch {
330 expected: stored,
331 computed,
332 });
333 }
334 }
335
336 let has_creation_order = flags & 0x04 != 0;
338
339 let mut messages = Vec::new();
341 let mut continuations = Vec::new();
342 Self::parse_v2_messages(
343 data,
344 chunk0_msg_start,
345 chunk0_msg_end,
346 has_creation_order,
347 offset_size,
348 length_size,
349 &mut messages,
350 &mut continuations,
351 )?;
352
353 let mut cont_remaining = 256u16;
355 while let Some((cont_offset, cont_length)) = continuations.pop() {
356 if cont_remaining == 0 {
357 return Err(FormatError::NestingDepthExceeded);
358 }
359 cont_remaining -= 1;
360 Self::parse_v2_continuation(
361 data,
362 cont_offset,
363 cont_length,
364 has_creation_order,
365 offset_size,
366 length_size,
367 &mut messages,
368 &mut continuations,
369 )?;
370 }
371
372 Ok(ObjectHeader {
373 version: 2,
374 messages,
375 reference_count: None,
376 flags,
377 access_time,
378 modification_time,
379 change_time,
380 birth_time,
381 })
382 }
383
384 #[allow(clippy::too_many_arguments)]
385 fn parse_v2_messages(
386 data: &[u8],
387 start: usize,
388 end: usize,
389 has_creation_order: bool,
390 offset_size: u8,
391 length_size: u8,
392 messages: &mut Vec<HeaderMessage>,
393 continuations: &mut Vec<(usize, usize)>,
394 ) -> Result<(), FormatError> {
395 let msg_header_size = if has_creation_order { 6 } else { 4 };
396 let mut pos = start;
397
398 while pos + msg_header_size <= end {
399 let msg_type_raw = data[pos] as u16;
400 let msg_data_size = LittleEndian::read_u16(&data[pos + 1..pos + 3]) as usize;
401 let msg_flags = data[pos + 3];
402 let creation_order = if has_creation_order {
403 Some(LittleEndian::read_u16(&data[pos + 4..pos + 6]))
404 } else {
405 None
406 };
407 pos += msg_header_size;
408
409 if pos + msg_data_size > end {
410 break;
412 }
413
414 let msg_type = MessageType::from_u16(msg_type_raw);
415
416 if let MessageType::Unknown(id) = msg_type {
417 if msg_flags & 0x08 != 0 {
418 return Err(FormatError::UnsupportedMessage(id));
419 }
420 }
421
422 let msg_data = data[pos..pos + msg_data_size].to_vec();
423
424 if msg_type == MessageType::ObjectHeaderContinuation {
425 if msg_data.len() >= (offset_size as usize + length_size as usize) {
427 let cont_off = read_offset(&msg_data, 0, offset_size)? as usize;
428 let cont_len =
429 read_offset(&msg_data, offset_size as usize, length_size)? as usize;
430 continuations.push((cont_off, cont_len));
431 }
432 } else if msg_type != MessageType::Nil {
433 messages.push(HeaderMessage {
434 msg_type,
435 size: msg_data_size,
436 flags: msg_flags,
437 creation_order,
438 data: msg_data,
439 });
440 }
441
442 pos += msg_data_size;
443 }
444
445 Ok(())
446 }
447
448 #[allow(clippy::too_many_arguments)]
449 fn parse_v2_continuation(
450 data: &[u8],
451 offset: usize,
452 length: usize,
453 has_creation_order: bool,
454 offset_size: u8,
455 length_size: u8,
456 messages: &mut Vec<HeaderMessage>,
457 continuations: &mut Vec<(usize, usize)>,
458 ) -> Result<(), FormatError> {
459 ensure_len(data, offset, length)?;
461 if length < 8 {
462 return Err(FormatError::UnexpectedEof {
463 expected: 8,
464 available: length,
465 });
466 }
467
468 ensure_len(data, offset, 4)?;
469 if data[offset..offset + 4] != OCHK_SIGNATURE {
470 return Err(FormatError::InvalidObjectHeaderSignature);
471 }
472
473 let msg_start = offset + 4;
474 let checksum_pos = offset + length - 4;
475
476 #[cfg(feature = "checksum")]
477 {
478 let stored = LittleEndian::read_u32(&data[checksum_pos..checksum_pos + 4]);
479 let computed = crate::checksum::jenkins_lookup3(&data[offset..checksum_pos]);
480 if computed != stored {
481 return Err(FormatError::ChecksumMismatch {
482 expected: stored,
483 computed,
484 });
485 }
486 }
487
488 Self::parse_v2_messages(
489 data,
490 msg_start,
491 checksum_pos,
492 has_creation_order,
493 offset_size,
494 length_size,
495 messages,
496 continuations,
497 )
498 }
499}
500
501#[cfg(test)]
502mod tests {
503 use super::*;
504
505 fn build_v1_header(
507 messages: &[(u16, &[u8], u8)], offset_size: u8,
509 length_size: u8,
510 ) -> Vec<u8> {
511 let _ = (offset_size, length_size);
512 let mut msg_bytes = Vec::new();
514 for (mtype, mdata, mflags) in messages {
515 msg_bytes.extend_from_slice(&mtype.to_le_bytes()); msg_bytes.extend_from_slice(&(mdata.len() as u16).to_le_bytes()); msg_bytes.push(*mflags); msg_bytes.extend_from_slice(&[0u8; 3]); msg_bytes.extend_from_slice(mdata); }
521
522 let mut buf = Vec::new();
523 buf.push(1); buf.push(0); buf.extend_from_slice(&(messages.len() as u16).to_le_bytes()); buf.extend_from_slice(&1u32.to_le_bytes()); buf.extend_from_slice(&(msg_bytes.len() as u32).to_le_bytes()); buf.extend_from_slice(&[0u8; 4]);
530 buf.extend_from_slice(&msg_bytes);
531 buf
532 }
533
534 fn build_v2_header(
536 flags: u8,
537 messages: &[(u8, &[u8], u8)], timestamps: Option<(u32, u32, u32, u32)>,
539 ) -> Vec<u8> {
540 let has_creation_order = flags & 0x04 != 0;
541 let has_timestamps = flags & 0x20 != 0;
542 let mut buf = Vec::new();
543 buf.extend_from_slice(&OHDR_SIGNATURE); buf.push(2); buf.push(flags);
546
547 if has_timestamps {
548 if let Some((at, mt, ct, bt)) = timestamps {
549 buf.extend_from_slice(&at.to_le_bytes());
550 buf.extend_from_slice(&mt.to_le_bytes());
551 buf.extend_from_slice(&ct.to_le_bytes());
552 buf.extend_from_slice(&bt.to_le_bytes());
553 }
554 }
555
556 if flags & 0x10 != 0 {
557 buf.extend_from_slice(&8u16.to_le_bytes()); buf.extend_from_slice(&6u16.to_le_bytes()); }
560
561 let mut msg_bytes = Vec::new();
563 for (mtype, mdata, mflags) in messages {
564 msg_bytes.push(*mtype); msg_bytes.extend_from_slice(&(mdata.len() as u16).to_le_bytes()); msg_bytes.push(*mflags); if has_creation_order {
568 msg_bytes.extend_from_slice(&0u16.to_le_bytes()); }
570 msg_bytes.extend_from_slice(mdata);
571 }
572
573 let chunk_size = msg_bytes.len();
574 match flags & 0x03 {
576 0 => buf.push(chunk_size as u8),
577 1 => buf.extend_from_slice(&(chunk_size as u16).to_le_bytes()),
578 2 => buf.extend_from_slice(&(chunk_size as u32).to_le_bytes()),
579 3 => buf.extend_from_slice(&(chunk_size as u64).to_le_bytes()),
580 _ => unreachable!(),
581 }
582
583 buf.extend_from_slice(&msg_bytes);
584
585 let checksum = crate::checksum::jenkins_lookup3(&buf);
587 buf.extend_from_slice(&checksum.to_le_bytes());
588 buf
589 }
590
591 #[test]
592 fn parse_v1_zero_messages() {
593 let data = build_v1_header(&[], 8, 8);
594 let hdr = ObjectHeader::parse(&data, 0, 8, 8).unwrap();
595 assert_eq!(hdr.version, 1);
596 assert_eq!(hdr.messages.len(), 0);
597 assert_eq!(hdr.reference_count, Some(1));
598 assert_eq!(hdr.flags, 0);
599 }
600
601 #[test]
602 fn parse_v1_two_messages() {
603 let messages = [
604 (0x0001u16, &[1u8, 2, 3, 4][..], 0u8), (0x0008, &[5u8, 6][..], 0), ];
607 let data = build_v1_header(&messages, 8, 8);
608 let hdr = ObjectHeader::parse(&data, 0, 8, 8).unwrap();
609 assert_eq!(hdr.messages.len(), 2);
610 assert_eq!(hdr.messages[0].msg_type, MessageType::Dataspace);
611 assert_eq!(hdr.messages[0].data, vec![1, 2, 3, 4]);
612 assert_eq!(hdr.messages[1].msg_type, MessageType::DataLayout);
613 assert_eq!(hdr.messages[1].data, vec![5, 6]);
614 }
615
616 #[test]
617 fn parse_v1_unknown_message_ok() {
618 let messages = [(0x00FFu16, &[0xAA, 0xBB][..], 0u8)];
619 let data = build_v1_header(&messages, 8, 8);
620 let hdr = ObjectHeader::parse(&data, 0, 8, 8).unwrap();
621 assert_eq!(hdr.messages.len(), 1);
622 assert_eq!(hdr.messages[0].msg_type, MessageType::Unknown(0x00FF));
623 }
624
625 #[test]
626 fn parse_v1_unknown_must_understand_errors() {
627 let messages = [(0x00FFu16, &[0xAA][..], 0x08u8)];
629 let data = build_v1_header(&messages, 8, 8);
630 let err = ObjectHeader::parse(&data, 0, 8, 8).unwrap_err();
631 assert_eq!(err, FormatError::UnsupportedMessage(0x00FF));
632 }
633
634 #[test]
635 fn parse_v2_no_timestamps_one_message() {
636 let data = build_v2_header(0x00, &[(0x01, &[10, 20], 0)], None);
637 let hdr = ObjectHeader::parse(&data, 0, 8, 8).unwrap();
638 assert_eq!(hdr.version, 2);
639 assert_eq!(hdr.flags, 0);
640 assert_eq!(hdr.messages.len(), 1);
641 assert_eq!(hdr.messages[0].msg_type, MessageType::Dataspace);
642 assert_eq!(hdr.messages[0].data, vec![10, 20]);
643 assert!(hdr.access_time.is_none());
644 }
645
646 #[test]
647 fn parse_v2_with_timestamps() {
648 let data = build_v2_header(
649 0x20,
650 &[(0x01, &[1], 0)],
651 Some((100, 200, 300, 400)),
652 );
653 let hdr = ObjectHeader::parse(&data, 0, 8, 8).unwrap();
654 assert_eq!(hdr.access_time, Some(100));
655 assert_eq!(hdr.modification_time, Some(200));
656 assert_eq!(hdr.change_time, Some(300));
657 assert_eq!(hdr.birth_time, Some(400));
658 assert_eq!(hdr.messages.len(), 1);
659 assert!(hdr.messages[0].creation_order.is_none());
661 }
662
663 #[test]
664 fn parse_v2_creation_order() {
665 let data = build_v2_header(
669 0x24,
670 &[(0x03, &[9], 0), (0x05, &[8], 0)],
671 Some((0, 0, 0, 0)),
672 );
673 let hdr = ObjectHeader::parse(&data, 0, 8, 8).unwrap();
674 assert_eq!(hdr.messages.len(), 2);
675 assert!(hdr.messages[0].creation_order.is_some());
676 assert!(hdr.messages[1].creation_order.is_some());
677 assert_eq!(hdr.access_time, Some(0));
678 }
679
680 #[test]
681 fn parse_v2_checksum_valid() {
682 let data = build_v2_header(0x00, &[(0x01, &[1, 2, 3], 0)], None);
683 let hdr = ObjectHeader::parse(&data, 0, 8, 8).unwrap();
685 assert_eq!(hdr.messages.len(), 1);
686 }
687
688 #[test]
689 fn parse_v2_checksum_invalid() {
690 let mut data = build_v2_header(0x00, &[(0x01, &[1, 2, 3], 0)], None);
691 let len = data.len();
693 data[len - 1] ^= 0xFF;
694 let err = ObjectHeader::parse(&data, 0, 8, 8).unwrap_err();
695 assert!(matches!(err, FormatError::ChecksumMismatch { .. }));
696 }
697
698 #[test]
699 fn parse_v2_nil_padding_skipped() {
700 let data = build_v2_header(
701 0x00,
702 &[
703 (0x00, &[0, 0, 0, 0], 0), (0x01, &[42], 0), ],
706 None,
707 );
708 let hdr = ObjectHeader::parse(&data, 0, 8, 8).unwrap();
709 assert_eq!(hdr.messages.len(), 1);
710 assert_eq!(hdr.messages[0].msg_type, MessageType::Dataspace);
711 }
712
713 #[test]
714 fn parse_v2_chunk_size_1byte() {
715 let data = build_v2_header(0x00, &[(0x01, &[1], 0)], None);
717 let hdr = ObjectHeader::parse(&data, 0, 8, 8).unwrap();
718 assert_eq!(hdr.messages.len(), 1);
719 }
720
721 #[test]
722 fn parse_v2_chunk_size_2byte() {
723 let data = build_v2_header(0x01, &[(0x01, &[1], 0)], None);
724 let hdr = ObjectHeader::parse(&data, 0, 8, 8).unwrap();
725 assert_eq!(hdr.messages.len(), 1);
726 }
727
728 #[test]
729 fn parse_v2_chunk_size_4byte() {
730 let data = build_v2_header(0x02, &[(0x01, &[1], 0)], None);
731 let hdr = ObjectHeader::parse(&data, 0, 8, 8).unwrap();
732 assert_eq!(hdr.messages.len(), 1);
733 }
734
735 #[test]
736 fn parse_v2_continuation() {
737 let ochk_offset = 256usize;
739 let ochk_msg_type = 0x03u8; let ochk_msg_data = [0xDE, 0xAD];
741
742 let mut ochk_buf = Vec::new();
744 ochk_buf.extend_from_slice(&OCHK_SIGNATURE);
745 ochk_buf.push(ochk_msg_type);
746 ochk_buf.extend_from_slice(&(ochk_msg_data.len() as u16).to_le_bytes());
747 ochk_buf.push(0); ochk_buf.extend_from_slice(&ochk_msg_data);
749 let checksum = crate::checksum::jenkins_lookup3(&ochk_buf);
750 ochk_buf.extend_from_slice(&checksum.to_le_bytes());
751
752 let ochk_length = ochk_buf.len();
753
754 let mut cont_data = Vec::new();
756 cont_data.extend_from_slice(&(ochk_offset as u64).to_le_bytes());
757 cont_data.extend_from_slice(&(ochk_length as u64).to_le_bytes());
758
759 let header = build_v2_header(
761 0x00,
762 &[
763 (0x01, &[42], 0), (0x10, &cont_data, 0), ],
766 None,
767 );
768
769 let total_size = ochk_offset + ochk_buf.len();
771 let mut file_data = vec![0u8; total_size];
772 file_data[..header.len()].copy_from_slice(&header);
773 file_data[ochk_offset..ochk_offset + ochk_buf.len()].copy_from_slice(&ochk_buf);
774
775 let hdr = ObjectHeader::parse(&file_data, 0, 8, 8).unwrap();
776 assert_eq!(hdr.messages.len(), 2);
777 assert_eq!(hdr.messages[0].msg_type, MessageType::Dataspace);
778 assert_eq!(hdr.messages[1].msg_type, MessageType::Datatype);
779 assert_eq!(hdr.messages[1].data, vec![0xDE, 0xAD]);
780 }
781
782 #[test]
783 fn truncated_v1_header() {
784 let data = vec![1u8, 0]; let err = ObjectHeader::parse(&data, 0, 8, 8).unwrap_err();
786 assert!(matches!(err, FormatError::UnexpectedEof { .. }));
787 }
788
789 #[test]
790 fn truncated_v2_header() {
791 let data = [b'O', b'H', b'D', b'R', 2]; let err = ObjectHeader::parse(&data, 0, 8, 8).unwrap_err();
793 assert!(matches!(err, FormatError::UnexpectedEof { .. }));
794 }
795}