diff --git a/crates/clawhdf5-format/src/addr.rs b/crates/clawhdf5-format/src/addr.rs new file mode 100644 index 0000000..ec25e3d --- /dev/null +++ b/crates/clawhdf5-format/src/addr.rs @@ -0,0 +1,59 @@ +//! File address and length → in-memory index conversion. +//! +//! HDF5 addresses and lengths are 64-bit; the file is parsed through a +//! `&[u8]` indexed by `usize`. On a 64-bit target every `u64` fits, but on a +//! 32-bit one (`wasm32`, `i686`, `thumbv7em`) an address past `usize::MAX` +//! used to be truncated by an `as usize` cast — silently pointing at another +//! part of the file — or to panic. [`to_usize`] is the one conversion the +//! parsers use instead: such an address is a clean +//! [`FormatError::Overflow`]. It cannot be inside the data anyway: no slice +//! is longer than `isize::MAX` bytes. + +#[cfg(not(feature = "std"))] +use alloc::format; + +use crate::error::FormatError; + +/// A file address, offset or length from the file as a `usize` index. +/// +/// Fails with [`FormatError::Overflow`] when the value does not fit this +/// platform's `usize` (only possible on targets narrower than 64 bits). +#[inline] +pub fn to_usize(value: u64) -> Result { + usize::try_from(value).map_err(|_| too_large(value)) +} + +#[cold] +#[inline(never)] +fn too_large(value: u64) -> FormatError { + FormatError::Overflow(format!( + "file address or length {value:#x} exceeds this platform's address space" + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn values_that_fit_convert_exactly() { + assert_eq!(to_usize(0), Ok(0)); + assert_eq!(to_usize(0x1234), Ok(0x1234)); + assert_eq!(to_usize(usize::MAX as u64), Ok(usize::MAX)); + } + + #[test] + fn values_past_usize_max_are_an_error_not_truncated() { + // Only reachable where usize is narrower than u64; on a 64-bit host + // every u64 fits, which the first branch checks instead. + if let Some(past) = (usize::MAX as u64).checked_add(1) { + let err = to_usize(past).unwrap_err(); + assert!(matches!(err, FormatError::Overflow(_)), "{err:?}"); + // The value an `as usize` cast would have produced is not returned. + assert!(to_usize(u64::MAX).is_err()); + assert!(to_usize(past + 0x10).is_err()); + } else { + assert_eq!(to_usize(u64::MAX), Ok(u64::MAX as usize)); + } + } +} diff --git a/crates/clawhdf5-format/src/lib.rs b/crates/clawhdf5-format/src/lib.rs index a197c9f..2737c9b 100644 --- a/crates/clawhdf5-format/src/lib.rs +++ b/crates/clawhdf5-format/src/lib.rs @@ -57,6 +57,7 @@ #[cfg(not(feature = "std"))] extern crate alloc; +pub mod addr; pub mod attribute; pub mod attribute_info; pub mod btree_v1;