///|
/// An attribute attached to an HDF5 object.
#derive(@debug.Debug)
pub(all) struct Attribute {
name : String
dataspace : Dataspace
datatype : Datatype
values : DatasetValues?
}
///|
pub extend Attribute with @debug.Debug::{to_repr}
///|
fn decode_c_string(bytes : Bytes) -> String raise Hdf5Error {
let mut end = 0
while end < bytes.length() && bytes.unsafe_get(end) != b'\x00' {
end = end + 1
}
let view = bytes.get_view(start=0, end~).unwrap()
@utf8.decode(view) catch {
_ => raise InvalidField("invalid UTF-8 attribute name")
}
}
///|
fn parse_attribute_data(
data : Bytes,
message_flags : Byte,
bytes : Bytes,
offset_size : Int32,
length_size : Int32,
base_address : UInt64,
source~ : &Hdf5Source? = None,
shared_messages~ : SharedMessageContext? = None,
) -> Attribute raise Hdf5Error {
let mut payload = data
if (message_flags.to_int() & shared_message_flag) != 0 {
let context = match shared_messages {
Some(context) => context
None =>
raise UnsupportedFeature(
"shared attribute messages are not supported here",
)
}
payload = context.resolve_shared_message(payload, msg_attribute, [])
}
let reader = Reader::new(payload)
let version = reader.read_u8().to_int()
let flags = if version == 1 {
let _reserved = reader.read_u8()
0
} else {
reader.read_u8().to_int()
}
let name_size = reader.read_u16_le().to_int()
let datatype_size = reader.read_u16_le().to_int()
let dataspace_size = reader.read_u16_le().to_int()
if version >= 3 {
let _encoding = reader.read_u8()
}
guard name_size >= 0 && datatype_size >= 0 && dataspace_size >= 0 else {
raise InvalidField("negative attribute field size")
}
let name_bytes = reader.read_bytes(name_size)
if version == 1 {
reader.skip(round_up_to_eight(name_size) - name_size)
}
let datatype_data = reader.read_bytes(datatype_size)
if version == 1 {
reader.skip(round_up_to_eight(datatype_size) - datatype_size)
}
let dataspace_data = reader.read_bytes(dataspace_size)
let value_data = reader.read_bytes(reader.remaining())
let datatype_data = if (flags & 0x01) != 0 {
match shared_messages {
Some(context) =>
context.resolve_shared_message(datatype_data, msg_datatype, [])
None =>
raise UnsupportedFeature(
"shared attribute datatype is not supported here",
)
}
} else {
datatype_data
}
let dataspace_data = if (flags & 0x02) != 0 {
match shared_messages {
Some(context) =>
context.resolve_shared_message(dataspace_data, msg_dataspace, [])
None =>
raise UnsupportedFeature(
"shared attribute dataspace is not supported here",
)
}
} else {
dataspace_data
}
let datatype = parse_datatype_data(datatype_data)
let dataspace = parse_dataspace_data(dataspace_data, length_size)
let count = dataspace.element_count()
let values = Some(
decode_values(
bytes,
value_data,
count,
datatype,
offset_size,
length_size,
base_address,
source~,
),
)
{ name: decode_c_string(name_bytes), dataspace, datatype, values, }
}
///|
fn parse_attribute_message(
message : HeaderMessage,
bytes : Bytes,
offset_size : Int32,
length_size : Int32,
base_address : UInt64,
shared_messages~ : SharedMessageContext? = None,
) -> Attribute raise Hdf5Error {
guard message.msg_type == msg_attribute else {
raise InvalidField("message is not an attribute message")
}
parse_attribute_data(
message.data,
message.flags,
bytes,
offset_size,
length_size,
base_address,
shared_messages~,
)
}
///|
/// Resolves the message payload for a dense attribute B-tree record. Shared
/// records store their message in the SOHM attribute heap instead of the
/// object's attribute heap.
fn dense_attribute_payload(
heap : FractalHeap,
record : MetadataBTreeRecord,
shared_messages : SharedMessageContext?,
) -> (Bytes, Byte) raise Hdf5Error {
if (record.flags.to_int() & shared_message_flag) == 0 {
return (heap.get_data(record.heap_id), record.flags)
}
let context = match shared_messages {
Some(context) => context
None =>
raise UnsupportedFeature(
"shared dense attribute messages are not supported here",
)
}
(
context.resolve_sohm_heap_id(record.heap_id, msg_attribute),
(record.flags.to_int() & (0xFF ^ shared_message_flag)).to_byte(),
)
}
///|
fn read_source_attribute_message(
source : &Hdf5Source,
message : HeaderMessage,
offset_size : Int32,
length_size : Int32,
base_address : UInt64,
shared_messages~ : SharedMessageContext? = None,
) -> Attribute raise Hdf5Error {
guard message.msg_type == msg_attribute else {
raise InvalidField("message is not an attribute message")
}
parse_attribute_data(
message.data,
message.flags,
b"",
offset_size,
length_size,
base_address,
source=Some(source),
shared_messages~,
)
}
///|
/// Parses all attributes attached to an object header.
fn read_attributes(
bytes : Bytes,
header : ObjectHeader,
offset_size : Int32,
length_size : Int32,
base_address : UInt64,
shared_messages~ : SharedMessageContext? = None,
) -> Array[Attribute] raise Hdf5Error {
let attributes : Array[Attribute] = []
for message in header.find(msg_attribute_info) {
let reader = Reader::new(message.data)
guard reader.read_u8() == b'\x00' else {
raise UnsupportedFeature("unsupported attribute info message version")
}
let flags = reader.read_u8().to_int()
guard (flags & 0xFC) == 0 && (flags & 3) != 2 else {
raise InvalidField("invalid attribute info flags")
}
if (flags & 1) != 0 {
reader.skip(2)
}
let heap_address = reader.read_uint_le(offset_size)
let name_index_address = reader.read_uint_le(offset_size)
let undefined = if offset_size == 8 {
0xFFFFFFFFFFFFFFFFUL
} else {
(1UL << (offset_size * 8)) - 1UL
}
let order_index_address = if (flags & 2) != 0 {
reader.read_uint_le(offset_size)
} else {
undefined
}
if heap_address != undefined {
let ordered = (flags & 2) != 0 && order_index_address != undefined
let index_address = if ordered {
order_index_address
} else if name_index_address != undefined {
name_index_address
} else {
raise InvalidField("dense attributes have no name or order index")
}
let heap = parse_fractal_heap(
bytes, heap_address, offset_size, length_size, base_address,
)
let records = read_metadata_btree_records(
bytes,
index_address,
offset_size,
length_size,
base_address,
[8, 9],
)
let dense : Array[Attribute] = []
for record in records {
let (payload, flags) = dense_attribute_payload(
heap, record, shared_messages,
)
dense.push(
parse_attribute_data(
payload,
flags,
bytes,
offset_size,
length_size,
base_address,
shared_messages~,
),
)
}
if !ordered {
dense.sort_by((left, right) => {
if left.name < right.name {
-1
} else if left.name > right.name {
1
} else {
0
}
})
}
attributes.append(dense)
} else {
guard name_index_address == undefined && order_index_address == undefined else {
raise InvalidField("attribute index is present without a fractal heap")
}
}
}
for message in header.messages {
if message.msg_type == msg_attribute {
attributes.push(
parse_attribute_message(
message,
bytes,
offset_size,
length_size,
base_address,
shared_messages~,
),
)
}
}
attributes
}
///|
/// Parses attributes using bounded reads from a random-access source.
fn read_source_attributes(
source : &Hdf5Source,
header : ObjectHeader,
offset_size : Int32,
length_size : Int32,
base_address : UInt64,
shared_messages~ : SharedMessageContext? = None,
) -> Array[Attribute] raise Hdf5Error {
let attributes : Array[Attribute] = []
for message in header.find(msg_attribute_info) {
let reader = Reader::new(message.data)
guard reader.read_u8() == b'\x00' else {
raise UnsupportedFeature("unsupported attribute info message version")
}
let flags = reader.read_u8().to_int()
guard (flags & 0xFC) == 0 && (flags & 3) != 2 else {
raise InvalidField("invalid attribute info flags")
}
if (flags & 1) != 0 {
reader.skip(2)
}
let heap_address = reader.read_uint_le(offset_size)
let name_index_address = reader.read_uint_le(offset_size)
let undefined = if offset_size == 8 {
0xFFFFFFFFFFFFFFFFUL
} else {
(1UL << (offset_size * 8)) - 1UL
}
let order_index_address = if (flags & 2) != 0 {
reader.read_uint_le(offset_size)
} else {
undefined
}
if heap_address != undefined {
let ordered = (flags & 2) != 0 && order_index_address != undefined
let index_address = if ordered {
order_index_address
} else if name_index_address != undefined {
name_index_address
} else {
raise InvalidField("dense attributes have no name or order index")
}
let heap = parse_source_fractal_heap(
source, heap_address, offset_size, length_size, base_address,
)
let records = read_source_metadata_btree_records(
source,
index_address,
offset_size,
length_size,
base_address,
[8, 9],
)
let dense : Array[Attribute] = []
for record in records {
let (payload, flags) = dense_attribute_payload(
heap, record, shared_messages,
)
dense.push(
parse_attribute_data(
payload,
flags,
b"",
offset_size,
length_size,
base_address,
source=Some(source),
shared_messages~,
),
)
}
if !ordered {
dense.sort_by((left, right) => {
if left.name < right.name {
-1
} else if left.name > right.name {
1
} else {
0
}
})
}
attributes.append(dense)
} else {
guard name_index_address == undefined && order_index_address == undefined else {
raise InvalidField("attribute index is present without a fractal heap")
}
}
}
for message in header.messages {
if message.msg_type == msg_attribute {
attributes.push(
read_source_attribute_message(
source,
message,
offset_size,
length_size,
base_address,
shared_messages~,
),
)
}
}
attributes
}