///|
/// One value in an enumeration datatype, preserving the signedness of its
/// integer base type.
#derive(Eq, @debug.Debug)
pub(all) enum EnumValue {
Signed(Int64)
Unsigned(UInt64)
}
///|
pub extend EnumValue with Eq::{not_equal, equal}
///|
pub extend EnumValue with @debug.Debug::{to_repr}
///|
/// A named member of an enumeration datatype.
#derive(@debug.Debug)
pub(all) struct EnumMember {
name : String
value : EnumValue
}
///|
pub extend EnumMember with @debug.Debug::{to_repr}
///|
/// A named field within a compound datatype.
#derive(@debug.Debug)
pub(all) struct CompoundMember {
name : String
offset : Int32
datatype : Datatype
}
///|
pub extend CompoundMember with @debug.Debug::{to_repr}
///|
#derive(@debug.Debug)
pub(all) struct Datatype {
class : Int32
version : Int32
size : Int32
signed : Bool
little_endian : Bool
vlen_is_string : Bool
enum_members : Array[EnumMember]
compound_members : Array[CompoundMember]
base_types : Array[Datatype]
array_dimensions : Array[UInt64]
bit_offset : Int32
bit_precision : Int32
float_sign_position : Int32
float_exponent_position : Int32
float_exponent_size : Int32
float_mantissa_position : Int32
float_mantissa_size : Int32
float_exponent_bias : Int32
float_normalization : Int32
string_padding : Int32
string_charset : Int32
opaque_tag : String
reference_kind : Int32
}
///|
pub extend Datatype with @debug.Debug::{to_repr}
///|
fn read_datatype_name(
reader : Reader,
padded : Bool,
description : String,
) -> String raise Hdf5Error {
let start = reader.position()
let mut end = start
while end < reader.limit && reader.bytes.unsafe_get(end) != b'\x00' {
end = end + 1
}
guard end < reader.limit else { raise UnexpectedEof(start.to_int64()) }
guard end > start else { raise InvalidField(description + " is empty") }
let length = end - start
let raw = reader.read_bytes(length)
reader.skip(1)
if padded {
let used = length + 1
reader.skip((used + 7) / 8 * 8 - used)
}
@utf8.decode(raw.get_view().unwrap()) catch {
_ => raise InvalidField("invalid UTF-8 " + description)
}
}
///|
fn datatype_offset_width(size : Int32) -> Int32 {
if size <= 0x100 {
1
} else if size <= 0x10000 {
2
} else {
4
}
}
///|
fn datatype_array_size(
dimensions : Array[UInt64],
base_size : Int32,
) -> Int32 raise Hdf5Error {
let mut size = base_size.to_int64()
for dimension in dimensions {
guard dimension > 0UL &&
dimension <= 0x7FFFFFFFUL &&
size <= 0x7FFFFFFFL / dimension.reinterpret_as_int64() else {
raise InvalidField("array datatype size is too large")
}
size = size * dimension.reinterpret_as_int64()
}
size.to_int()
}
///|
fn array_datatype(
version : Int32,
dimensions : Array[UInt64],
base : Datatype,
) -> Datatype raise Hdf5Error {
{
class: 10,
version,
size: datatype_array_size(dimensions, base.size),
signed: false,
little_endian: true,
vlen_is_string: false,
enum_members: [],
compound_members: [],
base_types: [base],
array_dimensions: dimensions,
bit_offset: 0,
bit_precision: 0,
float_sign_position: 0,
float_exponent_position: 0,
float_exponent_size: 0,
float_mantissa_position: 0,
float_mantissa_size: 0,
float_exponent_bias: 0,
float_normalization: 0,
string_padding: 0,
string_charset: 0,
opaque_tag: "",
reference_kind: 0,
}
}
///|
fn parse_datatype_reader(
reader : Reader,
depth : Int32,
) -> Datatype raise Hdf5Error {
guard depth < 16 else { raise InvalidField("datatype nesting is too deep") }
let class_and_version = reader.read_u8().to_int()
let class = class_and_version & 0x0F
let version = (class_and_version >> 4) & 0x0F
let bit_field_0 = reader.read_u8().to_int()
let bit_field_1 = reader.read_u8().to_int()
let bit_field_2 = reader.read_u8().to_int()
let size = reader.read_u32_le().reinterpret_as_int()
guard size > 0 else { raise InvalidField("invalid datatype size") }
let mut signed = if class == 0 { (bit_field_0 & 0x08) != 0 } else { false }
let mut little_endian = (bit_field_0 & 0x01) == 0
let mut vlen_is_string = false
let enum_members : Array[EnumMember] = []
let compound_members : Array[CompoundMember] = []
let base_types : Array[Datatype] = []
let array_dimensions : Array[UInt64] = []
let mut bit_offset = 0
let mut bit_precision = 0
let mut float_sign_position = 0
let mut float_exponent_position = 0
let mut float_exponent_size = 0
let mut float_mantissa_position = 0
let mut float_mantissa_size = 0
let mut float_exponent_bias = 0
let mut float_normalization = 0
let mut string_padding = 0
let mut string_charset = 0
let mut opaque_tag = ""
let mut reference_kind = 0
if class == 0 || class == 1 {
guard version >= 1 && version <= 3 else {
raise UnsupportedFeature("unsupported numeric datatype version")
}
let offset = reader.read_u16_le().to_int()
let precision = reader.read_u16_le().to_int()
bit_offset = offset
bit_precision = precision
if class == 0 {
guard size >= 1 && size <= 8 else {
raise UnsupportedFeature("integer width is outside 1..8 bytes")
}
guard (bit_field_0 & 0xF0) == 0 && bit_field_1 == 0 && bit_field_2 == 0 else {
raise UnsupportedFeature("integer datatype flags are not supported")
}
guard precision > 0 && precision <= 64 && offset + precision <= size * 8 else {
raise InvalidField("invalid integer offset or precision")
}
} else {
let exponent_position = reader.read_u8().to_int()
let exponent_size = reader.read_u8().to_int()
let mantissa_position = reader.read_u8().to_int()
let mantissa_size = reader.read_u8().to_int()
let exponent_bias = reader.read_u32_le()
float_normalization = (bit_field_0 >> 4) & 0x03
guard size >= 1 &&
size <= 8 &&
precision > 0 &&
precision <= 64 &&
offset + precision <= size * 8 &&
(bit_field_0 & 0xC0) == 0 &&
bit_field_2 == 0 else {
raise InvalidField("invalid floating-point layout")
}
guard float_normalization == 2 else {
raise UnsupportedFeature(
"floating-point normalization other than implied MSB",
)
}
guard exponent_size > 0 &&
exponent_size <= 32 &&
mantissa_size >= 0 &&
mantissa_size <= 64 &&
bit_field_1 >= 0 &&
bit_field_1 < precision &&
exponent_position >= 0 &&
exponent_position + exponent_size <= precision &&
mantissa_position >= 0 &&
mantissa_position + mantissa_size <= precision &&
exponent_bias <= 0x7FFFFFFFU else {
raise InvalidField("invalid floating-point field locations")
}
float_sign_position = bit_field_1
float_exponent_position = exponent_position
float_exponent_size = exponent_size
float_mantissa_position = mantissa_position
float_mantissa_size = mantissa_size
float_exponent_bias = exponent_bias.reinterpret_as_int()
}
} else if class == 2 {
guard version >= 1 &&
version <= 3 &&
(bit_field_0 & 0xFE) == 0 &&
bit_field_1 == 0 &&
bit_field_2 == 0 &&
(size == 1 || size == 2 || size == 4 || size == 8) else {
raise UnsupportedFeature("unsupported time datatype layout")
}
bit_precision = reader.read_u16_le().to_int()
guard bit_precision > 0 && bit_precision <= size * 8 else {
raise InvalidField("invalid time datatype precision")
}
} else if class == 3 {
string_padding = bit_field_0 & 0x0F
string_charset = (bit_field_0 >> 4) & 0x0F
guard version >= 1 &&
version <= 3 &&
string_padding <= 2 &&
string_charset <= 1 &&
bit_field_1 == 0 &&
bit_field_2 == 0 else {
raise UnsupportedFeature("unsupported string datatype layout")
}
} else if class == 4 {
guard version >= 1 &&
version <= 3 &&
(bit_field_0 & 0xF8) == 0 &&
bit_field_1 == 0 &&
bit_field_2 == 0 &&
(size == 1 || size == 2 || size == 4 || size == 8) else {
raise UnsupportedFeature("unsupported bitfield datatype layout")
}
bit_offset = reader.read_u16_le().to_int()
bit_precision = reader.read_u16_le().to_int()
guard bit_precision > 0 && bit_offset + bit_precision <= size * 8 else {
raise InvalidField("invalid bitfield offset or precision")
}
} else if class == 5 {
guard version >= 1 && version <= 3 && bit_field_1 == 0 && bit_field_2 == 0 else {
raise UnsupportedFeature("unsupported opaque datatype version")
}
let tag_length = bit_field_0
if tag_length > 0 {
let padded = (tag_length + 7) / 8 * 8
let raw = reader.read_bytes(padded)
let mut end = 0
while end < tag_length && raw.unsafe_get(end) != b'\x00' {
guard raw.unsafe_get(end).to_int() < 128 else {
raise InvalidField("opaque datatype tag is not ASCII")
}
end = end + 1
}
guard end < tag_length else {
raise InvalidField("opaque datatype tag is not terminated")
}
opaque_tag = @utf8.decode(raw.get_view(start=0, end~).unwrap()) catch {
_ => raise InvalidField("invalid opaque datatype tag")
}
}
} else if class == 6 {
guard version >= 1 && (version <= 4 || version == 5) && bit_field_2 == 0 else {
raise UnsupportedFeature("unsupported compound datatype version")
}
let member_count = bit_field_0 | (bit_field_1 << 8)
guard member_count > 0 else {
raise InvalidField("compound datatype has no members")
}
for _ in 0.. 0 {
array_datatype(version, legacy_dimensions, parsed)
} else {
parsed
}
guard member_offset + member_type.size.to_uint64() <= size.to_uint64() else {
raise InvalidField("compound member extends beyond datatype size")
}
for existing in compound_members {
guard existing.name != name else {
raise InvalidField("duplicate compound member name")
}
}
compound_members.push({
name,
offset: member_offset.to_int(),
datatype: member_type,
})
}
} else if class == 7 {
reference_kind = bit_field_0 & 0x0F
if version < 4 {
guard version >= 1 &&
reference_kind <= 1 &&
(bit_field_0 & 0xF0) == 0 &&
bit_field_1 == 0 &&
bit_field_2 == 0 else {
raise UnsupportedFeature("unsupported reference datatype layout")
}
} else {
guard version == 4 &&
(reference_kind == 2 || reference_kind == 3 || reference_kind == 4) &&
bit_field_0 >> 4 == 1 &&
bit_field_1 == 0 &&
bit_field_2 == 0 else {
raise UnsupportedFeature("unsupported reference datatype layout")
}
}
} else if class == 8 {
guard version >= 1 && version <= 4 && bit_field_2 == 0 else {
raise UnsupportedFeature("unsupported enum datatype version")
}
let member_count = bit_field_0 | (bit_field_1 << 8)
guard member_count > 0 else {
raise InvalidField("enum datatype has no members")
}
let base = parse_datatype_reader(reader, depth + 1)
guard base.class == 0 else {
raise UnsupportedFeature("enum base type is not an integer")
}
guard base.size == size else {
raise InvalidField("enum datatype size does not match its base type")
}
signed = base.signed
little_endian = base.little_endian
base_types.push(base)
let names : Array[String] = []
for _ in 0..> 4) & 0x0F
string_charset = bit_field_1 & 0x0F
guard version >= 1 &&
version <= 4 &&
(kind == 0 || kind == 1) &&
(bit_field_1 & 0xF0) == 0 &&
bit_field_2 == 0 &&
(
(kind == 0 && string_padding == 0 && string_charset == 0) ||
(kind == 1 && string_padding <= 2 && string_charset <= 1)
) else {
raise UnsupportedFeature("unsupported variable-length datatype layout")
}
base_types.push(parse_datatype_reader(reader, depth + 1))
} else if class == 10 {
guard version >= 2 &&
version <= 5 &&
bit_field_0 == 0 &&
bit_field_1 == 0 &&
bit_field_2 == 0 else {
raise UnsupportedFeature("unsupported array datatype version")
}
let rank = reader.read_u8().to_int()
guard rank > 0 && rank <= 32 else {
raise InvalidField("invalid array datatype rank")
}
if version == 2 {
guard reader.read_bytes(3) == b"\x00\x00\x00" else {
raise InvalidField("invalid array datatype reserved bytes")
}
}
for _ in 0.. Datatype raise Hdf5Error {
parse_datatype_reader(Reader::new(data), 0)
}
///|
fn parse_datatype(message : HeaderMessage) -> Datatype raise Hdf5Error {
guard message.msg_type == msg_datatype else {
raise InvalidField("message is not a datatype message")
}
parse_datatype_data(message.data)
}