use crate::format::bytes::read_le_uint as read_uint;
use crate::format::checksum::{checksum_metadata, jenkins_lookup3};
use crate::format::messages::{
MSG_ATTRIBUTE, MSG_DATASPACE, MSG_DATATYPE, MSG_FILL_VALUE, MSG_FILL_VALUE_OLD,
MSG_FILTER_PIPELINE,
};
use crate::format::{FormatContext, FormatError, FormatResult};
pub const SMTB_SIGNATURE: [u8; 4] = *b"SMTB";
pub const SMLI_SIGNATURE: [u8; 4] = *b"SMLI";
pub const SOHM_HEAP_ID_LEN: usize = 8;
pub const SOHM_POINTER_HEAP_ID_AT: usize = 2;
pub const SOHM_IN_HEAP: u8 = 0;
pub const SOHM_IN_OH: u8 = 1;
pub const SOHM_INDEX_LIST: u8 = 0;
pub const SOHM_INDEX_BTREE: u8 = 1;
pub const BT2_TYPE_SOHM_INDEX: u8 = 7;
pub const SOHM_B2_NODE_SIZE: u32 = 512;
pub const MAX_SOHM_INDEXES: usize = 8;
pub fn message_hash(body: &[u8], msg_type: u8) -> u32 {
jenkins_lookup3(body, u32::from(msg_type))
}
pub fn record_size(ctx: &FormatContext) -> usize {
let sa = ctx.sizeof_addr as usize;
1 + 4 + (4 + SOHM_HEAP_ID_LEN).max(1 + 1 + 2 + sa)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SohmRecord {
pub hash: u32,
pub location: SohmRecordLocation,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SohmRecordLocation {
InHeap {
ref_count: u32,
heap_id: [u8; SOHM_HEAP_ID_LEN],
},
InObjectHeader {
msg_type: u8,
index: u16,
oh_addr: u64,
},
}
impl SohmRecord {
pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
let size = record_size(ctx);
let mut buf = Vec::with_capacity(size);
match self.location {
SohmRecordLocation::InHeap { ref_count, heap_id } => {
buf.push(SOHM_IN_HEAP);
buf.extend_from_slice(&self.hash.to_le_bytes());
buf.extend_from_slice(&ref_count.to_le_bytes());
buf.extend_from_slice(&heap_id);
}
SohmRecordLocation::InObjectHeader {
msg_type,
index,
oh_addr,
} => {
buf.push(SOHM_IN_OH);
buf.extend_from_slice(&self.hash.to_le_bytes());
buf.push(0);
buf.push(msg_type);
buf.extend_from_slice(&index.to_le_bytes());
buf.extend_from_slice(&oh_addr.to_le_bytes()[..ctx.sizeof_addr as usize]);
}
}
buf.resize(size, 0);
buf
}
}
pub fn list_size(ctx: &FormatContext, list_max: u16) -> usize {
4 + record_size(ctx) * list_max as usize + 4
}
pub fn encode_list(records: &[SohmRecord], ctx: &FormatContext) -> Vec<u8> {
let mut buf = SMLI_SIGNATURE.to_vec();
for record in records {
buf.extend_from_slice(&record.encode(ctx));
}
let sum = checksum_metadata(&buf);
buf.extend_from_slice(&sum.to_le_bytes());
buf
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SharedLocation {
Unshared,
Sohm,
Committed,
Here,
}
impl SharedLocation {
fn from_byte(b: u8) -> Self {
match b {
1 => Self::Sohm,
2 => Self::Committed,
3 => Self::Here,
_ => Self::Unshared,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SharedMessagePointer {
pub version: u8,
pub location: SharedLocation,
pub heap_id: [u8; SOHM_HEAP_ID_LEN],
pub oh_addr: u64,
}
impl SharedMessagePointer {
pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<Self> {
let sa = ctx.sizeof_addr as usize;
let ss = ctx.sizeof_size as usize;
need(buf, 2)?;
let version = buf[0];
if version == 0 || version > 3 {
return Err(FormatError::InvalidVersion(version));
}
let mut location = if version >= 2 {
SharedLocation::from_byte(buf[1])
} else {
SharedLocation::Committed
};
let mut pos = 2;
let mut heap_id = [0u8; SOHM_HEAP_ID_LEN];
let mut oh_addr = 0u64;
if version == 1 {
pos += 6;
need(buf, pos + ss + sa)?;
pos += ss;
oh_addr = read_uint(&buf[pos..], sa);
} else if location == SharedLocation::Sohm {
if version < 3 {
return Err(FormatError::InvalidData(
"heap-shared message pointer requires version 3".into(),
));
}
need(buf, pos + SOHM_HEAP_ID_LEN)?;
heap_id.copy_from_slice(&buf[pos..pos + SOHM_HEAP_ID_LEN]);
} else {
if version < 3 {
location = SharedLocation::Committed;
}
need(buf, pos + sa)?;
oh_addr = read_uint(&buf[pos..], sa);
}
Ok(Self {
version,
location,
heap_id,
oh_addr,
})
}
pub fn committed(oh_addr: u64) -> Self {
Self {
version: 2,
location: SharedLocation::Committed,
heap_id: [0u8; SOHM_HEAP_ID_LEN],
oh_addr,
}
}
pub fn encode_committed(oh_addr: u64, ctx: &FormatContext) -> Vec<u8> {
let sa = ctx.sizeof_addr as usize;
let mut buf = Vec::with_capacity(2 + sa);
buf.push(2); buf.push(2); buf.extend_from_slice(&oh_addr.to_le_bytes()[..sa]);
buf
}
pub fn encode_sohm(heap_id: [u8; SOHM_HEAP_ID_LEN]) -> Vec<u8> {
let mut buf = Vec::with_capacity(2 + SOHM_HEAP_ID_LEN);
buf.push(3); buf.push(1); buf.extend_from_slice(&heap_id);
buf
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SohmIndexHeader {
pub index_type: u8,
pub mesg_types: u16,
pub min_mesg_size: u32,
pub list_max: u16,
pub btree_min: u16,
pub num_messages: u16,
pub index_addr: u64,
pub heap_addr: u64,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SohmMasterTable {
pub indexes: Vec<SohmIndexHeader>,
}
const SM_LIST_VERSION: u8 = 0;
impl SohmMasterTable {
pub fn encoded_size(ctx: &FormatContext, nindexes: u8) -> usize {
let sa = ctx.sizeof_addr as usize;
let per_index = 1 + 1 + 2 + 4 + 2 + 2 + 2 + sa + sa;
4 + nindexes as usize * per_index + 4
}
pub fn decode(buf: &[u8], ctx: &FormatContext, nindexes: u8) -> FormatResult<Self> {
let sa = ctx.sizeof_addr as usize;
let size = Self::encoded_size(ctx, nindexes);
need(buf, size)?;
if buf[0..4] != SMTB_SIGNATURE {
return Err(FormatError::InvalidSignature);
}
let stored =
u32::from_le_bytes([buf[size - 4], buf[size - 3], buf[size - 2], buf[size - 1]]);
let computed = checksum_metadata(&buf[..size - 4]);
if stored != computed {
return Err(FormatError::ChecksumMismatch {
expected: stored,
computed,
});
}
let mut pos = 4;
let mut indexes = Vec::with_capacity(nindexes as usize);
for _ in 0..nindexes {
let version = buf[pos];
if version != SM_LIST_VERSION {
return Err(FormatError::InvalidVersion(version));
}
pos += 1;
let index_type = buf[pos];
pos += 1;
let mesg_types = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
pos += 2;
let min_mesg_size =
u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]);
pos += 4;
let list_max = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
pos += 2;
let btree_min = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
pos += 2;
let num_messages = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
pos += 2;
let index_addr = read_uint(&buf[pos..], sa);
pos += sa;
let heap_addr = read_uint(&buf[pos..], sa);
pos += sa;
indexes.push(SohmIndexHeader {
index_type,
mesg_types,
min_mesg_size,
list_max,
btree_min,
num_messages,
index_addr,
heap_addr,
});
}
Ok(Self { indexes })
}
pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
let sa = ctx.sizeof_addr as usize;
let mut buf = SMTB_SIGNATURE.to_vec();
for index in &self.indexes {
buf.push(SM_LIST_VERSION);
buf.push(index.index_type);
buf.extend_from_slice(&index.mesg_types.to_le_bytes());
buf.extend_from_slice(&index.min_mesg_size.to_le_bytes());
buf.extend_from_slice(&index.list_max.to_le_bytes());
buf.extend_from_slice(&index.btree_min.to_le_bytes());
buf.extend_from_slice(&index.num_messages.to_le_bytes());
buf.extend_from_slice(&index.index_addr.to_le_bytes()[..sa]);
buf.extend_from_slice(&index.heap_addr.to_le_bytes()[..sa]);
}
let sum = checksum_metadata(&buf);
buf.extend_from_slice(&sum.to_le_bytes());
buf
}
pub fn heap_addr(&self, msg_type: u8) -> Option<u64> {
let flag = type_flag(msg_type)?;
self.indexes
.iter()
.find(|i| i.mesg_types & flag != 0)
.map(|i| i.heap_addr)
}
}
pub fn type_flag(msg_type: u8) -> Option<u16> {
let id = match msg_type {
MSG_DATASPACE | MSG_DATATYPE | MSG_FILL_VALUE | MSG_FILTER_PIPELINE | MSG_ATTRIBUTE => {
msg_type
}
MSG_FILL_VALUE_OLD => MSG_FILL_VALUE,
_ => return None,
};
Some(1u16 << id)
}
fn need(buf: &[u8], n: usize) -> FormatResult<()> {
if buf.len() < n {
Err(FormatError::BufferTooShort {
needed: n,
available: buf.len(),
})
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ctx() -> FormatContext {
FormatContext::default_v3()
}
fn index_header(mesg_types: u16, heap_addr: u64) -> Vec<u8> {
let mut b = vec![SM_LIST_VERSION, 0];
b.extend_from_slice(&mesg_types.to_le_bytes());
b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&50u16.to_le_bytes()); b.extend_from_slice(&40u16.to_le_bytes()); b.extend_from_slice(&7u16.to_le_bytes()); b.extend_from_slice(&0x1234u64.to_le_bytes()); b.extend_from_slice(&heap_addr.to_le_bytes());
b
}
fn master_table(headers: &[Vec<u8>]) -> Vec<u8> {
let mut b = SMTB_SIGNATURE.to_vec();
for h in headers {
b.extend_from_slice(h);
}
let sum = checksum_metadata(&b);
b.extend_from_slice(&sum.to_le_bytes());
b
}
#[test]
fn master_table_roundtrip() {
let buf = master_table(&[index_header(1 << MSG_DATATYPE, 0x8000)]);
assert_eq!(buf.len(), SohmMasterTable::encoded_size(&ctx(), 1));
let t = SohmMasterTable::decode(&buf, &ctx(), 1).unwrap();
assert_eq!(t.indexes.len(), 1);
assert_eq!(t.indexes[0].num_messages, 7);
assert_eq!(t.indexes[0].heap_addr, 0x8000);
}
#[test]
fn master_table_rejects_a_corrupt_checksum() {
let mut buf = master_table(&[index_header(1 << MSG_DATATYPE, 0x8000)]);
let n = buf.len();
buf[n - 1] ^= 0xff;
assert!(matches!(
SohmMasterTable::decode(&buf, &ctx(), 1).unwrap_err(),
FormatError::ChecksumMismatch { .. }
));
}
#[test]
fn master_table_rejects_a_bad_index_version() {
let mut hdr = index_header(1 << MSG_DATATYPE, 0x8000);
hdr[0] = 1;
let buf = master_table(&[hdr]);
assert!(matches!(
SohmMasterTable::decode(&buf, &ctx(), 1).unwrap_err(),
FormatError::InvalidVersion(1)
));
}
#[test]
fn heap_address_is_selected_by_message_type() {
let buf = master_table(&[
index_header(1 << MSG_ATTRIBUTE, 0x1000),
index_header((1 << MSG_DATATYPE) | (1 << MSG_DATASPACE), 0x2000),
]);
let t = SohmMasterTable::decode(&buf, &ctx(), 2).unwrap();
assert_eq!(t.heap_addr(MSG_ATTRIBUTE), Some(0x1000));
assert_eq!(t.heap_addr(MSG_DATATYPE), Some(0x2000));
assert_eq!(t.heap_addr(MSG_DATASPACE), Some(0x2000));
assert_eq!(t.heap_addr(crate::format::messages::MSG_DATA_LAYOUT), None);
}
#[test]
fn old_fill_value_shares_the_new_fill_value_bit() {
assert_eq!(type_flag(MSG_FILL_VALUE_OLD), type_flag(MSG_FILL_VALUE));
assert_eq!(type_flag(MSG_FILL_VALUE), Some(1 << MSG_FILL_VALUE));
assert_eq!(type_flag(crate::format::messages::MSG_LINK), None);
}
#[test]
fn committed_pointer_encodes_the_bytes_h5o_shared_encode_writes() {
let buf = SharedMessagePointer::encode_committed(0x320, &ctx());
let mut want = vec![2u8, 2u8];
want.extend_from_slice(&0x320u64.to_le_bytes());
assert_eq!(buf, want);
assert_eq!(
SharedMessagePointer::decode(&buf, &ctx()).unwrap(),
SharedMessagePointer::committed(0x320)
);
}
#[test]
fn committed_pointer_follows_the_address_width() {
let ctx4 = FormatContext {
sizeof_addr: 4,
sizeof_size: 4,
};
let buf = SharedMessagePointer::encode_committed(0x1234, &ctx4);
assert_eq!(buf, vec![2u8, 2, 0x34, 0x12, 0, 0]);
assert_eq!(
SharedMessagePointer::decode(&buf, &ctx4).unwrap().oh_addr,
0x1234
);
}
#[test]
fn version_three_heap_pointer_carries_a_heap_id() {
let mut buf = vec![3u8, 1u8];
buf.extend_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
let p = SharedMessagePointer::decode(&buf, &ctx()).unwrap();
assert_eq!(p.location, SharedLocation::Sohm);
assert_eq!(p.heap_id, [1, 2, 3, 4, 5, 6, 7, 8]);
}
#[test]
fn version_three_committed_pointer_carries_an_address() {
let mut buf = vec![3u8, 2u8];
buf.extend_from_slice(&0x4321u64.to_le_bytes());
let p = SharedMessagePointer::decode(&buf, &ctx()).unwrap();
assert_eq!(p.location, SharedLocation::Committed);
assert_eq!(p.oh_addr, 0x4321);
}
#[test]
fn version_one_pointer_skips_the_symbol_table_entry_prefix() {
let mut buf = vec![1u8, 0u8];
buf.extend_from_slice(&[0u8; 6]);
buf.extend_from_slice(&0xdeadu64.to_le_bytes()); buf.extend_from_slice(&0x9999u64.to_le_bytes());
let p = SharedMessagePointer::decode(&buf, &ctx()).unwrap();
assert_eq!(p.location, SharedLocation::Committed);
assert_eq!(p.oh_addr, 0x9999);
}
#[test]
fn a_libhdf5_committed_datatype_reference_names_its_object_header() {
let buf = [0x02, 0x02, 0x20, 0x03, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let p = SharedMessagePointer::decode(&buf, &ctx()).unwrap();
assert_eq!(p.location, SharedLocation::Committed);
assert_eq!(p.oh_addr, 800);
}
#[test]
fn version_two_non_heap_pointer_is_committed() {
let mut buf = vec![2u8, 0u8];
buf.extend_from_slice(&0x77u64.to_le_bytes());
let p = SharedMessagePointer::decode(&buf, &ctx()).unwrap();
assert_eq!(p.location, SharedLocation::Committed);
assert_eq!(p.oh_addr, 0x77);
}
#[test]
fn heap_pointer_before_version_three_is_rejected() {
let mut buf = vec![2u8, 1u8];
buf.extend_from_slice(&[0u8; 8]);
assert!(matches!(
SharedMessagePointer::decode(&buf, &ctx()).unwrap_err(),
FormatError::InvalidData(_)
));
}
#[test]
fn pointer_rejects_unknown_versions() {
assert!(matches!(
SharedMessagePointer::decode(&[0u8, 1u8], &ctx()).unwrap_err(),
FormatError::InvalidVersion(0)
));
assert!(matches!(
SharedMessagePointer::decode(&[4u8, 1u8], &ctx()).unwrap_err(),
FormatError::InvalidVersion(4)
));
}
#[test]
fn message_hash_matches_the_fixture_records() {
let sdspace = [
1u8, 1, 1, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0,
];
assert_eq!(message_hash(&sdspace, MSG_DATASPACE), 701521455);
let dtype = [
0x11u8, 0x20, 0x3f, 0x00, 8, 0, 0, 0, 0, 0, 0x40, 0x00, 0x34, 0x0b, 0x00, 0x34, 0xff,
0x03, 0x00, 0x00,
];
assert_eq!(message_hash(&dtype, MSG_DATATYPE), 3573483313);
assert_ne!(
message_hash(&sdspace, MSG_DATASPACE),
message_hash(&sdspace, MSG_DATATYPE)
);
}
#[test]
fn record_is_seventeen_bytes_for_eight_byte_addresses() {
assert_eq!(record_size(&ctx()), 17);
assert_eq!(
record_size(&FormatContext {
sizeof_addr: 4,
sizeof_size: 4
}),
17
);
}
#[test]
fn list_index_encodes_the_fixture_image() {
let heaped = |hash, ref_count, heap_id| SohmRecord {
hash,
location: SohmRecordLocation::InHeap { ref_count, heap_id },
};
let records = [
heaped(701521455, 5, [0x00, 0x42, 0, 0, 0, 0, 0x18, 0x00]),
heaped(3573483313, 1, [0x00, 0x16, 0, 0, 0, 0, 0x14, 0x00]),
heaped(826238635, 1, [0x00, 0x2a, 0, 0, 0, 0, 0x18, 0x00]),
heaped(2575530442, 4, [0x00, 0x7a, 0, 0, 0, 0, 0x38, 0x00]),
];
let image = encode_list(&records, &ctx());
let want = concat!(
"534d4c49",
"002f5ed029050000000042000000001800",
"003107ffd4010000000016000000001400",
"00ab663f3101000000002a000000001800",
"00ca79839904000000007a000000003800",
"cbfec07c",
);
assert_eq!(hex(&image), want);
assert_eq!(list_size(&ctx(), 50), 858);
assert!(image.len() < list_size(&ctx(), 50));
}
#[test]
fn an_object_header_record_names_the_header_holding_the_body() {
let record = SohmRecord {
hash: 701521455,
location: SohmRecordLocation::InObjectHeader {
msg_type: MSG_DATASPACE,
index: 0,
oh_addr: 0x0349,
},
};
assert_eq!(
hex(&record.encode(&ctx())),
concat!(
"01", "2f5ed029", "00", "01", "0000", "4903000000000000", )
);
assert_eq!(record.encode(&ctx()).len(), record_size(&ctx()));
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
#[test]
fn master_table_encodes_what_decode_reads_back() {
let table = SohmMasterTable {
indexes: vec![SohmIndexHeader {
index_type: SOHM_INDEX_LIST,
mesg_types: (1 << MSG_DATASPACE) | (1 << MSG_DATATYPE) | (1 << MSG_ATTRIBUTE),
min_mesg_size: 0,
list_max: 50,
btree_min: 40,
num_messages: 4,
index_addr: 1125,
heap_addr: 1983,
}],
};
let image = table.encode(&ctx());
assert_eq!(image.len(), SohmMasterTable::encoded_size(&ctx(), 1));
assert_eq!(&image[..4], &SMTB_SIGNATURE);
assert_eq!(
u16::from_le_bytes([image[6], image[7]]),
0x100a,
"the type mask libhdf5 wrote for DTYPE|SDSPACE|ATTR"
);
assert_eq!(SohmMasterTable::decode(&image, &ctx(), 1).unwrap(), table);
}
#[test]
fn heap_pointer_is_a_version_three_message() {
let id = [0x00, 0x7a, 0, 0, 0, 0, 0x38, 0x00];
let buf = SharedMessagePointer::encode_sohm(id);
assert_eq!(hex(&buf), "0301007a000000003800");
let p = SharedMessagePointer::decode(&buf, &ctx()).unwrap();
assert_eq!(p.location, SharedLocation::Sohm);
assert_eq!(p.heap_id, id);
}
#[test]
fn pointer_rejects_a_truncated_body() {
assert!(matches!(
SharedMessagePointer::decode(&[3u8, 1u8, 0, 0], &ctx()).unwrap_err(),
FormatError::BufferTooShort { .. }
));
}
}