///|
/// HDF5 object header message types.
let _msg_nil : Int32 = 0x0000
///|
let msg_dataspace : Int32 = 0x0001
///|
let msg_link_info : Int32 = 0x0002
///|
let msg_datatype : Int32 = 0x0003
///|
let msg_fill_value_old : Int32 = 0x0004
///|
let msg_fill_value : Int32 = 0x0005
///|
let msg_link : Int32 = 0x0006
///|
let msg_external_data_files : Int32 = 0x0007
///|
let msg_data_layout : Int32 = 0x0008
///|
let msg_filter_pipeline : Int32 = 0x000B
///|
let msg_attribute : Int32 = 0x000C
///|
let msg_object_continuation : Int32 = 0x0010
///|
let msg_symbol_table : Int32 = 0x0011
///|
let msg_attribute_info : Int32 = 0x0015
///|
let undefined_address : UInt64 = 0xFFFFFFFFFFFFFFFFUL
///|
fn address_to_offset(
address : UInt64,
base_address : UInt64,
) -> Int32 raise Hdf5Error {
guard address != undefined_address else {
raise InvalidField("HDF5 address is undefined")
}
let absolute = base_address + address
guard absolute >= address && absolute <= 0x7FFFFFFFUL else {
raise InvalidField("HDF5 address is undefined or out of range")
}
absolute.to_int()
}
///|
/// One message from an object header.
priv struct HeaderMessage {
msg_type : Int32
flags : Byte
data : Bytes
}
///|
/// A parsed object header.
priv struct ObjectHeader {
_version : Int32
messages : Array[HeaderMessage]
}
///|
fn ObjectHeader::find(
self : ObjectHeader,
msg_type : Int32,
) -> Array[HeaderMessage] {
self.messages.filter(message => message.msg_type == msg_type)
}
///|
fn ObjectHeader::first(self : ObjectHeader, msg_type : Int32) -> HeaderMessage? {
for message in self.messages {
if message.msg_type == msg_type {
return Some(message)
}
}
None
}
///|
fn parse_v1_message_block(
bytes : Bytes,
block : Reader,
offset_size : Int32,
length_size : Int32,
base_address : UInt64,
visited : Array[Int32],
messages : Array[HeaderMessage],
) -> Unit raise Hdf5Error {
while block.remaining() >= 8 {
let msg_type = block.read_u16_le().to_int()
let size = block.read_u16_le().to_int()
let flags = block.read_u8()
block.skip(3)
let data = block.read_bytes(size)
messages.push({ msg_type, flags, data, })
if msg_type == msg_object_continuation {
let continuation = Reader::new(data)
let address = continuation.read_uint_le(offset_size)
let length = continuation.read_uint_le(length_size)
let continuation_offset = address_to_offset(address, base_address)
if !visited.contains(continuation_offset) {
visited.push(continuation_offset)
guard length <= 0x7FFFFFFFUL else {
raise InvalidField("object header continuation is too large")
}
let next = Reader::at(bytes, continuation_offset)
parse_v1_message_block(
bytes,
next.sub(continuation_offset, length.to_int()),
offset_size,
length_size,
base_address,
visited,
messages,
)
}
}
}
}
///|
fn parse_object_header_v1(
bytes : Bytes,
address : Int32,
offset_size : Int32,
length_size : Int32,
base_address : UInt64,
) -> ObjectHeader raise Hdf5Error {
let reader = Reader::at(bytes, address)
let version = reader.read_u8().to_int()
guard version == 1 else {
raise InvalidField("expected object header version 1")
}
let _reserved = reader.read_u8()
let _total_messages = reader.read_u16_le()
let _reference_count = reader.read_u32_le()
let header_size = reader.read_u32_le().reinterpret_as_int()
let _padding = reader.read_u32_le()
guard header_size >= 0 else {
raise InvalidField("negative object header size")
}
let payload_start = reader.position()
let block = reader.sub(payload_start, header_size)
let messages : Array[HeaderMessage] = []
let visited : Array[Int32] = [address]
parse_v1_message_block(
bytes, block, offset_size, length_size, base_address, visited, messages,
)
{ _version: version, messages, }
}
///|
fn parse_v2_message_block(
bytes : Bytes,
block : Reader,
offset_size : Int32,
length_size : Int32,
base_address : UInt64,
creation_order_size : Int32,
visited : Array[Int32],
messages : Array[HeaderMessage],
) -> Unit raise Hdf5Error {
let prefix_size = 4 + creation_order_size
while block.remaining() >= prefix_size {
let msg_type = block.read_u8().to_int()
let size = block.read_u16_le().to_int()
let flags = block.read_u8()
if creation_order_size == 2 {
block.skip(2)
}
let data = block.read_bytes(size)
messages.push({ msg_type, flags, data, })
if msg_type == msg_object_continuation {
let continuation = Reader::new(data)
let address = continuation.read_uint_le(offset_size)
let length = continuation.read_uint_le(length_size)
let continuation_offset = address_to_offset(address, base_address)
if !visited.contains(continuation_offset) {
visited.push(continuation_offset)
guard length >= 8UL && length <= 0x7FFFFFFFUL else {
raise InvalidField("invalid object header continuation length")
}
let next = Reader::at(bytes, continuation_offset)
let complete = next.sub(continuation_offset, length.to_int())
let signature = complete.read_bytes(4)
guard signature == b"OCHK" else {
raise InvalidField("object header continuation is missing OCHK")
}
verify_metadata_checksum(
bytes,
continuation_offset,
continuation_offset + length.to_int() - metadata_checksum_size,
"object header continuation",
)
parse_v2_message_block(
bytes,
complete.sub(continuation_offset + 4, length.to_int() - 8),
offset_size,
length_size,
base_address,
creation_order_size,
visited,
messages,
)
}
}
}
}
///|
fn parse_object_header_v2(
bytes : Bytes,
address : Int32,
offset_size : Int32,
length_size : Int32,
base_address : UInt64,
) -> ObjectHeader raise Hdf5Error {
let reader = Reader::at(bytes, address)
let signature = reader.read_bytes(4)
guard signature == b"OHDR" else {
raise InvalidField("expected OHDR signature")
}
let version = reader.read_u8().to_int()
guard version == 2 else {
raise InvalidField("expected object header version 2")
}
let flags = reader.read_u8().to_int()
let creation_order_size = if (flags & 0x04) != 0 { 2 } else { 0 }
if (flags & 0x20) != 0 {
reader.skip(16)
}
if (flags & 0x10) != 0 {
reader.skip(4)
}
let raw_chunk_size = reader.read_uint_le(1 << (flags & 0x03))
guard raw_chunk_size <= 0x7FFFFFFFUL else {
raise InvalidField("object header chunk size is too large")
}
let chunk_size = raw_chunk_size.to_int()
let payload_start = reader.position()
verify_metadata_checksum(
bytes,
address,
payload_start + chunk_size,
"object header",
)
let block = reader.sub(payload_start, chunk_size)
let messages : Array[HeaderMessage] = []
let visited : Array[Int32] = [address]
parse_v2_message_block(
bytes, block, offset_size, length_size, base_address, creation_order_size, visited,
messages,
)
{ _version: version, messages, }
}
///|
/// Parses an HDF5 object header at `address`.
fn parse_object_header(
bytes : Bytes,
address : UInt64,
offset_size : Int32,
length_size : Int32,
base_address : UInt64,
) -> ObjectHeader raise Hdf5Error {
let offset = address_to_offset(address, base_address)
let reader = Reader::at(bytes, offset)
let first = reader.read_u8()
match first {
b'\x01' =>
parse_object_header_v1(
bytes, offset, offset_size, length_size, base_address,
)
b'O' =>
parse_object_header_v2(
bytes, offset, offset_size, length_size, base_address,
)
_ => raise InvalidField("unknown object header version")
}
}