//! HDF5 file signature (magic bytes) detection. use crate::error::FormatError; /// The 8-byte HDF5 magic signature. pub const HDF5_SIGNATURE: [u8; 8] = [0x89, b'H', b'D', b'F', b'\r', b'\n', 0x1A, b'\n']; /// Search for the HDF5 signature at valid offsets. /// /// The HDF5 spec says the signature can appear at offset 0, 512, 1024, 2048, 4096, ... /// (powers of two starting at 512, plus offset 0). /// /// Returns the byte offset where the signature was found. /// /// A non-zero offset means the file starts with a *user block*, and every /// address inside the file is relative to the superblock's position, not to /// byte 0 (libhdf5 uses the signature's position as the base address even /// when the stored base-address field disagrees). The parsers in this crate /// take addresses as indices into `file_data`, so they must be handed the /// bytes from the signature on — use [`split_user_block`]. [`Superblock::parse`] /// refuses a non-zero offset for this reason. /// /// [`Superblock::parse`]: crate::superblock::Superblock::parse pub fn find_signature(data: &[u8]) -> Result { // Check offset 0 if data.len() >= 8 && data[..8] == HDF5_SIGNATURE { return Ok(0); } // Check powers of 2 starting at 512 let mut offset = 512; while offset + 8 <= data.len() { if data[offset..offset + 8] == HDF5_SIGNATURE { return Ok(offset); } offset *= 2; } Err(FormatError::SignatureNotFound) } /// Split a file into its user block and its HDF5 bytes. /// /// Returns `(user_block, hdf5)`: `user_block` is everything before the /// superblock signature (empty for most files) and `hdf5` is the rest, in /// which every HDF5 address is a plain index. Pass `hdf5` as `file_data` to /// every parser in this crate, and parse the superblock at offset 0 of it. pub fn split_user_block(data: &[u8]) -> Result<(&[u8], &[u8]), FormatError> { let offset = find_signature(data)?; Ok(data.split_at(offset)) } #[cfg(test)] mod tests { use super::*; #[test] fn signature_at_offset_0() { let mut data = vec![0u8; 64]; data[..8].copy_from_slice(&HDF5_SIGNATURE); assert_eq!(find_signature(&data), Ok(0)); } #[test] fn signature_at_offset_512() { let mut data = vec![0u8; 1024]; data[512..520].copy_from_slice(&HDF5_SIGNATURE); assert_eq!(find_signature(&data), Ok(512)); } #[test] fn signature_at_offset_1024() { let mut data = vec![0u8; 2048]; data[1024..1032].copy_from_slice(&HDF5_SIGNATURE); assert_eq!(find_signature(&data), Ok(1024)); } #[test] fn signature_at_offset_2048() { let mut data = vec![0u8; 4096]; data[2048..2056].copy_from_slice(&HDF5_SIGNATURE); assert_eq!(find_signature(&data), Ok(2048)); } #[test] fn signature_not_found() { let data = vec![0u8; 8192]; assert_eq!(find_signature(&data), Err(FormatError::SignatureNotFound)); } #[test] fn signature_not_found_empty() { assert_eq!(find_signature(&[]), Err(FormatError::SignatureNotFound)); } #[test] fn signature_not_found_too_short() { assert_eq!( find_signature(&[0x89, b'H', b'D']), Err(FormatError::SignatureNotFound) ); } #[test] fn signature_at_non_power_of_two_not_found() { // Signature at offset 100 should NOT be found let mut data = vec![0u8; 1024]; data[100..108].copy_from_slice(&HDF5_SIGNATURE); assert_eq!(find_signature(&data), Err(FormatError::SignatureNotFound)); } #[test] fn split_user_block_rebases_at_the_signature() { let mut data = vec![7u8; 1024]; data[512..520].copy_from_slice(&HDF5_SIGNATURE); let (ub, hdf5) = split_user_block(&data).unwrap(); assert_eq!(ub.len(), 512); assert_eq!(hdf5.len(), 512); assert_eq!(&hdf5[..8], &HDF5_SIGNATURE); data[..8].copy_from_slice(&HDF5_SIGNATURE); let (ub, hdf5) = split_user_block(&data).unwrap(); assert!(ub.is_empty()); assert_eq!(hdf5.len(), 1024); } #[test] fn signature_prefers_earliest() { // Signature at both 0 and 512, should return 0 let mut data = vec![0u8; 1024]; data[..8].copy_from_slice(&HDF5_SIGNATURE); data[512..520].copy_from_slice(&HDF5_SIGNATURE); assert_eq!(find_signature(&data), Ok(0)); } }