diff --git a/crates/clawhdf5-format/src/symbol_table.rs b/crates/clawhdf5-format/src/symbol_table.rs index 9809c75..8fb4453 100644 --- a/crates/clawhdf5-format/src/symbol_table.rs +++ b/crates/clawhdf5-format/src/symbol_table.rs @@ -4,6 +4,7 @@ use alloc::vec::Vec; use crate::error::FormatError; +use crate::storage::{Storage, len_usize, read_exact_at}; /// Symbol Table message (type 0x0011) found in v1 group object headers. #[derive(Debug, Clone, PartialEq)] @@ -79,48 +80,53 @@ impl SymbolTableNode { offset: usize, offset_size: u8, ) -> Result { - // signature(4) + version(1) + reserved(1) + number_of_symbols(2) = 8 - if offset - .checked_add(8) - .is_none_or(|end| end > file_data.len()) - { - return Err(FormatError::UnexpectedEof { - expected: offset.saturating_add(8), - available: file_data.len(), - }); - } + Self::parse_in(&file_data, offset as u64, offset_size) + } - if &file_data[offset..offset + 4] != b"SNOD" { + /// [`Self::parse`] over any [`Storage`]: one read of the node's header, + /// one of its entries. + pub fn parse_in( + file: &dyn Storage, + offset: u64, + offset_size: u8, + ) -> Result { + let file_len = len_usize(file); + // signature(4) + version(1) + reserved(1) + number_of_symbols(2) = 8 + let header = read_exact_at(file, offset, 8)?; + + if &header[..4] != b"SNOD" { return Err(FormatError::InvalidSymbolTableNodeSignature); } - let version = file_data[offset + 4]; + let version = header[4]; if version != 1 { return Err(FormatError::InvalidSymbolTableNodeVersion(version)); } - let num_symbols = - u16::from_le_bytes([file_data[offset + 6], file_data[offset + 7]]) as usize; + let num_symbols = u16::from_le_bytes([header[6], header[7]]) as usize; let os = offset_size as usize; // Each entry: link_name_offset(os) + obj_hdr_addr(os) + cache_type(4) + reserved(4) + scratch(16) let entry_size = os + os + 4 + 4 + 16; - let entries_start = offset + 8; + // `offset + 8` fits: the header's read checked it. + let entries_start = offset as usize + 8; let needed = entries_start.checked_add(num_symbols * entry_size).ok_or( FormatError::UnexpectedEof { expected: usize::MAX, - available: file_data.len(), + available: file_len, }, )?; - if needed > file_data.len() { + if needed > file_len { return Err(FormatError::UnexpectedEof { expected: needed, - available: file_data.len(), + available: file_len, }); } + let body = read_exact_at(file, entries_start as u64, num_symbols * entry_size)?; + let file_data: &[u8] = &body; let mut entries = Vec::with_capacity(num_symbols); - let mut pos = entries_start; + let mut pos = 0usize; for _ in 0..num_symbols { let link_name_offset = read_offset(file_data, pos, offset_size)?; pos += os; @@ -256,4 +262,28 @@ mod tests { let result = SymbolTableNode::parse(&data, usize::MAX / 2, 8); assert!(result.is_err()); } + + /// Nodes, cut at every length and at an offset, parse identically + /// through a `read_at`-only storage. + #[test] + fn storage_parse_matches_slice_parse() { + use crate::storage::CountingStorage; + for os in [4u8, 8] { + let node = build_snod(&[(0, 0x100, 0), (8, 0x200, 1), (16, 0x300, 2)], os); + let mut bad = node.clone(); + bad[4] = 2; + for full in [node, bad] { + for at in [0usize, 7] { + for cut in 0..=full.len() { + let mut f = vec![0u8; at]; + f.extend_from_slice(&full[..cut]); + let storage = CountingStorage::new(f.clone()); + let want = SymbolTableNode::parse(&f, at, os); + let got = SymbolTableNode::parse_in(&storage, at as u64, os); + assert_eq!(format!("{got:?}"), format!("{want:?}")); + } + } + } + } + } }