#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use crate::address::BaseAddress;
use crate::bytes::{ensure_len, read_offset};
use crate::convert::TryToUsize;
use crate::error::FormatError;
use crate::message_type::MessageType;
use crate::object_header::ObjectHeader;
use crate::sohm::SohmTable;
use crate::source::Source;
pub(crate) const FHEAP_ID_LEN: usize = 8;
const REF_TYPE_SOHM: u8 = 1;
const REF_TYPE_COMMITTED: u8 = 2;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SharedLocation {
ObjectHeader(u64),
SohmHeap([u8; FHEAP_ID_LEN]),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SharedMessageRef {
pub version: u8,
pub ref_type: u8,
pub location: SharedLocation,
}
pub fn is_shared(msg_flags: u8) -> bool {
msg_flags & 0x02 != 0
}
pub fn parse_shared_ref(
data: &[u8],
offset_size: u8,
length_size: u8,
) -> Result<SharedMessageRef, FormatError> {
ensure_len(data, 0, 2)?;
let version = data[0];
match version {
1 => {
let pos = 2 + 6 + length_size as usize;
let addr = read_offset(data, pos, offset_size)?;
Ok(SharedMessageRef {
version,
ref_type: REF_TYPE_COMMITTED,
location: SharedLocation::ObjectHeader(addr),
})
}
2 | 3 => {
let ref_type = data[1];
let location = if ref_type == REF_TYPE_SOHM {
ensure_len(data, 2, FHEAP_ID_LEN)?;
let mut id = [0u8; FHEAP_ID_LEN];
id.copy_from_slice(&data[2..2 + FHEAP_ID_LEN]);
SharedLocation::SohmHeap(id)
} else {
SharedLocation::ObjectHeader(read_offset(data, 2, offset_size)?)
};
Ok(SharedMessageRef {
version,
ref_type,
location,
})
}
_ => Err(FormatError::InvalidSharedMessageVersion(version)),
}
}
const WRITE_REF_VERSION: u8 = 2;
pub fn encode_committed_ref(address: u64, offset_size: u8) -> Vec<u8> {
let mut buf = Vec::with_capacity(2 + offset_size as usize);
buf.push(WRITE_REF_VERSION);
buf.push(REF_TYPE_COMMITTED);
buf.extend_from_slice(&address.to_le_bytes()[..offset_size as usize]);
buf
}
const SOHM_REF_VERSION: u8 = 3;
pub fn encode_sohm_ref(heap_id: &[u8; FHEAP_ID_LEN]) -> Vec<u8> {
let mut buf = Vec::with_capacity(2 + FHEAP_ID_LEN);
buf.push(SOHM_REF_VERSION);
buf.push(REF_TYPE_SOHM);
buf.extend_from_slice(heap_id);
buf
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DatatypeLocation {
Inline,
Committed(u64),
CommittedPath(String),
}
impl DatatypeLocation {
pub fn reference_bytes(&self, offset_size: u8) -> Option<Vec<u8>> {
match self {
Self::Inline => None,
Self::Committed(addr) => Some(encode_committed_ref(*addr, offset_size)),
Self::CommittedPath(_) => Some(encode_committed_ref(u64::MAX, offset_size)),
}
}
pub fn unresolved_path(&self) -> Option<&str> {
match self {
Self::CommittedPath(path) => Some(path),
Self::Inline | Self::Committed(_) => None,
}
}
pub fn is_committed(&self) -> bool {
!matches!(self, Self::Inline)
}
}
pub trait SharedResolver {
fn resolve(&self, reference: &[u8], target: MessageType) -> Result<Vec<u8>, FormatError>;
fn committed_address(&self, reference: &[u8]) -> Result<Option<u64>, FormatError>;
}
pub struct BufferedResolver<'a> {
file_data: &'a [u8],
offset_size: u8,
length_size: u8,
sohm: Option<&'a SohmTable>,
}
impl<'a> BufferedResolver<'a> {
pub fn new(
file_data: &'a [u8],
offset_size: u8,
length_size: u8,
sohm: Option<&'a SohmTable>,
) -> Self {
Self {
file_data,
offset_size,
length_size,
sohm,
}
}
}
impl SharedResolver for BufferedResolver<'_> {
fn resolve(&self, reference: &[u8], target: MessageType) -> Result<Vec<u8>, FormatError> {
let parsed = parse_shared_ref(reference, self.offset_size, self.length_size)?;
let addr = match parsed.location {
SharedLocation::SohmHeap(id) => {
let table = self.sohm.ok_or(FormatError::UnsupportedSohmReference)?;
return crate::sohm::read_heap_message(
self.file_data,
table,
target,
&id,
self.offset_size,
self.length_size,
);
}
SharedLocation::ObjectHeader(addr) => addr,
};
let header = ObjectHeader::parse(
self.file_data,
addr.to_usize()?,
self.offset_size,
self.length_size,
)?;
select_shared_message(&header, target, addr)
}
fn committed_address(&self, reference: &[u8]) -> Result<Option<u64>, FormatError> {
committed_address_in(reference, self.offset_size, self.length_size)
}
}
pub struct SourceResolver<'a, S: Source + ?Sized> {
source: &'a S,
offset_size: u8,
length_size: u8,
sohm: Option<&'a SohmTable>,
}
impl<'a, S: Source + ?Sized> SourceResolver<'a, S> {
pub fn new(
source: &'a S,
offset_size: u8,
length_size: u8,
sohm: Option<&'a SohmTable>,
) -> Self {
Self {
source,
offset_size,
length_size,
sohm,
}
}
}
impl<S: Source + ?Sized> SharedResolver for SourceResolver<'_, S> {
fn resolve(&self, reference: &[u8], target: MessageType) -> Result<Vec<u8>, FormatError> {
let parsed = parse_shared_ref(reference, self.offset_size, self.length_size)?;
let addr = match parsed.location {
SharedLocation::SohmHeap(id) => {
let table = self.sohm.ok_or(FormatError::UnsupportedSohmReference)?;
return crate::sohm::read_heap_message_from_source(
self.source,
table,
target,
&id,
self.offset_size,
self.length_size,
);
}
SharedLocation::ObjectHeader(addr) => addr,
};
let header = ObjectHeader::parse_from_source(
self.source,
addr,
self.offset_size,
self.length_size,
BaseAddress::ZERO,
)?;
select_shared_message(&header, target, addr)
}
fn committed_address(&self, reference: &[u8]) -> Result<Option<u64>, FormatError> {
committed_address_in(reference, self.offset_size, self.length_size)
}
}
pub struct Unresolvable;
impl SharedResolver for Unresolvable {
fn resolve(&self, _reference: &[u8], target: MessageType) -> Result<Vec<u8>, FormatError> {
Err(FormatError::UnresolvedSharedMessage(target.to_u16()))
}
fn committed_address(&self, _reference: &[u8]) -> Result<Option<u64>, FormatError> {
Err(FormatError::UnresolvedSharedMessage(
MessageType::Datatype.to_u16(),
))
}
}
pub(crate) fn committed_address_in(
reference: &[u8],
offset_size: u8,
length_size: u8,
) -> Result<Option<u64>, FormatError> {
match parse_shared_ref(reference, offset_size, length_size)?.location {
SharedLocation::ObjectHeader(addr) => Ok(Some(addr)),
SharedLocation::SohmHeap(_) => Ok(None),
}
}
fn select_shared_message(
target_header: &ObjectHeader,
target_msg_type: MessageType,
object_header_address: u64,
) -> Result<Vec<u8>, FormatError> {
target_header
.messages
.iter()
.find(|msg| msg.msg_type == target_msg_type && !is_shared(msg.flags))
.map(|msg| msg.data.clone())
.ok_or(FormatError::SharedMessageMissing {
object_header_address,
message_type: target_msg_type.to_u16(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::object_header::HeaderMessage;
fn header_with(messages: Vec<HeaderMessage>) -> ObjectHeader {
ObjectHeader {
version: 2,
messages,
reference_count: None,
flags: 0,
access_time: None,
modification_time: None,
change_time: None,
birth_time: None,
}
}
fn message(msg_type: MessageType, flags: u8, data: Vec<u8>) -> HeaderMessage {
HeaderMessage {
msg_type,
size: data.len(),
flags,
creation_order: None,
data,
}
}
#[test]
fn is_shared_flag() {
assert!(!is_shared(0x00));
assert!(!is_shared(0x01));
assert!(is_shared(0x02));
assert!(is_shared(0x03));
assert!(is_shared(0x06));
}
#[test]
fn parse_v2_committed_ref() {
let mut data = vec![2, REF_TYPE_COMMITTED];
data.extend_from_slice(&0x320u64.to_le_bytes());
let shared = parse_shared_ref(&data, 8, 8).unwrap();
assert_eq!(shared.version, 2);
assert_eq!(shared.ref_type, REF_TYPE_COMMITTED);
assert_eq!(shared.location, SharedLocation::ObjectHeader(0x320));
}
#[test]
fn parse_v2_ref_as_libhdf5_writes_it() {
let data = [0x02, 0x02, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let shared = parse_shared_ref(&data, 8, 8).unwrap();
assert_eq!(shared.location, SharedLocation::ObjectHeader(800));
}
#[test]
fn parse_v1_ref_skips_the_local_heap_address() {
let mut data = vec![1, 0];
data.extend_from_slice(&[0u8; 6]); data.extend_from_slice(&0x1111u64.to_le_bytes()); data.extend_from_slice(&0x5678u64.to_le_bytes());
let shared = parse_shared_ref(&data, 8, 8).unwrap();
assert_eq!(shared.version, 1);
assert_eq!(shared.location, SharedLocation::ObjectHeader(0x5678));
}
#[test]
fn parse_v1_ref_uses_the_files_length_size() {
let mut data = vec![1, 0];
data.extend_from_slice(&[0u8; 6]);
data.extend_from_slice(&0x1111u32.to_le_bytes()); data.extend_from_slice(&0x5678u32.to_le_bytes());
let shared = parse_shared_ref(&data, 4, 4).unwrap();
assert_eq!(shared.location, SharedLocation::ObjectHeader(0x5678));
}
#[test]
fn parse_v3_committed_ref() {
let mut data = vec![3, REF_TYPE_COMMITTED];
data.extend_from_slice(&0xABCDu64.to_le_bytes());
let shared = parse_shared_ref(&data, 8, 8).unwrap();
assert_eq!(shared.version, 3);
assert_eq!(shared.location, SharedLocation::ObjectHeader(0xABCD));
}
#[test]
fn parse_v3_sohm_ref() {
let mut data = vec![3, REF_TYPE_SOHM];
data.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44]);
let shared = parse_shared_ref(&data, 8, 8).unwrap();
assert_eq!(shared.ref_type, REF_TYPE_SOHM);
assert_eq!(
shared.location,
SharedLocation::SohmHeap([0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44])
);
}
#[test]
fn a_heap_reference_round_trips_through_its_encoding() {
let id = [0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44];
let encoded = encode_sohm_ref(&id);
assert_eq!(encoded[0], 3);
assert_eq!(
parse_shared_ref(&encoded, 8, 8).unwrap().location,
SharedLocation::SohmHeap(id)
);
}
#[test]
fn parse_v3_sohm_too_short() {
let data = vec![3, REF_TYPE_SOHM, 0xAA, 0xBB];
let err = parse_shared_ref(&data, 8, 8).unwrap_err();
assert!(matches!(err, FormatError::UnexpectedEof { .. }));
}
#[test]
fn invalid_version() {
let data = vec![99, 0];
let err = parse_shared_ref(&data, 8, 8).unwrap_err();
assert_eq!(err, FormatError::InvalidSharedMessageVersion(99));
}
#[test]
fn truncated_data() {
let data = vec![3u8]; let err = parse_shared_ref(&data, 8, 8).unwrap_err();
assert!(matches!(err, FormatError::UnexpectedEof { .. }));
}
#[test]
fn parse_four_byte_offsets() {
let mut data = vec![3, REF_TYPE_COMMITTED];
data.extend_from_slice(&0x1000u32.to_le_bytes());
let shared = parse_shared_ref(&data, 4, 4).unwrap();
assert_eq!(shared.location, SharedLocation::ObjectHeader(0x1000));
}
#[test]
fn a_sohm_reference_without_a_table_is_refused_rather_than_followed() {
let mut reference = vec![3, REF_TYPE_SOHM];
reference.extend_from_slice(&[0xFF; 8]);
let resolver = BufferedResolver::new(&[], 8, 8, None);
let err = resolver
.resolve(&reference, MessageType::Datatype)
.unwrap_err();
assert_eq!(err, FormatError::UnsupportedSohmReference);
}
#[test]
fn a_sohm_reference_names_no_committed_object() {
let mut reference = vec![3, REF_TYPE_SOHM];
reference.extend_from_slice(&[0xFF; 8]);
assert_eq!(committed_address_in(&reference, 8, 8).unwrap(), None);
}
#[test]
fn a_reference_to_a_header_without_that_message_is_an_error() {
let header = header_with(vec![message(MessageType::Dataspace, 0, vec![1, 2, 3])]);
let err = select_shared_message(&header, MessageType::Datatype, 0x320).unwrap_err();
assert_eq!(
err,
FormatError::SharedMessageMissing {
object_header_address: 0x320,
message_type: MessageType::Datatype.to_u16(),
}
);
}
#[test]
fn a_shared_message_in_the_target_is_not_mistaken_for_content() {
let header = header_with(vec![message(MessageType::Datatype, 0x02, vec![2, 2, 0, 0])]);
let err = select_shared_message(&header, MessageType::Datatype, 0x320).unwrap_err();
assert!(matches!(err, FormatError::SharedMessageMissing { .. }));
}
#[test]
fn the_unresolvable_resolver_refuses() {
let err = Unresolvable
.resolve(
&[2, REF_TYPE_COMMITTED, 0, 0, 0, 0, 0, 0, 0, 0],
MessageType::Datatype,
)
.unwrap_err();
assert_eq!(
err,
FormatError::UnresolvedSharedMessage(MessageType::Datatype.to_u16())
);
}
}