1use crate::format::bytes::{read_le_addr, read_le_uint};
38use crate::format::messages::datatype::ReferenceKind;
39use crate::format::selection::Selection;
40use crate::format::{FormatContext, FormatError, FormatResult, UNDEF_ADDR};
41
42fn target_address(elem: &[u8], sizeof_addr: usize) -> Option<u64> {
53 match read_le_addr(elem, sizeof_addr) {
54 0 | UNDEF_ADDR => None,
55 addr => Some(addr),
56 }
57}
58
59pub fn decode_object_element(elem: &[u8], ctx: &FormatContext) -> FormatResult<Option<u64>> {
61 let sa = ctx.sizeof_addr as usize;
62 if elem.len() < sa {
63 return Err(FormatError::BufferTooShort {
64 needed: sa,
65 available: elem.len(),
66 });
67 }
68 Ok(target_address(elem, sa))
69}
70
71pub fn decode_region_element(elem: &[u8], ctx: &FormatContext) -> FormatResult<Option<(u64, u32)>> {
74 let sa = ctx.sizeof_addr as usize;
75 if elem.len() < sa + 4 {
76 return Err(FormatError::BufferTooShort {
77 needed: sa + 4,
78 available: elem.len(),
79 });
80 }
81 let Some(addr) = target_address(elem, sa) else {
82 return Ok(None);
83 };
84 let idx = u32::from_le_bytes([elem[sa], elem[sa + 1], elem[sa + 2], elem[sa + 3]]);
85 Ok(Some((addr, idx)))
86}
87
88pub fn decode_region_heap_object(
91 data: &[u8],
92 ctx: &FormatContext,
93) -> FormatResult<(u64, Selection)> {
94 let sa = ctx.sizeof_addr as usize;
95 if data.len() < sa {
96 return Err(FormatError::BufferTooShort {
97 needed: sa,
98 available: data.len(),
99 });
100 }
101 let addr = read_le_addr(data, sa);
102 let (selection, _) = Selection::decode(&data[sa..])?;
103 Ok((addr, selection))
104}
105
106const REVISED_FLAG_EXTERNAL: u8 = 0x01;
109
110const REVISED_HEADER: usize = 2;
112
113#[derive(Debug, Clone, PartialEq, Eq)]
119pub enum RevisedElement<'a> {
120 Null,
122 Inline {
126 kind: ReferenceKind,
128 body: &'a [u8],
130 },
131 Heap {
133 kind: ReferenceKind,
135 external: bool,
138 collection: u64,
140 index: u32,
142 },
143}
144
145pub fn decode_revised_element<'a>(
151 elem: &'a [u8],
152 ctx: &FormatContext,
153) -> FormatResult<RevisedElement<'a>> {
154 let sa = ctx.sizeof_addr as usize;
155 let heap_id = REVISED_HEADER + 4;
156 if elem.len() < REVISED_HEADER {
157 return Err(FormatError::BufferTooShort {
158 needed: REVISED_HEADER,
159 available: elem.len(),
160 });
161 }
162 let (code, flags) = (elem[0], elem[1]);
163
164 if code == 0 {
169 if elem.len() < heap_id + sa {
170 return Err(FormatError::BufferTooShort {
171 needed: heap_id + sa,
172 available: elem.len(),
173 });
174 }
175 return match read_le_addr(&elem[heap_id..], sa) {
176 0 => Ok(RevisedElement::Null),
177 addr => Err(FormatError::InvalidData(format!(
178 "reference type 0 over a blob at {addr:#x}, which is not the nil id a null \
179 reference carries"
180 ))),
181 };
182 }
183
184 let kind = ReferenceKind::from_code(code)
185 .filter(|k| k.is_revised())
186 .ok_or_else(|| {
187 FormatError::InvalidData(format!("reference type {code} in a 1.12 element"))
188 })?;
189
190 let external = flags & REVISED_FLAG_EXTERNAL != 0;
191
192 if !external && kind == ReferenceKind::Object2 {
193 return Ok(RevisedElement::Inline {
194 kind,
195 body: &elem[REVISED_HEADER..],
196 });
197 }
198
199 if elem.len() < heap_id + sa + 4 {
200 return Err(FormatError::BufferTooShort {
201 needed: heap_id + sa + 4,
202 available: elem.len(),
203 });
204 }
205 let collection = read_le_addr(&elem[heap_id..], sa);
206 let index = read_le_uint(&elem[heap_id + sa..heap_id + sa + 4], 4) as u32;
207 Ok(RevisedElement::Heap {
208 kind,
209 external,
210 collection,
211 index,
212 })
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222pub enum ReferenceElementImage {
223 Legacy(u64),
226 Inline(u64),
230 Blob {
235 kind: ReferenceKind,
237 size: u32,
240 collection: u64,
242 index: u32,
244 },
245}
246
247pub fn encode_reference_element(
255 image: &ReferenceElementImage,
256 size: usize,
257 ctx: &FormatContext,
258) -> FormatResult<Vec<u8>> {
259 let sa = ctx.sizeof_addr as usize;
260 let mut elem = vec![0u8; size];
261 let (what, needed) = match image {
262 ReferenceElementImage::Legacy(_) => ("an H5R_OBJECT1", sa),
263 ReferenceElementImage::Inline(_) => ("an H5R_OBJECT2", REVISED_HEADER + 1 + sa),
264 ReferenceElementImage::Blob { kind, .. } => (
265 match kind {
266 ReferenceKind::DatasetRegion2 => "an H5R_DATASET_REGION2",
267 ReferenceKind::Attr => "an H5R_ATTR",
268 _ => "a blob-backed",
269 },
270 REVISED_HEADER + 4 + sa + 4,
271 ),
272 };
273 if needed > size {
274 return Err(FormatError::InvalidData(format!(
275 "{what} element needs {needed} bytes but its datatype declares {size}"
276 )));
277 }
278 match *image {
279 ReferenceElementImage::Legacy(address) => {
280 elem[..sa].copy_from_slice(&address.to_le_bytes()[..sa]);
281 }
282 ReferenceElementImage::Inline(address) => {
285 elem[0] = ReferenceKind::Object2.code();
286 elem[1] = 0;
287 elem[2] = sa as u8;
288 let at = REVISED_HEADER + 1;
289 elem[at..at + sa].copy_from_slice(&address.to_le_bytes()[..sa]);
290 }
291 ReferenceElementImage::Blob {
292 kind,
293 size: blob_size,
294 collection,
295 index,
296 } => {
297 elem[0] = kind.code();
298 elem[1] = 0;
299 elem[REVISED_HEADER..REVISED_HEADER + 4].copy_from_slice(&blob_size.to_le_bytes());
300 let at = REVISED_HEADER + 4;
301 elem[at..at + sa].copy_from_slice(&collection.to_le_bytes()[..sa]);
302 elem[at + sa..at + sa + 4].copy_from_slice(&index.to_le_bytes());
303 }
304 }
305 Ok(elem)
306}
307
308pub fn encode_revised_blob(
322 address: u64,
323 target: &ReferenceTarget,
324 extent_rank: usize,
325 ctx: &FormatContext,
326) -> FormatResult<Vec<u8>> {
327 let sa = ctx.sizeof_addr as usize;
328 let mut blob = Vec::with_capacity(1 + sa + 16);
329 blob.push(sa as u8);
330 blob.extend_from_slice(&address.to_le_bytes()[..sa]);
331 match target {
332 ReferenceTarget::Object => {}
333 ReferenceTarget::Region(selection) => {
336 let bytes = selection.encode()?;
337 blob.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
338 blob.extend_from_slice(&(extent_rank as u32).to_le_bytes());
339 blob.extend_from_slice(&bytes);
340 }
341 ReferenceTarget::Attribute(name) => {
343 let len = u16::try_from(name.len()).map_err(|_| {
344 FormatError::InvalidData(format!(
345 "attribute name of {} bytes does not fit a reference's 16-bit length",
346 name.len()
347 ))
348 })?;
349 blob.extend_from_slice(&len.to_le_bytes());
350 blob.extend_from_slice(name.as_bytes());
351 }
352 }
353 Ok(blob)
354}
355
356pub const REVISED_BLOB_TOKEN_OFFSET: usize = 1;
359
360#[derive(Debug, Clone, PartialEq, Eq)]
362pub enum ReferenceTarget {
363 Object,
365 Region(Selection),
367 Attribute(String),
369}
370
371#[derive(Debug, Clone, PartialEq, Eq)]
374pub struct DecodedReference {
375 pub address: u64,
377 pub file: Option<String>,
382 pub target: ReferenceTarget,
384}
385
386pub fn decode_revised_body(
395 kind: ReferenceKind,
396 external: bool,
397 body: &[u8],
398 ctx: &FormatContext,
399) -> FormatResult<Option<DecodedReference>> {
400 let sa = ctx.sizeof_addr as usize;
401 let mut r = Cursor::new(body);
402
403 let token_size = r.u8()? as usize;
408 if token_size != sa {
409 return Err(FormatError::UnsupportedFeature(format!(
410 "object tokens {token_size} bytes wide, not the {sa}-byte file addresses the \
411 native format uses"
412 )));
413 }
414 let token = r.take(token_size)?;
415 let Some(address) = target_address(token, sa) else {
416 return Ok(None);
417 };
418
419 let file = if external {
422 Some(decode_string(&mut r)?)
423 } else {
424 None
425 };
426
427 let target = match kind {
428 ReferenceKind::Object2 => ReferenceTarget::Object,
429 ReferenceKind::DatasetRegion2 => {
430 let len = r.u32()? as usize;
434 let _rank = r.u32()?;
435 ReferenceTarget::Region(Selection::decode(r.take(len)?)?.0)
436 }
437 ReferenceKind::Attr => ReferenceTarget::Attribute(decode_string(&mut r)?),
438 ReferenceKind::Object1 | ReferenceKind::DatasetRegion1 => {
439 return Err(FormatError::InvalidData(format!(
440 "{kind:?} is not a 1.12 encoded reference"
441 )))
442 }
443 };
444 Ok(Some(DecodedReference {
445 address,
446 file,
447 target,
448 }))
449}
450
451fn decode_string(r: &mut Cursor<'_>) -> FormatResult<String> {
455 let len = r.u16()? as usize;
456 let bytes = r.take(len)?;
457 String::from_utf8(bytes.to_vec())
458 .map_err(|_| FormatError::InvalidData("a string in a reference is not UTF-8".into()))
459}
460
461#[derive(Debug, Clone, PartialEq, Eq)]
468pub enum Reference {
469 Null,
472 Object {
474 address: u64,
476 file: Option<String>,
478 path: Option<String>,
480 },
481 Region {
484 address: u64,
486 file: Option<String>,
488 path: Option<String>,
490 selection: Selection,
492 },
493 Attr {
496 address: u64,
498 file: Option<String>,
500 path: Option<String>,
502 name: String,
504 },
505}
506
507impl Reference {
508 pub fn file(&self) -> Option<&str> {
517 match self {
518 Self::Null => None,
519 Self::Object { file, .. } | Self::Region { file, .. } | Self::Attr { file, .. } => {
520 file.as_deref()
521 }
522 }
523 }
524
525 pub fn path(&self) -> Option<&str> {
527 match self {
528 Self::Null => None,
529 Self::Object { path, .. } | Self::Region { path, .. } | Self::Attr { path, .. } => {
530 path.as_deref()
531 }
532 }
533 }
534
535 pub fn address(&self) -> Option<u64> {
537 match self {
538 Self::Null => None,
539 Self::Object { address, .. }
540 | Self::Region { address, .. }
541 | Self::Attr { address, .. } => Some(*address),
542 }
543 }
544
545 pub fn attribute_name(&self) -> Option<&str> {
547 match self {
548 Self::Attr { name, .. } => Some(name),
549 _ => None,
550 }
551 }
552
553 pub fn selection(&self) -> Option<&Selection> {
555 match self {
556 Self::Region { selection, .. } => Some(selection),
557 _ => None,
558 }
559 }
560
561 pub fn bounds(&self) -> Option<(Vec<u64>, Vec<u64>)> {
564 self.selection()?.bounds()
565 }
566
567 pub fn is_null(&self) -> bool {
569 matches!(self, Self::Null)
570 }
571}
572
573struct Cursor<'a> {
575 buf: &'a [u8],
576 pos: usize,
577}
578
579impl<'a> Cursor<'a> {
580 fn new(buf: &'a [u8]) -> Self {
581 Self { buf, pos: 0 }
582 }
583
584 fn take(&mut self, n: usize) -> FormatResult<&'a [u8]> {
585 let end = self.pos.checked_add(n).ok_or(FormatError::BufferTooShort {
586 needed: usize::MAX,
587 available: self.buf.len(),
588 })?;
589 if end > self.buf.len() {
590 return Err(FormatError::BufferTooShort {
591 needed: end,
592 available: self.buf.len(),
593 });
594 }
595 let out = &self.buf[self.pos..end];
596 self.pos = end;
597 Ok(out)
598 }
599
600 fn u8(&mut self) -> FormatResult<u8> {
601 Ok(self.take(1)?[0])
602 }
603
604 fn u16(&mut self) -> FormatResult<u16> {
605 let b = self.take(2)?;
606 Ok(u16::from_le_bytes([b[0], b[1]]))
607 }
608
609 fn u32(&mut self) -> FormatResult<u32> {
610 let b = self.take(4)?;
611 Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
612 }
613}
614
615#[cfg(test)]
616mod tests {
617 use super::*;
618
619 use crate::format::selection::{Hyperslab, HyperslabBlock, PointSelection};
620
621 fn ctx() -> FormatContext {
622 FormatContext::default_v3()
623 }
624
625 const REGION_HEAP_OBJECT: [u8; 40] = [
629 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, ];
639
640 #[test]
641 fn region_heap_object_from_libhdf5_decodes() {
642 let (addr, selection) = decode_region_heap_object(®ION_HEAP_OBJECT, &ctx()).unwrap();
643 assert_eq!(addr, 0x320);
644 assert_eq!(
645 selection,
646 Selection::Hyperslab {
647 rank: 1,
648 form: Hyperslab::Blocks(vec![HyperslabBlock {
649 start: vec![0],
650 end: vec![2],
651 }]),
652 }
653 );
654 assert_eq!(selection.bounds(), Some((vec![0], vec![2])));
655 }
656
657 #[test]
658 fn object_and_region_elements_report_null() {
659 assert_eq!(
660 decode_object_element(&0x320u64.to_le_bytes(), &ctx()).unwrap(),
661 Some(0x320)
662 );
663 assert_eq!(
664 decode_object_element(&[0xFF; 8], &ctx()).unwrap(),
665 None,
666 "an undefined address is a null reference"
667 );
668 assert_eq!(
669 decode_object_element(&[0; 8], &ctx()).unwrap(),
670 None,
671 "so is address 0, which h5py writes for an unset element"
672 );
673 let mut elem = [0u8; 12];
674 elem[..8].copy_from_slice(&0x820u64.to_le_bytes());
675 elem[8..].copy_from_slice(&2u32.to_le_bytes());
676 assert_eq!(
677 decode_region_element(&elem, &ctx()).unwrap(),
678 Some((0x820, 2))
679 );
680 assert_eq!(
681 decode_region_element(&[0u8; 12], &ctx()).unwrap(),
682 None,
683 "a zeroed element carries no heap id"
684 );
685 }
686
687 const OBJ2_ELEMENT: [u8; 18] = [
697 0x02, 0x00, 0x08, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
698 0x00, 0x00, 0x00,
699 ];
700
701 const REGION2_ELEMENT: [u8; 18] = [
703 0x03, 0x00, 0x39, 0x00, 0x00, 0x00, 0xA8, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
704 0x00, 0x00, 0x00,
705 ];
706
707 const ATTR_ELEMENT: [u8; 18] = [
709 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xA8, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
710 0x00, 0x00, 0x00,
711 ];
712
713 const REGION2_HYPERSLAB_BLOB: [u8; 57] = [
717 0x08, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x02, 0x00,
718 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00,
719 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00,
720 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
721 ];
722
723 const REGION2_POINT_BLOB: [u8; 57] = [
725 0x08, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00,
726 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00,
727 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
728 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
729 ];
730
731 const ATTR_BLOB: [u8; 15] = [
733 0x08, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x6E, 0x6F, 0x74, 0x65,
734 ];
735
736 const EXT_FILE: &str = "tests/fixtures/ext_ref_target.h5";
740
741 const EXT_OBJ2_ELEMENT: [u8; 18] = [
745 0x02, 0x01, 0x2B, 0x00, 0x00, 0x00, 0x24, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
746 0x00, 0x00, 0x00,
747 ];
748
749 const EXT_OBJ2_BLOB: [u8; 43] = [
751 0x08, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x74, 0x65, 0x73, 0x74,
752 0x73, 0x2F, 0x66, 0x69, 0x78, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2F, 0x65, 0x78, 0x74, 0x5F,
753 0x72, 0x65, 0x66, 0x5F, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2E, 0x68, 0x35,
754 ];
755
756 const EXT_ATTR_BLOB: [u8; 49] = [
760 0x08, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x74, 0x65, 0x73, 0x74,
761 0x73, 0x2F, 0x66, 0x69, 0x78, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2F, 0x65, 0x78, 0x74, 0x5F,
762 0x72, 0x65, 0x66, 0x5F, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2E, 0x68, 0x35, 0x04, 0x00,
763 0x6E, 0x6F, 0x74, 0x65,
764 ];
765
766 #[test]
769 fn revised_elements_from_libhdf5_split_into_kind_and_body() {
770 let RevisedElement::Inline { kind, body } =
771 decode_revised_element(&OBJ2_ELEMENT, &ctx()).unwrap()
772 else {
773 panic!("an object reference is stored in the element");
774 };
775 assert_eq!(kind, ReferenceKind::Object2);
776 assert_eq!(
777 decode_revised_body(kind, false, body, &ctx()).unwrap(),
778 Some(DecodedReference {
779 address: 0xC3,
780 file: None,
781 target: ReferenceTarget::Object,
782 })
783 );
784
785 assert_eq!(
786 decode_revised_element(®ION2_ELEMENT, &ctx()).unwrap(),
787 RevisedElement::Heap {
788 kind: ReferenceKind::DatasetRegion2,
789 external: false,
790 collection: 0x8A8,
791 index: 1,
792 }
793 );
794 assert_eq!(
795 decode_revised_element(&ATTR_ELEMENT, &ctx()).unwrap(),
796 RevisedElement::Heap {
797 kind: ReferenceKind::Attr,
798 external: false,
799 collection: 0x8A8,
800 index: 3,
801 }
802 );
803 }
804
805 #[test]
808 fn revised_region_blobs_decode_to_their_selections() {
809 let hyper = decode_revised_body(
810 ReferenceKind::DatasetRegion2,
811 false,
812 ®ION2_HYPERSLAB_BLOB,
813 &ctx(),
814 )
815 .unwrap()
816 .unwrap();
817 assert_eq!(hyper.address, 0xC3);
818 let ReferenceTarget::Region(selection) = &hyper.target else {
819 panic!("a region reference names a selection");
820 };
821 assert_eq!(
822 selection.bounds(),
823 Some((vec![1, 2], vec![2, 4])),
824 "{selection:?}"
825 );
826
827 let points = decode_revised_body(
828 ReferenceKind::DatasetRegion2,
829 false,
830 ®ION2_POINT_BLOB,
831 &ctx(),
832 )
833 .unwrap()
834 .unwrap();
835 let ReferenceTarget::Region(selection) = &points.target else {
836 panic!("a region reference names a selection");
837 };
838 assert_eq!(
839 selection,
840 &Selection::Points(PointSelection {
841 rank: 2,
842 points: vec![vec![0, 1], vec![3, 5]],
843 })
844 );
845 assert_eq!(selection.bounds(), Some((vec![0, 1], vec![3, 5])));
846 }
847
848 #[test]
849 fn an_attribute_reference_carries_its_name() {
850 assert_eq!(
851 decode_revised_body(ReferenceKind::Attr, false, &ATTR_BLOB, &ctx()).unwrap(),
852 Some(DecodedReference {
853 address: 0xC3,
854 file: None,
855 target: ReferenceTarget::Attribute("note".into()),
856 })
857 );
858 }
859
860 #[test]
865 fn revised_elements_that_name_nothing_or_cannot_be_followed() {
866 assert_eq!(
867 decode_revised_element(&[0u8; 18], &ctx()).unwrap(),
868 RevisedElement::Null
869 );
870
871 let mut stale = [0u8; 18];
872 stale[6] = 0xA8;
873 stale[7] = 0x08;
874 assert!(
875 matches!(
876 decode_revised_element(&stale, &ctx()).unwrap_err(),
877 FormatError::InvalidData(_)
878 ),
879 "reference type 0 over a live blob is not a null reference"
880 );
881
882 let mut old_code = OBJ2_ELEMENT;
883 old_code[0] = ReferenceKind::DatasetRegion1.code();
884 assert!(matches!(
885 decode_revised_element(&old_code, &ctx()).unwrap_err(),
886 FormatError::InvalidData(_)
887 ));
888
889 let mut foreign = ATTR_BLOB;
890 foreign[0] = 16;
891 assert!(
892 matches!(
893 decode_revised_body(ReferenceKind::Attr, false, &foreign, &ctx()).unwrap_err(),
894 FormatError::UnsupportedFeature(_)
895 ),
896 "a token that is not a file address belongs to another VOL connector"
897 );
898
899 let mut unset = OBJ2_ELEMENT;
900 unset[3] = 0;
901 let RevisedElement::Inline { kind, body } = decode_revised_element(&unset, &ctx()).unwrap()
902 else {
903 panic!("an object reference is stored in the element");
904 };
905 assert_eq!(
906 decode_revised_body(kind, false, body, &ctx()).unwrap(),
907 None,
908 "a zero token names no object"
909 );
910 }
911
912 #[test]
916 fn an_external_reference_names_the_file_its_target_is_in() {
917 assert_eq!(
918 decode_revised_element(&EXT_OBJ2_ELEMENT, &ctx()).unwrap(),
919 RevisedElement::Heap {
920 kind: ReferenceKind::Object2,
921 external: true,
922 collection: 0x824,
923 index: 1,
924 }
925 );
926 assert_eq!(
927 decode_revised_body(ReferenceKind::Object2, true, &EXT_OBJ2_BLOB, &ctx()).unwrap(),
928 Some(DecodedReference {
929 address: 0x320,
930 file: Some(EXT_FILE.into()),
931 target: ReferenceTarget::Object,
932 })
933 );
934
935 assert_eq!(
938 decode_revised_body(ReferenceKind::Attr, true, &EXT_ATTR_BLOB, &ctx()).unwrap(),
939 Some(DecodedReference {
940 address: 0x320,
941 file: Some(EXT_FILE.into()),
942 target: ReferenceTarget::Attribute("note".into()),
943 })
944 );
945 let internal = decode_revised_body(ReferenceKind::Attr, false, &EXT_ATTR_BLOB, &ctx())
946 .unwrap()
947 .unwrap();
948 assert_eq!(
949 internal.target,
950 ReferenceTarget::Attribute(EXT_FILE.into()),
951 "the flag is what tells the file name from the attribute name"
952 );
953 }
954
955 #[test]
959 fn revised_elements_encode_to_the_libhdf5_images() {
960 assert_eq!(
961 encode_reference_element(&ReferenceElementImage::Inline(0xC3), 18, &ctx()).unwrap(),
962 OBJ2_ELEMENT
963 );
964 assert_eq!(
965 encode_reference_element(
966 &ReferenceElementImage::Blob {
967 kind: ReferenceKind::DatasetRegion2,
968 size: REGION2_HYPERSLAB_BLOB.len() as u32,
969 collection: 0x8A8,
970 index: 1,
971 },
972 18,
973 &ctx()
974 )
975 .unwrap(),
976 REGION2_ELEMENT
977 );
978 assert_eq!(
979 encode_reference_element(
980 &ReferenceElementImage::Blob {
981 kind: ReferenceKind::Attr,
982 size: ATTR_BLOB.len() as u32,
983 collection: 0x8A8,
984 index: 3,
985 },
986 18,
987 &ctx()
988 )
989 .unwrap(),
990 ATTR_ELEMENT
991 );
992 assert_eq!(
994 encode_reference_element(&ReferenceElementImage::Legacy(0xC3), 8, &ctx()).unwrap(),
995 [0xC3, 0, 0, 0, 0, 0, 0, 0]
996 );
997 }
998
999 #[test]
1002 fn an_element_narrower_than_its_layout_is_refused() {
1003 let err = encode_reference_element(
1004 &ReferenceElementImage::Blob {
1005 kind: ReferenceKind::Attr,
1006 size: 15,
1007 collection: 0x8A8,
1008 index: 3,
1009 },
1010 11,
1011 &ctx(),
1012 )
1013 .unwrap_err();
1014 assert!(matches!(err, FormatError::InvalidData(_)), "{err:?}");
1015 assert!(encode_reference_element(&ReferenceElementImage::Inline(0xC3), 11, &ctx()).is_ok());
1016 }
1017
1018 #[test]
1028 fn revised_blobs_encode_to_what_libhdf5_reads_back() {
1029 let attr = decode_revised_body(ReferenceKind::Attr, false, &ATTR_BLOB, &ctx())
1030 .unwrap()
1031 .unwrap();
1032 assert_eq!(
1033 encode_revised_blob(attr.address, &attr.target, 0, &ctx()).unwrap(),
1034 ATTR_BLOB
1035 );
1036
1037 for (kind, golden) in [
1038 (ReferenceKind::DatasetRegion2, ®ION2_HYPERSLAB_BLOB[..]),
1039 (ReferenceKind::DatasetRegion2, ®ION2_POINT_BLOB[..]),
1040 ] {
1041 let DecodedReference {
1042 address, target, ..
1043 } = decode_revised_body(kind, false, golden, &ctx())
1044 .unwrap()
1045 .unwrap();
1046 let blob = encode_revised_blob(address, &target, 2, &ctx()).unwrap();
1047 assert_eq!(blob[..9], golden[..9]);
1049 assert_eq!(blob[13..17], golden[13..17], "the extent rank");
1050 let selection_len = u32::from_le_bytes(blob[9..13].try_into().unwrap()) as usize;
1051 assert_eq!(blob.len(), 17 + selection_len);
1052 let back = decode_revised_body(kind, false, &blob, &ctx())
1053 .unwrap()
1054 .unwrap();
1055 assert_eq!(back.address, address);
1056 let (ReferenceTarget::Region(was), ReferenceTarget::Region(now)) =
1057 (&target, &back.target)
1058 else {
1059 panic!("a region reference names a selection");
1060 };
1061 assert_eq!(
1064 was.to_boxes(&[4, 6]).unwrap(),
1065 now.to_boxes(&[4, 6]).unwrap()
1066 );
1067 assert_eq!(was.bounds(), now.bounds());
1068 }
1069
1070 let all =
1073 encode_revised_blob(0xC3, &ReferenceTarget::Region(Selection::All), 3, &ctx()).unwrap();
1074 assert_eq!(u32::from_le_bytes(all[13..17].try_into().unwrap()), 3);
1075 assert_eq!(
1076 decode_revised_body(ReferenceKind::DatasetRegion2, false, &all, &ctx()).unwrap(),
1077 Some(DecodedReference {
1078 address: 0xC3,
1079 file: None,
1080 target: ReferenceTarget::Region(Selection::All),
1081 })
1082 );
1083
1084 assert_eq!(
1087 encode_revised_blob(0xC3, &ReferenceTarget::Object, 0, &ctx()).unwrap(),
1088 OBJ2_ELEMENT[2..11]
1089 );
1090 }
1091
1092 #[test]
1094 fn a_truncated_selection_is_refused() {
1095 let err = Selection::decode(®ION_HEAP_OBJECT[8..20]).unwrap_err();
1096 assert!(
1097 matches!(err, FormatError::BufferTooShort { .. }),
1098 "unexpected error: {err:?}"
1099 );
1100 }
1101}