#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::btree_v2::{BTreeV2Header, collect_btree_v2_records_from_source};
use crate::bytes::{ensure_len, read_offset};
use crate::convert::{TryToUsize, is_undefined_addr};
use crate::error::FormatError;
use crate::fractal_heap::FractalHeapHeader;
use crate::message_type::MessageType;
use crate::shared_message::FHEAP_ID_LEN;
use crate::source::Source;
const TABLE_SIGNATURE: &[u8; 4] = b"SMTB";
const LIST_SIGNATURE: &[u8; 4] = b"SMLI";
const SOHM_VERSION: u8 = 0;
const BTREE_SOHM_INDEX_TYPE: u8 = 7;
const MAX_INDEXES: u8 = 8;
const TABLE_FIXED_LEN: usize = 4 + 4;
const INDEX_HEADER_FIXED_LEN: usize = 14;
const LIST_FIXED_LEN: usize = 4 + 4;
const RECORD_PREFIX_LEN: usize = 5;
const HEAP_LOCATION_LEN: usize = 4 + FHEAP_ID_LEN;
const LOCATION_HEAP: u8 = 0;
const LOCATION_OBJECT_HEADER: u8 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SharedMessageTableMessage {
pub table_address: u64,
pub index_count: u8,
}
impl SharedMessageTableMessage {
pub fn parse(data: &[u8], offset_size: u8) -> Result<Self, FormatError> {
ensure_len(data, 0, 1)?;
let version = data[0];
if version != SOHM_VERSION {
return Err(FormatError::InvalidSohmTableVersion(version));
}
let table_address = read_offset(data, 1, offset_size)?;
let pos = 1 + offset_size as usize;
ensure_len(data, pos, 1)?;
let index_count = data[pos];
if index_count == 0 || index_count > MAX_INDEXES {
return Err(FormatError::InvalidSohmIndexCount(index_count));
}
Ok(Self {
table_address,
index_count,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SohmIndexKind {
List,
BTree,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SohmIndexHeader {
pub message_type_flags: u16,
pub min_message_size: u32,
pub list_max: u16,
pub btree_min: u16,
pub message_count: u16,
pub kind: SohmIndexKind,
pub index_address: Option<u64>,
pub heap_address: Option<u64>,
}
impl SohmIndexHeader {
pub fn covers(&self, message_type: MessageType) -> bool {
match u32::from(message_type.to_u16()) {
bit if bit < 16 => self.message_type_flags & (1u16 << bit) != 0,
_ => false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SohmTable {
pub indexes: Vec<SohmIndexHeader>,
}
fn index_header_len(offset_size: u8) -> usize {
INDEX_HEADER_FIXED_LEN + 2 * offset_size as usize
}
fn table_len(index_count: u8, offset_size: u8) -> usize {
TABLE_FIXED_LEN + index_count as usize * index_header_len(offset_size)
}
pub fn record_len(offset_size: u8) -> usize {
let object_header_location = 1 + 1 + 2 + offset_size as usize;
RECORD_PREFIX_LEN + HEAP_LOCATION_LEN.max(object_header_location)
}
#[cfg_attr(not(feature = "checksum"), allow(unused_variables))]
fn verify_checksum(image: &[u8]) -> Result<(), FormatError> {
#[cfg(feature = "checksum")]
{
let split = image.len() - 4;
let stored = u32::from_le_bytes([
image[split],
image[split + 1],
image[split + 2],
image[split + 3],
]);
let computed = crate::checksum::jenkins_lookup3(&image[..split]);
if computed != stored {
return Err(FormatError::ChecksumMismatch {
expected: stored,
computed,
});
}
}
Ok(())
}
impl SohmTable {
pub fn parse(image: &[u8], index_count: u8, offset_size: u8) -> Result<Self, FormatError> {
if index_count == 0 || index_count > MAX_INDEXES {
return Err(FormatError::InvalidSohmIndexCount(index_count));
}
let expected = table_len(index_count, offset_size);
ensure_len(image, 0, expected)?;
let image = &image[..expected];
if &image[..4] != TABLE_SIGNATURE {
return Err(FormatError::InvalidSohmTableSignature);
}
verify_checksum(image)?;
let mut indexes = Vec::with_capacity(index_count as usize);
let mut pos = 4;
for _ in 0..index_count {
let version = image[pos];
if version != SOHM_VERSION {
return Err(FormatError::InvalidSohmTableVersion(version));
}
let kind = match image[pos + 1] {
0 => SohmIndexKind::List,
1 => SohmIndexKind::BTree,
other => return Err(FormatError::InvalidSohmIndexKind(other)),
};
let message_type_flags = u16::from_le_bytes([image[pos + 2], image[pos + 3]]);
let min_message_size = u32::from_le_bytes([
image[pos + 4],
image[pos + 5],
image[pos + 6],
image[pos + 7],
]);
let list_max = u16::from_le_bytes([image[pos + 8], image[pos + 9]]);
let btree_min = u16::from_le_bytes([image[pos + 10], image[pos + 11]]);
let message_count = u16::from_le_bytes([image[pos + 12], image[pos + 13]]);
let mut at = pos + INDEX_HEADER_FIXED_LEN;
let index_address = optional_address(image, at, offset_size)?;
at += offset_size as usize;
let heap_address = optional_address(image, at, offset_size)?;
pos = at + offset_size as usize;
indexes.push(SohmIndexHeader {
message_type_flags,
min_message_size,
list_max,
btree_min,
message_count,
kind,
index_address,
heap_address,
});
}
Ok(Self { indexes })
}
pub fn read(
file_data: &[u8],
message: &SharedMessageTableMessage,
offset_size: u8,
) -> Result<Self, FormatError> {
let at = message.table_address.to_usize()?;
let len = table_len(message.index_count, offset_size);
ensure_len(file_data, at, len)?;
Self::parse(&file_data[at..at + len], message.index_count, offset_size)
}
pub fn read_from_source<S: Source + ?Sized>(
source: &S,
message: &SharedMessageTableMessage,
offset_size: u8,
) -> Result<Self, FormatError> {
let len = table_len(message.index_count, offset_size);
let image = source.read_metadata_at(message.table_address, len)?;
Self::parse(&image, message.index_count, offset_size)
}
pub fn index_for(&self, message_type: MessageType) -> Option<&SohmIndexHeader> {
self.indexes.iter().find(|index| index.covers(message_type))
}
}
fn optional_address(data: &[u8], pos: usize, offset_size: u8) -> Result<Option<u64>, FormatError> {
let addr = read_offset(data, pos, offset_size)?;
Ok((!is_undefined_addr(addr, offset_size)).then_some(addr))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SohmLocation {
Heap {
reference_count: u32,
heap_id: [u8; FHEAP_ID_LEN],
},
ObjectHeader {
message_type: u8,
creation_index: u16,
address: u64,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SohmRecord {
pub hash: u32,
pub location: SohmLocation,
}
impl SohmRecord {
pub fn parse(data: &[u8], offset_size: u8) -> Result<Self, FormatError> {
ensure_len(data, 0, RECORD_PREFIX_LEN)?;
let hash = u32::from_le_bytes([data[1], data[2], data[3], data[4]]);
let location = match data[0] {
LOCATION_HEAP => {
ensure_len(data, RECORD_PREFIX_LEN, HEAP_LOCATION_LEN)?;
let at = RECORD_PREFIX_LEN;
let reference_count =
u32::from_le_bytes([data[at], data[at + 1], data[at + 2], data[at + 3]]);
let mut heap_id = [0u8; FHEAP_ID_LEN];
heap_id.copy_from_slice(&data[at + 4..at + 4 + FHEAP_ID_LEN]);
SohmLocation::Heap {
reference_count,
heap_id,
}
}
LOCATION_OBJECT_HEADER => {
let at = RECORD_PREFIX_LEN;
ensure_len(data, at, 4)?;
let message_type = data[at + 1];
let creation_index = u16::from_le_bytes([data[at + 2], data[at + 3]]);
let address = read_offset(data, at + 4, offset_size)?;
SohmLocation::ObjectHeader {
message_type,
creation_index,
address,
}
}
other => return Err(FormatError::InvalidSohmRecordLocation(other)),
};
Ok(Self { hash, location })
}
}
pub fn parse_list(
image: &[u8],
message_count: u16,
offset_size: u8,
) -> Result<Vec<SohmRecord>, FormatError> {
let stride = record_len(offset_size);
let expected = LIST_FIXED_LEN + message_count as usize * stride;
ensure_len(image, 0, expected)?;
let image = &image[..expected];
if &image[..4] != LIST_SIGNATURE {
return Err(FormatError::InvalidSohmListSignature);
}
verify_checksum(image)?;
let mut records = Vec::with_capacity(message_count as usize);
for i in 0..message_count as usize {
let at = 4 + i * stride;
records.push(SohmRecord::parse(&image[at..at + stride], offset_size)?);
}
Ok(records)
}
fn list_image_len(message_count: u16, offset_size: u8) -> usize {
LIST_FIXED_LEN + message_count as usize * record_len(offset_size)
}
pub fn read_index_records_from_source<S: Source + ?Sized>(
source: &S,
index: &SohmIndexHeader,
offset_size: u8,
length_size: u8,
) -> Result<Vec<SohmRecord>, FormatError> {
let Some(address) = index.index_address else {
return Ok(Vec::new());
};
match index.kind {
SohmIndexKind::List => {
let len = list_image_len(index.message_count, offset_size);
let image = source.read_metadata_at(address, len)?;
parse_list(&image, index.message_count, offset_size)
}
SohmIndexKind::BTree => {
let header =
BTreeV2Header::parse_from_source(source, address, offset_size, length_size)?;
check_btree_type(&header)?;
let records =
collect_btree_v2_records_from_source(source, &header, offset_size, length_size)?;
records
.iter()
.map(|record| SohmRecord::parse(&record.data, offset_size))
.collect()
}
}
}
fn check_btree_type(header: &BTreeV2Header) -> Result<(), FormatError> {
if header.tree_type != BTREE_SOHM_INDEX_TYPE {
return Err(FormatError::InvalidSohmBTreeType(header.tree_type));
}
Ok(())
}
fn index_for_read(
table: &SohmTable,
message_type: MessageType,
) -> Result<(&SohmIndexHeader, u64), FormatError> {
let index = table
.index_for(message_type)
.ok_or(FormatError::SohmIndexMissing(message_type.to_u16()))?;
let heap = index
.heap_address
.ok_or(FormatError::SohmIndexMissing(message_type.to_u16()))?;
Ok((index, heap))
}
pub fn read_heap_message(
file_data: &[u8],
table: &SohmTable,
message_type: MessageType,
heap_id: &[u8; FHEAP_ID_LEN],
offset_size: u8,
length_size: u8,
) -> Result<Vec<u8>, FormatError> {
let (_, heap_address) = index_for_read(table, message_type)?;
let heap = FractalHeapHeader::parse(
file_data,
heap_address.to_usize()?,
offset_size,
length_size,
)?;
heap.object_reader(offset_size, length_size)
.read(file_data, &heap_id[..heap.heap_id_length as usize])
}
pub fn read_heap_message_from_source<S: Source + ?Sized>(
source: &S,
table: &SohmTable,
message_type: MessageType,
heap_id: &[u8; FHEAP_ID_LEN],
offset_size: u8,
length_size: u8,
) -> Result<Vec<u8>, FormatError> {
let (_, heap_address) = index_for_read(table, message_type)?;
let heap =
FractalHeapHeader::parse_from_source(source, heap_address, offset_size, length_size)?;
heap.object_reader(offset_size, length_size)
.read_from_source(source, &heap_id[..heap.heap_id_length as usize])
}
#[cfg(test)]
mod tests {
use super::*;
fn table_image(indexes: &[SohmIndexHeader]) -> Vec<u8> {
let mut image = Vec::from(*TABLE_SIGNATURE);
for index in indexes {
image.push(SOHM_VERSION);
image.push(match index.kind {
SohmIndexKind::List => 0,
SohmIndexKind::BTree => 1,
});
image.extend_from_slice(&index.message_type_flags.to_le_bytes());
image.extend_from_slice(&index.min_message_size.to_le_bytes());
image.extend_from_slice(&index.list_max.to_le_bytes());
image.extend_from_slice(&index.btree_min.to_le_bytes());
image.extend_from_slice(&index.message_count.to_le_bytes());
image.extend_from_slice(&index.index_address.unwrap_or(u64::MAX).to_le_bytes());
image.extend_from_slice(&index.heap_address.unwrap_or(u64::MAX).to_le_bytes());
}
let checksum = crate::checksum::jenkins_lookup3(&image);
image.extend_from_slice(&checksum.to_le_bytes());
image
}
fn reseal(image: &mut [u8]) {
let split = image.len() - 4;
let checksum = crate::checksum::jenkins_lookup3(&image[..split]);
image[split..].copy_from_slice(&checksum.to_le_bytes());
}
fn sample_index() -> SohmIndexHeader {
SohmIndexHeader {
message_type_flags: (1 << 1) | (1 << 3) | (1 << 12),
min_message_size: 250,
list_max: 50,
btree_min: 40,
message_count: 2,
kind: SohmIndexKind::List,
index_address: Some(0x1234),
heap_address: Some(0x5678),
}
}
#[test]
fn the_table_message_reads_the_count_after_the_address() {
let mut data = vec![0u8];
data.extend_from_slice(&0x400u64.to_le_bytes());
data.push(3);
let message = SharedMessageTableMessage::parse(&data, 8).unwrap();
assert_eq!(message.table_address, 0x400);
assert_eq!(message.index_count, 3);
}
#[test]
fn a_table_message_with_no_indexes_is_refused() {
let mut data = vec![0u8];
data.extend_from_slice(&0x400u64.to_le_bytes());
data.push(0);
assert_eq!(
SharedMessageTableMessage::parse(&data, 8).unwrap_err(),
FormatError::InvalidSohmIndexCount(0)
);
}
#[test]
fn a_table_message_past_the_index_maximum_is_refused() {
let mut data = vec![0u8];
data.extend_from_slice(&0x400u64.to_le_bytes());
data.push(MAX_INDEXES + 1);
assert_eq!(
SharedMessageTableMessage::parse(&data, 8).unwrap_err(),
FormatError::InvalidSohmIndexCount(MAX_INDEXES + 1)
);
}
#[test]
fn a_table_message_of_another_version_is_refused() {
let mut data = vec![1u8];
data.extend_from_slice(&0x400u64.to_le_bytes());
data.push(1);
assert_eq!(
SharedMessageTableMessage::parse(&data, 8).unwrap_err(),
FormatError::InvalidSohmTableVersion(1)
);
}
#[test]
fn an_index_header_round_trips_through_its_image() {
let index = sample_index();
let table = SohmTable::parse(&table_image(std::slice::from_ref(&index)), 1, 8).unwrap();
assert_eq!(table.indexes, vec![index]);
}
#[test]
fn the_version_byte_precedes_the_index_type_byte() {
let mut index = sample_index();
index.kind = SohmIndexKind::BTree;
let image = table_image(&[index]);
assert_eq!(image[4], SOHM_VERSION);
assert_eq!(image[5], 1);
assert_eq!(
SohmTable::parse(&image, 1, 8).unwrap().indexes[0].kind,
SohmIndexKind::BTree
);
}
#[test]
fn an_undefined_index_or_heap_address_reads_as_none() {
let mut index = sample_index();
index.index_address = None;
index.heap_address = None;
let table = SohmTable::parse(&table_image(&[index]), 1, 8).unwrap();
assert_eq!(table.indexes[0].index_address, None);
assert_eq!(table.indexes[0].heap_address, None);
}
#[test]
fn a_table_with_a_bad_signature_is_refused() {
let mut image = table_image(&[sample_index()]);
image[0] = b'X';
assert_eq!(
SohmTable::parse(&image, 1, 8).unwrap_err(),
FormatError::InvalidSohmTableSignature
);
}
#[cfg(feature = "checksum")]
#[test]
fn a_table_whose_checksum_disagrees_is_refused() {
let mut image = table_image(&[sample_index()]);
image[12] ^= 0x01;
assert!(matches!(
SohmTable::parse(&image, 1, 8).unwrap_err(),
FormatError::ChecksumMismatch { .. }
));
}
#[test]
fn an_index_kind_the_format_does_not_define_is_refused() {
let mut image = table_image(&[sample_index()]);
image[5] = 2;
reseal(&mut image);
assert_eq!(
SohmTable::parse(&image, 1, 8).unwrap_err(),
FormatError::InvalidSohmIndexKind(2)
);
}
#[test]
fn a_table_shorter_than_its_index_count_is_refused() {
let image = table_image(&[sample_index()]);
assert!(matches!(
SohmTable::parse(&image, 2, 8).unwrap_err(),
FormatError::UnexpectedEof { .. }
));
}
#[test]
fn an_index_covers_exactly_the_types_its_flags_name() {
let index = sample_index();
assert!(index.covers(MessageType::Dataspace));
assert!(index.covers(MessageType::Datatype));
assert!(index.covers(MessageType::Attribute));
assert!(!index.covers(MessageType::FillValue));
assert!(!index.covers(MessageType::FilterPipeline));
assert!(!index.covers(MessageType::AttributeInfo));
}
#[test]
fn the_table_picks_the_index_covering_a_type() {
let mut datatypes = sample_index();
datatypes.message_type_flags = 1 << 3;
let mut attributes = sample_index();
attributes.message_type_flags = 1 << 12;
attributes.heap_address = Some(0x9999);
let table = SohmTable {
indexes: vec![datatypes, attributes],
};
assert_eq!(
table
.index_for(MessageType::Attribute)
.unwrap()
.heap_address,
Some(0x9999)
);
assert_eq!(
table.index_for(MessageType::Datatype).unwrap().heap_address,
Some(0x5678)
);
assert!(table.index_for(MessageType::FillValue).is_none());
}
#[test]
fn both_record_shapes_share_one_stride() {
assert_eq!(record_len(8), 17);
assert_eq!(record_len(4), 17);
assert_eq!(record_len(16), 25);
}
fn heap_record_bytes(reference_count: u32, heap_id: [u8; 8], offset_size: u8) -> Vec<u8> {
let mut data = vec![LOCATION_HEAP];
data.extend_from_slice(&0xDEADBEEFu32.to_le_bytes());
data.extend_from_slice(&reference_count.to_le_bytes());
data.extend_from_slice(&heap_id);
data.resize(record_len(offset_size), 0);
data
}
#[test]
fn a_heap_record_carries_its_reference_count_and_heap_id() {
let id = [1, 2, 3, 4, 5, 6, 7, 8];
let record = SohmRecord::parse(&heap_record_bytes(3, id, 8), 8).unwrap();
assert_eq!(record.hash, 0xDEADBEEF);
assert_eq!(
record.location,
SohmLocation::Heap {
reference_count: 3,
heap_id: id,
}
);
}
#[test]
fn an_object_header_record_skips_its_reserved_byte() {
let mut data = vec![LOCATION_OBJECT_HEADER];
data.extend_from_slice(&7u32.to_le_bytes());
data.push(0); data.push(0x0C); data.extend_from_slice(&4u16.to_le_bytes());
data.extend_from_slice(&0x2000u64.to_le_bytes());
let record = SohmRecord::parse(&data, 8).unwrap();
assert_eq!(record.hash, 7);
assert_eq!(
record.location,
SohmLocation::ObjectHeader {
message_type: 0x0C,
creation_index: 4,
address: 0x2000,
}
);
}
#[test]
fn a_record_location_the_format_does_not_define_is_refused() {
let mut data = vec![9u8];
data.extend_from_slice(&[0u8; 16]);
assert_eq!(
SohmRecord::parse(&data, 8).unwrap_err(),
FormatError::InvalidSohmRecordLocation(9)
);
}
fn list_image(records: &[Vec<u8>]) -> Vec<u8> {
let mut image = Vec::from(*LIST_SIGNATURE);
for record in records {
image.extend_from_slice(record);
}
let checksum = crate::checksum::jenkins_lookup3(&image);
image.extend_from_slice(&checksum.to_le_bytes());
image
}
#[test]
fn a_list_index_reads_every_record_it_declares() {
let first = heap_record_bytes(1, [1, 0, 0, 0, 0, 0, 0, 0], 8);
let second = heap_record_bytes(2, [2, 0, 0, 0, 0, 0, 0, 0], 8);
let image = list_image(&[first, second]);
let records = parse_list(&image, 2, 8).unwrap();
assert_eq!(records.len(), 2);
assert_eq!(
records[1].location,
SohmLocation::Heap {
reference_count: 2,
heap_id: [2, 0, 0, 0, 0, 0, 0, 0],
}
);
}
#[test]
fn a_list_checksum_covers_only_the_records_in_use() {
let record = heap_record_bytes(1, [1, 0, 0, 0, 0, 0, 0, 0], 8);
let mut image = list_image(&[record]);
image.extend_from_slice(&vec![0u8; 4 * record_len(8)]);
assert_eq!(parse_list(&image, 1, 8).unwrap().len(), 1);
}
#[test]
fn a_list_with_a_bad_signature_is_refused() {
let mut image = list_image(&[heap_record_bytes(1, [0; 8], 8)]);
image[0] = b'X';
assert_eq!(
parse_list(&image, 1, 8).unwrap_err(),
FormatError::InvalidSohmListSignature
);
}
#[cfg(feature = "checksum")]
#[test]
fn a_list_whose_checksum_disagrees_is_refused() {
let mut image = list_image(&[heap_record_bytes(1, [0; 8], 8)]);
image[6] ^= 0x01;
assert!(matches!(
parse_list(&image, 1, 8).unwrap_err(),
FormatError::ChecksumMismatch { .. }
));
}
#[test]
fn a_list_shorter_than_its_record_count_is_refused() {
let image = list_image(&[heap_record_bytes(1, [0; 8], 8)]);
assert!(matches!(
parse_list(&image, 4, 8).unwrap_err(),
FormatError::UnexpectedEof { .. }
));
}
#[test]
fn an_index_with_no_address_holds_no_records() {
let mut index = sample_index();
index.index_address = None;
let empty = crate::source::BytesSource::new(Vec::new());
assert!(
read_index_records_from_source(&empty, &index, 8, 8)
.unwrap()
.is_empty()
);
}
#[test]
fn a_list_index_is_read_at_the_address_its_header_names() {
let mut image = vec![0u8; 64];
image.extend_from_slice(&list_image(&[heap_record_bytes(
3,
[7, 0, 0, 0, 0, 0, 0, 0],
8,
)]));
let mut index = sample_index();
index.index_address = Some(64);
index.message_count = 1;
let source = crate::source::BytesSource::new(image);
let records = read_index_records_from_source(&source, &index, 8, 8).unwrap();
assert_eq!(
records[0].location,
SohmLocation::Heap {
reference_count: 3,
heap_id: [7, 0, 0, 0, 0, 0, 0, 0],
}
);
}
#[test]
fn a_message_type_no_index_covers_is_named_in_the_error() {
let table = SohmTable {
indexes: vec![sample_index()],
};
assert_eq!(
index_for_read(&table, MessageType::FillValue).unwrap_err(),
FormatError::SohmIndexMissing(MessageType::FillValue.to_u16())
);
}
#[test]
fn an_index_without_a_heap_cannot_answer_a_lookup() {
let mut index = sample_index();
index.heap_address = None;
let table = SohmTable {
indexes: vec![index],
};
assert_eq!(
index_for_read(&table, MessageType::Datatype).unwrap_err(),
FormatError::SohmIndexMissing(MessageType::Datatype.to_u16())
);
}
}