use crate::format::bytes::{read_le_addr, read_le_uint};
use crate::format::messages::datatype::ReferenceKind;
use crate::format::selection::Selection;
use crate::format::{FormatContext, FormatError, FormatResult, UNDEF_ADDR};
fn target_address(elem: &[u8], sizeof_addr: usize) -> Option<u64> {
match read_le_addr(elem, sizeof_addr) {
0 | UNDEF_ADDR => None,
addr => Some(addr),
}
}
pub fn decode_object_element(elem: &[u8], ctx: &FormatContext) -> FormatResult<Option<u64>> {
let sa = ctx.sizeof_addr as usize;
if elem.len() < sa {
return Err(FormatError::BufferTooShort {
needed: sa,
available: elem.len(),
});
}
Ok(target_address(elem, sa))
}
pub fn decode_region_element(elem: &[u8], ctx: &FormatContext) -> FormatResult<Option<(u64, u32)>> {
let sa = ctx.sizeof_addr as usize;
if elem.len() < sa + 4 {
return Err(FormatError::BufferTooShort {
needed: sa + 4,
available: elem.len(),
});
}
let Some(addr) = target_address(elem, sa) else {
return Ok(None);
};
let idx = u32::from_le_bytes([elem[sa], elem[sa + 1], elem[sa + 2], elem[sa + 3]]);
Ok(Some((addr, idx)))
}
pub fn decode_region_heap_object(
data: &[u8],
ctx: &FormatContext,
) -> FormatResult<(u64, Selection)> {
let sa = ctx.sizeof_addr as usize;
if data.len() < sa {
return Err(FormatError::BufferTooShort {
needed: sa,
available: data.len(),
});
}
let addr = read_le_addr(data, sa);
let (selection, _) = Selection::decode(&data[sa..])?;
Ok((addr, selection))
}
const REVISED_FLAG_EXTERNAL: u8 = 0x01;
const REVISED_HEADER: usize = 2;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RevisedElement<'a> {
Null,
Inline {
kind: ReferenceKind,
body: &'a [u8],
},
Heap {
kind: ReferenceKind,
external: bool,
collection: u64,
index: u32,
},
}
pub fn decode_revised_element<'a>(
elem: &'a [u8],
ctx: &FormatContext,
) -> FormatResult<RevisedElement<'a>> {
let sa = ctx.sizeof_addr as usize;
let heap_id = REVISED_HEADER + 4;
if elem.len() < REVISED_HEADER {
return Err(FormatError::BufferTooShort {
needed: REVISED_HEADER,
available: elem.len(),
});
}
let (code, flags) = (elem[0], elem[1]);
if code == 0 {
if elem.len() < heap_id + sa {
return Err(FormatError::BufferTooShort {
needed: heap_id + sa,
available: elem.len(),
});
}
return match read_le_addr(&elem[heap_id..], sa) {
0 => Ok(RevisedElement::Null),
addr => Err(FormatError::InvalidData(format!(
"reference type 0 over a blob at {addr:#x}, which is not the nil id a null \
reference carries"
))),
};
}
let kind = ReferenceKind::from_code(code)
.filter(|k| k.is_revised())
.ok_or_else(|| {
FormatError::InvalidData(format!("reference type {code} in a 1.12 element"))
})?;
let external = flags & REVISED_FLAG_EXTERNAL != 0;
if !external && kind == ReferenceKind::Object2 {
return Ok(RevisedElement::Inline {
kind,
body: &elem[REVISED_HEADER..],
});
}
if elem.len() < heap_id + sa + 4 {
return Err(FormatError::BufferTooShort {
needed: heap_id + sa + 4,
available: elem.len(),
});
}
let collection = read_le_addr(&elem[heap_id..], sa);
let index = read_le_uint(&elem[heap_id + sa..heap_id + sa + 4], 4) as u32;
Ok(RevisedElement::Heap {
kind,
external,
collection,
index,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReferenceElementImage {
Legacy(u64),
Inline(u64),
Blob {
kind: ReferenceKind,
size: u32,
collection: u64,
index: u32,
},
}
pub fn encode_reference_element(
image: &ReferenceElementImage,
size: usize,
ctx: &FormatContext,
) -> FormatResult<Vec<u8>> {
let sa = ctx.sizeof_addr as usize;
let mut elem = vec![0u8; size];
let (what, needed) = match image {
ReferenceElementImage::Legacy(_) => ("an H5R_OBJECT1", sa),
ReferenceElementImage::Inline(_) => ("an H5R_OBJECT2", REVISED_HEADER + 1 + sa),
ReferenceElementImage::Blob { kind, .. } => (
match kind {
ReferenceKind::DatasetRegion2 => "an H5R_DATASET_REGION2",
ReferenceKind::Attr => "an H5R_ATTR",
_ => "a blob-backed",
},
REVISED_HEADER + 4 + sa + 4,
),
};
if needed > size {
return Err(FormatError::InvalidData(format!(
"{what} element needs {needed} bytes but its datatype declares {size}"
)));
}
match *image {
ReferenceElementImage::Legacy(address) => {
elem[..sa].copy_from_slice(&address.to_le_bytes()[..sa]);
}
ReferenceElementImage::Inline(address) => {
elem[0] = ReferenceKind::Object2.code();
elem[1] = 0;
elem[2] = sa as u8;
let at = REVISED_HEADER + 1;
elem[at..at + sa].copy_from_slice(&address.to_le_bytes()[..sa]);
}
ReferenceElementImage::Blob {
kind,
size: blob_size,
collection,
index,
} => {
elem[0] = kind.code();
elem[1] = 0;
elem[REVISED_HEADER..REVISED_HEADER + 4].copy_from_slice(&blob_size.to_le_bytes());
let at = REVISED_HEADER + 4;
elem[at..at + sa].copy_from_slice(&collection.to_le_bytes()[..sa]);
elem[at + sa..at + sa + 4].copy_from_slice(&index.to_le_bytes());
}
}
Ok(elem)
}
pub fn encode_revised_blob(
address: u64,
target: &ReferenceTarget,
extent_rank: usize,
ctx: &FormatContext,
) -> FormatResult<Vec<u8>> {
let sa = ctx.sizeof_addr as usize;
let mut blob = Vec::with_capacity(1 + sa + 16);
blob.push(sa as u8);
blob.extend_from_slice(&address.to_le_bytes()[..sa]);
match target {
ReferenceTarget::Object => {}
ReferenceTarget::Region(selection) => {
let bytes = selection.encode()?;
blob.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
blob.extend_from_slice(&(extent_rank as u32).to_le_bytes());
blob.extend_from_slice(&bytes);
}
ReferenceTarget::Attribute(name) => {
let len = u16::try_from(name.len()).map_err(|_| {
FormatError::InvalidData(format!(
"attribute name of {} bytes does not fit a reference's 16-bit length",
name.len()
))
})?;
blob.extend_from_slice(&len.to_le_bytes());
blob.extend_from_slice(name.as_bytes());
}
}
Ok(blob)
}
pub const REVISED_BLOB_TOKEN_OFFSET: usize = 1;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReferenceTarget {
Object,
Region(Selection),
Attribute(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodedReference {
pub address: u64,
pub file: Option<String>,
pub target: ReferenceTarget,
}
pub fn decode_revised_body(
kind: ReferenceKind,
external: bool,
body: &[u8],
ctx: &FormatContext,
) -> FormatResult<Option<DecodedReference>> {
let sa = ctx.sizeof_addr as usize;
let mut r = Cursor::new(body);
let token_size = r.u8()? as usize;
if token_size != sa {
return Err(FormatError::UnsupportedFeature(format!(
"object tokens {token_size} bytes wide, not the {sa}-byte file addresses the \
native format uses"
)));
}
let token = r.take(token_size)?;
let Some(address) = target_address(token, sa) else {
return Ok(None);
};
let file = if external {
Some(decode_string(&mut r)?)
} else {
None
};
let target = match kind {
ReferenceKind::Object2 => ReferenceTarget::Object,
ReferenceKind::DatasetRegion2 => {
let len = r.u32()? as usize;
let _rank = r.u32()?;
ReferenceTarget::Region(Selection::decode(r.take(len)?)?.0)
}
ReferenceKind::Attr => ReferenceTarget::Attribute(decode_string(&mut r)?),
ReferenceKind::Object1 | ReferenceKind::DatasetRegion1 => {
return Err(FormatError::InvalidData(format!(
"{kind:?} is not a 1.12 encoded reference"
)))
}
};
Ok(Some(DecodedReference {
address,
file,
target,
}))
}
fn decode_string(r: &mut Cursor<'_>) -> FormatResult<String> {
let len = r.u16()? as usize;
let bytes = r.take(len)?;
String::from_utf8(bytes.to_vec())
.map_err(|_| FormatError::InvalidData("a string in a reference is not UTF-8".into()))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Reference {
Null,
Object {
address: u64,
file: Option<String>,
path: Option<String>,
},
Region {
address: u64,
file: Option<String>,
path: Option<String>,
selection: Selection,
},
Attr {
address: u64,
file: Option<String>,
path: Option<String>,
name: String,
},
}
impl Reference {
pub fn file(&self) -> Option<&str> {
match self {
Self::Null => None,
Self::Object { file, .. } | Self::Region { file, .. } | Self::Attr { file, .. } => {
file.as_deref()
}
}
}
pub fn path(&self) -> Option<&str> {
match self {
Self::Null => None,
Self::Object { path, .. } | Self::Region { path, .. } | Self::Attr { path, .. } => {
path.as_deref()
}
}
}
pub fn address(&self) -> Option<u64> {
match self {
Self::Null => None,
Self::Object { address, .. }
| Self::Region { address, .. }
| Self::Attr { address, .. } => Some(*address),
}
}
pub fn attribute_name(&self) -> Option<&str> {
match self {
Self::Attr { name, .. } => Some(name),
_ => None,
}
}
pub fn selection(&self) -> Option<&Selection> {
match self {
Self::Region { selection, .. } => Some(selection),
_ => None,
}
}
pub fn bounds(&self) -> Option<(Vec<u64>, Vec<u64>)> {
self.selection()?.bounds()
}
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
}
struct Cursor<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> Cursor<'a> {
fn new(buf: &'a [u8]) -> Self {
Self { buf, pos: 0 }
}
fn take(&mut self, n: usize) -> FormatResult<&'a [u8]> {
let end = self.pos.checked_add(n).ok_or(FormatError::BufferTooShort {
needed: usize::MAX,
available: self.buf.len(),
})?;
if end > self.buf.len() {
return Err(FormatError::BufferTooShort {
needed: end,
available: self.buf.len(),
});
}
let out = &self.buf[self.pos..end];
self.pos = end;
Ok(out)
}
fn u8(&mut self) -> FormatResult<u8> {
Ok(self.take(1)?[0])
}
fn u16(&mut self) -> FormatResult<u16> {
let b = self.take(2)?;
Ok(u16::from_le_bytes([b[0], b[1]]))
}
fn u32(&mut self) -> FormatResult<u32> {
let b = self.take(4)?;
Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::format::selection::{Hyperslab, HyperslabBlock, PointSelection};
fn ctx() -> FormatContext {
FormatContext::default_v3()
}
const REGION_HEAP_OBJECT: [u8; 40] = [
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, ];
#[test]
fn region_heap_object_from_libhdf5_decodes() {
let (addr, selection) = decode_region_heap_object(®ION_HEAP_OBJECT, &ctx()).unwrap();
assert_eq!(addr, 0x320);
assert_eq!(
selection,
Selection::Hyperslab {
rank: 1,
form: Hyperslab::Blocks(vec![HyperslabBlock {
start: vec![0],
end: vec![2],
}]),
}
);
assert_eq!(selection.bounds(), Some((vec![0], vec![2])));
}
#[test]
fn object_and_region_elements_report_null() {
assert_eq!(
decode_object_element(&0x320u64.to_le_bytes(), &ctx()).unwrap(),
Some(0x320)
);
assert_eq!(
decode_object_element(&[0xFF; 8], &ctx()).unwrap(),
None,
"an undefined address is a null reference"
);
assert_eq!(
decode_object_element(&[0; 8], &ctx()).unwrap(),
None,
"so is address 0, which h5py writes for an unset element"
);
let mut elem = [0u8; 12];
elem[..8].copy_from_slice(&0x820u64.to_le_bytes());
elem[8..].copy_from_slice(&2u32.to_le_bytes());
assert_eq!(
decode_region_element(&elem, &ctx()).unwrap(),
Some((0x820, 2))
);
assert_eq!(
decode_region_element(&[0u8; 12], &ctx()).unwrap(),
None,
"a zeroed element carries no heap id"
);
}
const OBJ2_ELEMENT: [u8; 18] = [
0x02, 0x00, 0x08, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
];
const REGION2_ELEMENT: [u8; 18] = [
0x03, 0x00, 0x39, 0x00, 0x00, 0x00, 0xA8, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00,
];
const ATTR_ELEMENT: [u8; 18] = [
0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xA8, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00,
];
const REGION2_HYPERSLAB_BLOB: [u8; 57] = [
0x08, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00,
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
const REGION2_POINT_BLOB: [u8; 57] = [
0x08, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
const ATTR_BLOB: [u8; 15] = [
0x08, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x6E, 0x6F, 0x74, 0x65,
];
const EXT_FILE: &str = "tests/fixtures/ext_ref_target.h5";
const EXT_OBJ2_ELEMENT: [u8; 18] = [
0x02, 0x01, 0x2B, 0x00, 0x00, 0x00, 0x24, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00,
];
const EXT_OBJ2_BLOB: [u8; 43] = [
0x08, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x74, 0x65, 0x73, 0x74,
0x73, 0x2F, 0x66, 0x69, 0x78, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2F, 0x65, 0x78, 0x74, 0x5F,
0x72, 0x65, 0x66, 0x5F, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2E, 0x68, 0x35,
];
const EXT_ATTR_BLOB: [u8; 49] = [
0x08, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x74, 0x65, 0x73, 0x74,
0x73, 0x2F, 0x66, 0x69, 0x78, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2F, 0x65, 0x78, 0x74, 0x5F,
0x72, 0x65, 0x66, 0x5F, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2E, 0x68, 0x35, 0x04, 0x00,
0x6E, 0x6F, 0x74, 0x65,
];
#[test]
fn revised_elements_from_libhdf5_split_into_kind_and_body() {
let RevisedElement::Inline { kind, body } =
decode_revised_element(&OBJ2_ELEMENT, &ctx()).unwrap()
else {
panic!("an object reference is stored in the element");
};
assert_eq!(kind, ReferenceKind::Object2);
assert_eq!(
decode_revised_body(kind, false, body, &ctx()).unwrap(),
Some(DecodedReference {
address: 0xC3,
file: None,
target: ReferenceTarget::Object,
})
);
assert_eq!(
decode_revised_element(®ION2_ELEMENT, &ctx()).unwrap(),
RevisedElement::Heap {
kind: ReferenceKind::DatasetRegion2,
external: false,
collection: 0x8A8,
index: 1,
}
);
assert_eq!(
decode_revised_element(&ATTR_ELEMENT, &ctx()).unwrap(),
RevisedElement::Heap {
kind: ReferenceKind::Attr,
external: false,
collection: 0x8A8,
index: 3,
}
);
}
#[test]
fn revised_region_blobs_decode_to_their_selections() {
let hyper = decode_revised_body(
ReferenceKind::DatasetRegion2,
false,
®ION2_HYPERSLAB_BLOB,
&ctx(),
)
.unwrap()
.unwrap();
assert_eq!(hyper.address, 0xC3);
let ReferenceTarget::Region(selection) = &hyper.target else {
panic!("a region reference names a selection");
};
assert_eq!(
selection.bounds(),
Some((vec![1, 2], vec![2, 4])),
"{selection:?}"
);
let points = decode_revised_body(
ReferenceKind::DatasetRegion2,
false,
®ION2_POINT_BLOB,
&ctx(),
)
.unwrap()
.unwrap();
let ReferenceTarget::Region(selection) = &points.target else {
panic!("a region reference names a selection");
};
assert_eq!(
selection,
&Selection::Points(PointSelection {
rank: 2,
points: vec![vec![0, 1], vec![3, 5]],
})
);
assert_eq!(selection.bounds(), Some((vec![0, 1], vec![3, 5])));
}
#[test]
fn an_attribute_reference_carries_its_name() {
assert_eq!(
decode_revised_body(ReferenceKind::Attr, false, &ATTR_BLOB, &ctx()).unwrap(),
Some(DecodedReference {
address: 0xC3,
file: None,
target: ReferenceTarget::Attribute("note".into()),
})
);
}
#[test]
fn revised_elements_that_name_nothing_or_cannot_be_followed() {
assert_eq!(
decode_revised_element(&[0u8; 18], &ctx()).unwrap(),
RevisedElement::Null
);
let mut stale = [0u8; 18];
stale[6] = 0xA8;
stale[7] = 0x08;
assert!(
matches!(
decode_revised_element(&stale, &ctx()).unwrap_err(),
FormatError::InvalidData(_)
),
"reference type 0 over a live blob is not a null reference"
);
let mut old_code = OBJ2_ELEMENT;
old_code[0] = ReferenceKind::DatasetRegion1.code();
assert!(matches!(
decode_revised_element(&old_code, &ctx()).unwrap_err(),
FormatError::InvalidData(_)
));
let mut foreign = ATTR_BLOB;
foreign[0] = 16;
assert!(
matches!(
decode_revised_body(ReferenceKind::Attr, false, &foreign, &ctx()).unwrap_err(),
FormatError::UnsupportedFeature(_)
),
"a token that is not a file address belongs to another VOL connector"
);
let mut unset = OBJ2_ELEMENT;
unset[3] = 0;
let RevisedElement::Inline { kind, body } = decode_revised_element(&unset, &ctx()).unwrap()
else {
panic!("an object reference is stored in the element");
};
assert_eq!(
decode_revised_body(kind, false, body, &ctx()).unwrap(),
None,
"a zero token names no object"
);
}
#[test]
fn an_external_reference_names_the_file_its_target_is_in() {
assert_eq!(
decode_revised_element(&EXT_OBJ2_ELEMENT, &ctx()).unwrap(),
RevisedElement::Heap {
kind: ReferenceKind::Object2,
external: true,
collection: 0x824,
index: 1,
}
);
assert_eq!(
decode_revised_body(ReferenceKind::Object2, true, &EXT_OBJ2_BLOB, &ctx()).unwrap(),
Some(DecodedReference {
address: 0x320,
file: Some(EXT_FILE.into()),
target: ReferenceTarget::Object,
})
);
assert_eq!(
decode_revised_body(ReferenceKind::Attr, true, &EXT_ATTR_BLOB, &ctx()).unwrap(),
Some(DecodedReference {
address: 0x320,
file: Some(EXT_FILE.into()),
target: ReferenceTarget::Attribute("note".into()),
})
);
let internal = decode_revised_body(ReferenceKind::Attr, false, &EXT_ATTR_BLOB, &ctx())
.unwrap()
.unwrap();
assert_eq!(
internal.target,
ReferenceTarget::Attribute(EXT_FILE.into()),
"the flag is what tells the file name from the attribute name"
);
}
#[test]
fn revised_elements_encode_to_the_libhdf5_images() {
assert_eq!(
encode_reference_element(&ReferenceElementImage::Inline(0xC3), 18, &ctx()).unwrap(),
OBJ2_ELEMENT
);
assert_eq!(
encode_reference_element(
&ReferenceElementImage::Blob {
kind: ReferenceKind::DatasetRegion2,
size: REGION2_HYPERSLAB_BLOB.len() as u32,
collection: 0x8A8,
index: 1,
},
18,
&ctx()
)
.unwrap(),
REGION2_ELEMENT
);
assert_eq!(
encode_reference_element(
&ReferenceElementImage::Blob {
kind: ReferenceKind::Attr,
size: ATTR_BLOB.len() as u32,
collection: 0x8A8,
index: 3,
},
18,
&ctx()
)
.unwrap(),
ATTR_ELEMENT
);
assert_eq!(
encode_reference_element(&ReferenceElementImage::Legacy(0xC3), 8, &ctx()).unwrap(),
[0xC3, 0, 0, 0, 0, 0, 0, 0]
);
}
#[test]
fn an_element_narrower_than_its_layout_is_refused() {
let err = encode_reference_element(
&ReferenceElementImage::Blob {
kind: ReferenceKind::Attr,
size: 15,
collection: 0x8A8,
index: 3,
},
11,
&ctx(),
)
.unwrap_err();
assert!(matches!(err, FormatError::InvalidData(_)), "{err:?}");
assert!(encode_reference_element(&ReferenceElementImage::Inline(0xC3), 11, &ctx()).is_ok());
}
#[test]
fn revised_blobs_encode_to_what_libhdf5_reads_back() {
let attr = decode_revised_body(ReferenceKind::Attr, false, &ATTR_BLOB, &ctx())
.unwrap()
.unwrap();
assert_eq!(
encode_revised_blob(attr.address, &attr.target, 0, &ctx()).unwrap(),
ATTR_BLOB
);
for (kind, golden) in [
(ReferenceKind::DatasetRegion2, ®ION2_HYPERSLAB_BLOB[..]),
(ReferenceKind::DatasetRegion2, ®ION2_POINT_BLOB[..]),
] {
let DecodedReference {
address, target, ..
} = decode_revised_body(kind, false, golden, &ctx())
.unwrap()
.unwrap();
let blob = encode_revised_blob(address, &target, 2, &ctx()).unwrap();
assert_eq!(blob[..9], golden[..9]);
assert_eq!(blob[13..17], golden[13..17], "the extent rank");
let selection_len = u32::from_le_bytes(blob[9..13].try_into().unwrap()) as usize;
assert_eq!(blob.len(), 17 + selection_len);
let back = decode_revised_body(kind, false, &blob, &ctx())
.unwrap()
.unwrap();
assert_eq!(back.address, address);
let (ReferenceTarget::Region(was), ReferenceTarget::Region(now)) =
(&target, &back.target)
else {
panic!("a region reference names a selection");
};
assert_eq!(
was.to_boxes(&[4, 6]).unwrap(),
now.to_boxes(&[4, 6]).unwrap()
);
assert_eq!(was.bounds(), now.bounds());
}
let all =
encode_revised_blob(0xC3, &ReferenceTarget::Region(Selection::All), 3, &ctx()).unwrap();
assert_eq!(u32::from_le_bytes(all[13..17].try_into().unwrap()), 3);
assert_eq!(
decode_revised_body(ReferenceKind::DatasetRegion2, false, &all, &ctx()).unwrap(),
Some(DecodedReference {
address: 0xC3,
file: None,
target: ReferenceTarget::Region(Selection::All),
})
);
assert_eq!(
encode_revised_blob(0xC3, &ReferenceTarget::Object, 0, &ctx()).unwrap(),
OBJ2_ELEMENT[2..11]
);
}
#[test]
fn a_truncated_selection_is_refused() {
let err = Selection::decode(®ION_HEAP_OBJECT[8..20]).unwrap_err();
assert!(
matches!(err, FormatError::BufferTooShort { .. }),
"unexpected error: {err:?}"
);
}
}