#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldDescriptor {
pub name: String,
pub offset: usize,
pub size: usize,
pub type_class: TypeClass,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TypeClass {
Integer {
signed: bool,
},
Float,
Enum {
signed: bool,
size: usize,
members: Vec<(String, u64)>,
},
Compound {
size: usize,
fields: Vec<FieldDescriptor>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrimitiveType {
Integer { signed: bool, size: usize },
Float { size: usize },
}
pub unsafe trait H5Type: Copy + 'static {
fn type_size() -> usize;
fn has_padding() -> bool {
false
}
fn requires_validation() -> bool {
false
}
fn primitive_type() -> Option<PrimitiveType> {
None
}
fn validate_bytes(bytes: &[u8]) -> crate::Result<()> {
if bytes.len() == Self::type_size() {
Ok(())
} else {
Err(crate::Error::InvalidFormat(format!(
"byte count {} does not match element size {}",
bytes.len(),
Self::type_size()
)))
}
}
fn validate_byte_slice(bytes: &[u8]) -> crate::Result<()> {
let elem_size = Self::type_size();
if elem_size == 0 {
return Err(crate::Error::Other("zero-size type".into()));
}
if !bytes.len().is_multiple_of(elem_size) {
return Err(crate::Error::InvalidFormat(format!(
"byte count {} is not a multiple of element size {}",
bytes.len(),
elem_size
)));
}
for chunk in bytes.chunks_exact(elem_size) {
Self::validate_bytes(chunk)?;
}
Ok(())
}
fn visit_compound_fields<F>(_visitor: F) -> Option<()>
where
F: FnMut(FieldDescriptor),
{
None
}
fn compound_fields_into(out: &mut Vec<FieldDescriptor>) -> Option<()> {
let mut next = Vec::new();
let visited = Self::visit_compound_fields(|field| next.push(field));
if visited.is_some() {
*out = next;
} else {
out.clear();
}
visited
}
fn compound_fields() -> Option<Vec<FieldDescriptor>> {
let mut fields = Vec::new();
Self::compound_fields_into(&mut fields)?;
Some(fields)
}
fn visit_enum_members<F>(_visitor: F) -> Option<()>
where
F: FnMut(&str, u64),
{
None
}
fn enum_members_into(out: &mut Vec<(String, u64)>) -> Option<()> {
let mut next = Vec::new();
let visited = Self::visit_enum_members(|name, value| next.push((name.to_string(), value)));
if visited.is_some() {
*out = next;
} else {
out.clear();
}
visited
}
fn enum_members() -> Option<Vec<(String, u64)>> {
let mut members = Vec::new();
Self::enum_members_into(&mut members)?;
Some(members)
}
fn enum_base_type() -> Option<(bool, usize)> {
None
}
fn enum_members_u64_into(out: &mut Vec<(String, u64)>) -> Option<()> {
out.clear();
Self::visit_enum_members(|name, value| out.push((name.to_string(), value)))
}
}
macro_rules! impl_h5type_integer {
($($t:ty => $signed:literal),*) => {
$(
unsafe impl H5Type for $t {
fn type_size() -> usize { std::mem::size_of::<$t>() }
fn primitive_type() -> Option<PrimitiveType> {
Some(PrimitiveType::Integer {
signed: $signed,
size: std::mem::size_of::<$t>(),
})
}
}
)*
};
}
macro_rules! impl_h5type_float {
($($t:ty),*) => {
$(
unsafe impl H5Type for $t {
fn type_size() -> usize { std::mem::size_of::<$t>() }
fn primitive_type() -> Option<PrimitiveType> {
Some(PrimitiveType::Float {
size: std::mem::size_of::<$t>(),
})
}
}
)*
};
}
impl_h5type_integer!(
u8 => false,
u16 => false,
u32 => false,
u64 => false,
u128 => false,
i8 => true,
i16 => true,
i32 => true,
i64 => true,
i128 => true
);
impl_h5type_float!(f32, f64);
pub fn type_class_for<T: H5Type>() -> Option<TypeClass> {
if let Some((signed, size)) = T::enum_base_type() {
let mut members = Vec::new();
T::enum_members_u64_into(&mut members)?;
Some(TypeClass::Enum {
signed,
size,
members,
})
} else if let Some(primitive) = T::primitive_type() {
match primitive {
PrimitiveType::Integer { signed, .. } => Some(TypeClass::Integer { signed }),
PrimitiveType::Float { .. } => Some(TypeClass::Float),
}
} else {
T::compound_fields().map(|fields| TypeClass::Compound {
size: T::type_size(),
fields,
})
}
}
pub fn bytes_to_slice<T: H5Type>(bytes: &[u8]) -> crate::Result<&[T]> {
let elem_size = T::type_size();
if elem_size == 0 {
return Err(crate::Error::Other("zero-size type".into()));
}
if !bytes.len().is_multiple_of(elem_size) {
return Err(crate::Error::InvalidFormat(format!(
"byte count {} is not a multiple of element size {}",
bytes.len(),
elem_size
)));
}
let align = std::mem::align_of::<T>();
if !(bytes.as_ptr() as usize).is_multiple_of(align) {
return Err(crate::Error::Other(
"buffer alignment insufficient for type; use read() instead of read_scalar()".into(),
));
}
T::validate_byte_slice(bytes)?;
let count = bytes.len() / elem_size;
let ptr = bytes.as_ptr() as *const T;
Ok(unsafe { std::slice::from_raw_parts(ptr, count) })
}
pub fn slice_as_bytes_mut<T: H5Type>(values: &mut [T]) -> &mut [u8] {
let len = values
.len()
.checked_mul(T::type_size())
.expect("typed slice byte length overflow");
let ptr = values.as_mut_ptr() as *mut u8;
unsafe { std::slice::from_raw_parts_mut(ptr, len) }
}
pub fn slice_as_bytes_checked<T: H5Type>(values: &[T]) -> crate::Result<&[u8]> {
if T::has_padding() {
return Err(crate::Error::Unsupported(
"typed writes for H5Type values with padding are not supported".into(),
));
}
Ok(slice_as_bytes(values))
}
pub fn values_as_bytes_checked<T: H5Type>(
values: &[T],
) -> crate::Result<std::borrow::Cow<'_, [u8]>> {
if !T::has_padding() {
return Ok(std::borrow::Cow::Borrowed(slice_as_bytes(values)));
}
let elem_size = T::type_size();
let total_len = values
.len()
.checked_mul(elem_size)
.ok_or_else(|| crate::Error::InvalidFormat("typed slice byte length overflow".into()))?;
let fields = T::compound_fields().ok_or_else(|| {
crate::Error::Unsupported(
"typed writes for padded non-compound H5Type values are not supported".into(),
)
})?;
let mut out = vec![0u8; total_len];
for (index, value) in values.iter().enumerate() {
let src_base = value as *const T as *const u8;
let dst_base = index * elem_size;
copy_initialized_field_bytes(
&mut out,
dst_base,
src_base,
elem_size,
&fields,
"compound field",
)?;
}
Ok(std::borrow::Cow::Owned(out))
}
fn copy_initialized_field_bytes(
out: &mut [u8],
dst_base: usize,
src_base: *const u8,
elem_size: usize,
fields: &[FieldDescriptor],
context: &str,
) -> crate::Result<()> {
for field in fields {
let field_end = field
.offset
.checked_add(field.size)
.ok_or_else(|| crate::Error::InvalidFormat(format!("{context} range overflow")))?;
if field_end > elem_size {
return Err(crate::Error::InvalidFormat(format!(
"{context} '{}' ends at byte {field_end}, beyond element size {elem_size}",
field.name
)));
}
match &field.type_class {
TypeClass::Compound {
size,
fields: nested,
} => {
if field.size != *size {
return Err(crate::Error::InvalidFormat(format!(
"nested compound field '{}' size metadata mismatch: field size {}, type size {}",
field.name, field.size, size
)));
}
copy_initialized_field_bytes(
out,
dst_base + field.offset,
unsafe { src_base.add(field.offset) },
*size,
nested,
"nested compound field",
)?;
}
_ => {
let src =
unsafe { std::slice::from_raw_parts(src_base.add(field.offset), field.size) };
out[dst_base + field.offset..dst_base + field_end].copy_from_slice(src);
}
}
}
Ok(())
}
pub fn slice_as_bytes<T: H5Type>(values: &[T]) -> &[u8] {
assert!(
!T::has_padding(),
"typed writes for H5Type values with padding are not supported"
);
let len = values
.len()
.checked_mul(T::type_size())
.expect("typed slice byte length overflow");
let ptr = values.as_ptr() as *const u8;
unsafe { std::slice::from_raw_parts(ptr, len) }
}
pub fn bytes_to_vec<T: H5Type>(bytes: Vec<u8>) -> crate::Result<Vec<T>> {
let elem_size = T::type_size();
let actual_size = std::mem::size_of::<T>();
if elem_size == 0 {
return Err(crate::Error::Other("zero-size type".into()));
}
if elem_size != actual_size {
return Err(crate::Error::Other(format!(
"H5Type size {} does not match Rust type size {}",
elem_size, actual_size
)));
}
if !bytes.len().is_multiple_of(elem_size) {
return Err(crate::Error::InvalidFormat(format!(
"byte count {} is not a multiple of element size {}",
bytes.len(),
elem_size
)));
}
let count = bytes.len() / elem_size;
let align = std::mem::align_of::<T>();
if elem_size == 1 && align == 1 {
T::validate_byte_slice(&bytes)?;
let mut bytes = std::mem::ManuallyDrop::new(bytes);
let ptr = bytes.as_mut_ptr() as *mut T;
let cap = bytes.capacity();
Ok(unsafe { Vec::from_raw_parts(ptr, count, cap) })
} else {
T::validate_byte_slice(&bytes)?;
let mut result = Vec::<T>::with_capacity(count);
unsafe {
std::ptr::copy_nonoverlapping(
bytes.as_ptr(),
result.as_mut_ptr() as *mut u8,
bytes.len(),
);
result.set_len(count);
}
Ok(result)
}
}