format: parse the superblock over Storage
Superblock::parse_in(&dyn Storage, offset) reads one bounded window of 128 bytes (the largest superblock is 100) and runs the existing version parsers on it; parse and refresh_eof keep their &[u8] signatures as wrappers. No behaviour change: on a file longer than the window no bounds check can fail, and on a shorter one the window is the whole file. New test: every version and truncation parses to the same result through a read_at-only CountingStorage, in one read. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -7,6 +7,11 @@ use byteorder::{ByteOrder, LittleEndian};
|
||||
|
||||
use crate::error::FormatError;
|
||||
use crate::signature::HDF5_SIGNATURE;
|
||||
use crate::storage::{Storage, read_upto};
|
||||
|
||||
/// Bytes read to parse a superblock: more than the largest one (version 1
|
||||
/// with 8-byte offsets and lengths, 100 bytes).
|
||||
const SUPERBLOCK_READ_LEN: usize = 128;
|
||||
|
||||
/// Parsed HDF5 superblock (all versions).
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -161,7 +166,16 @@ impl Superblock {
|
||||
file_data: &[u8],
|
||||
signature_offset: usize,
|
||||
) -> Result<u64, FormatError> {
|
||||
let refreshed = Superblock::parse(file_data, signature_offset)?;
|
||||
self.refresh_eof_in(&file_data, signature_offset as u64)
|
||||
}
|
||||
|
||||
/// [`Self::refresh_eof`] over any [`Storage`].
|
||||
pub fn refresh_eof_in(
|
||||
&mut self,
|
||||
file: &dyn Storage,
|
||||
signature_offset: u64,
|
||||
) -> Result<u64, FormatError> {
|
||||
let refreshed = Superblock::parse_in(file, signature_offset)?;
|
||||
self.eof_address = refreshed.eof_address;
|
||||
self.consistency_flags = refreshed.consistency_flags;
|
||||
Ok(self.eof_address)
|
||||
@@ -219,15 +233,20 @@ impl Superblock {
|
||||
/// [`FormatError::UserBlockNotStripped`] because the addresses in the
|
||||
/// returned superblock would otherwise be applied to the wrong bytes.
|
||||
pub fn parse(data: &[u8], signature_offset: usize) -> Result<Superblock, FormatError> {
|
||||
if signature_offset != 0 {
|
||||
return Err(FormatError::UserBlockNotStripped(signature_offset as u64));
|
||||
Self::parse_in(&data, signature_offset as u64)
|
||||
}
|
||||
let d = data
|
||||
.get(signature_offset..)
|
||||
.ok_or(FormatError::UnexpectedEof {
|
||||
expected: signature_offset + 1,
|
||||
available: data.len(),
|
||||
})?;
|
||||
|
||||
/// [`Self::parse`] over any [`Storage`]: one read of the first
|
||||
/// [`SUPERBLOCK_READ_LEN`] bytes (fewer when the file is shorter, which
|
||||
/// is then refused with the same end-of-file errors as a short slice).
|
||||
pub fn parse_in(file: &dyn Storage, signature_offset: u64) -> Result<Superblock, FormatError> {
|
||||
if signature_offset != 0 {
|
||||
return Err(FormatError::UserBlockNotStripped(signature_offset));
|
||||
}
|
||||
// Every bounds check below needs at most 100 bytes, so on a longer
|
||||
// file none of them can fail and the window's length does not show.
|
||||
let window = read_upto(file, 0, SUPERBLOCK_READ_LEN)?;
|
||||
let d: &[u8] = &window;
|
||||
ensure_len(d, 9)?; // signature(8) + version(1)
|
||||
|
||||
// Verify signature
|
||||
@@ -894,4 +913,34 @@ mod tests {
|
||||
assert_eq!(parsed.version, 3);
|
||||
assert_eq!(parsed.page_size, None);
|
||||
}
|
||||
|
||||
/// Through a storage that serves only `read_at`, every version parses
|
||||
/// to the same superblock, and every truncation to the same error, as
|
||||
/// from a slice — in one read.
|
||||
#[test]
|
||||
fn parse_in_matches_slice_parse() {
|
||||
use crate::storage::CountingStorage;
|
||||
let mut files = vec![
|
||||
build_v0_bytes(8),
|
||||
build_v0_bytes(4),
|
||||
build_v1_bytes(8),
|
||||
build_v1_bytes(4),
|
||||
build_v2_bytes(8, 2),
|
||||
build_v2_bytes(4, 3),
|
||||
];
|
||||
for f in files.clone() {
|
||||
let mut long = f.clone();
|
||||
long.resize(4096, 0xAB);
|
||||
files.push(long);
|
||||
for cut in [0, 5, 9, 13, 20, 30, f.len() - 1] {
|
||||
files.push(f[..cut.min(f.len())].to_vec());
|
||||
}
|
||||
}
|
||||
for f in files {
|
||||
let want = Superblock::parse(&f, 0);
|
||||
let storage = CountingStorage::new(f.clone());
|
||||
assert_eq!(Superblock::parse_in(&storage, 0), want, "{} bytes", f.len());
|
||||
assert_eq!(storage.reads(), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user