diff --git a/crates/clawhdf5-format/src/superblock_ext.rs b/crates/clawhdf5-format/src/superblock_ext.rs index e1d1cf3..40efd5c 100644 --- a/crates/clawhdf5-format/src/superblock_ext.rs +++ b/crates/clawhdf5-format/src/superblock_ext.rs @@ -21,13 +21,14 @@ //! file is never copied whole. #[cfg(not(feature = "std"))] -use alloc::{collections::BTreeSet, vec::Vec}; +use alloc::{borrow::Cow, collections::BTreeSet, vec::Vec}; #[cfg(feature = "std")] -use std::collections::BTreeSet; +use std::{borrow::Cow, collections::BTreeSet}; use crate::error::FormatError; use crate::message_type::MessageType; use crate::object_header::ObjectHeader; +use crate::storage::{Storage, read_exact_at}; use crate::superblock::Superblock; /// Message type of the File Space Info message. @@ -169,6 +170,15 @@ impl<'a> Cursor<'a> { pub fn read_superblock_extension( data: &[u8], sb: &Superblock, +) -> Result, FormatError> { + read_superblock_extension_in(&data, sb) +} + +/// [`read_superblock_extension`] over any [`Storage`]; its length is the +/// end of file. +pub fn read_superblock_extension_in( + file: &dyn Storage, + sb: &Superblock, ) -> Result, FormatError> { let os = sb.offset_size; let ls = sb.length_size; @@ -181,8 +191,8 @@ pub fn read_superblock_extension( return Ok(None); }; let addr = usize::try_from(addr).map_err(|_| ext_err("address out of range"))?; - let header = ObjectHeader::parse(data, addr, os, ls)?; - let eoa = data.len() as u64; + let header = ObjectHeader::parse_in(file, addr as u64, os, ls)?; + let eoa = file.len(); let mut ext = SuperblockExtension::default(); for msg in &header.messages { @@ -340,12 +350,21 @@ impl CacheImage { data: &[u8], location: CacheImageLocation, sb: &Superblock, + ) -> Result { + Self::decode_in(&data, location, sb) + } + + /// [`Self::decode`] over any [`Storage`]: one read of the image block. + pub fn decode_in( + file: &dyn Storage, + location: CacheImageLocation, + sb: &Superblock, ) -> Result { let (offset_size, length_size) = (sb.offset_size, sb.length_size); let bad = FormatError::InvalidCacheImage; - let block = image_block(data, location)?; - let eoa = data.len() as u64; - let mut c = Cursor::new(block, bad(RAN_OFF)); + let block = image_block_in(file, location)?; + let eoa = file.len(); + let mut c = Cursor::new(&block, bad(RAN_OFF)); // Header: signature, version, flags, image data length, entry count. if c.take(4)? != MDCI_SIGNATURE { @@ -463,6 +482,11 @@ impl CacheImage { image_block(data, self.location) } + /// [`Self::block`] over any [`Storage`]. + pub fn block_in<'a>(&self, file: &'a dyn Storage) -> Result, FormatError> { + image_block_in(file, self.location) + } + /// Write every entry over `dst`, the file's bytes from the superblock /// on (as long as the `data` the image was decoded from), taking the /// entries from `block` (the image block, see [`Self::block`]). `block` @@ -483,13 +507,28 @@ impl CacheImage { } fn image_block(data: &[u8], location: CacheImageLocation) -> Result<&[u8], FormatError> { + let (start, len) = image_block_range(data.len() as u64, location)?; + Ok(&data[start as usize..start as usize + len]) +} + +fn image_block_in( + file: &dyn Storage, + location: CacheImageLocation, +) -> Result, FormatError> { + let (start, len) = image_block_range(file.len(), location)?; + read_exact_at(file, start, len) +} + +/// Where the image block is, checked against a file of `file_len` bytes. +fn image_block_range(file_len: u64, location: CacheImageLocation) -> Result<(u64, usize), FormatError> { let bad = FormatError::InvalidCacheImage; let start = usize::try_from(location.address).map_err(|_| bad("address out of range"))?; let len = usize::try_from(location.length).map_err(|_| bad("length out of range"))?; start .checked_add(len) - .and_then(|end| data.get(start..end)) - .ok_or(bad("image block extends past the end of the file")) + .filter(|&end| end as u64 <= file_len) + .ok_or(bad("image block extends past the end of the file"))?; + Ok((start as u64, len)) } /// What an opener must do before reading a file's metadata: check the @@ -498,11 +537,19 @@ fn image_block(data: &[u8], location: CacheImageLocation) -> Result<&[u8], Forma /// ([`CacheImage::decode`]). `data` is the file from the superblock on, up /// to its recorded end of file. pub fn cache_image_state(data: &[u8], sb: &Superblock) -> Result { - match read_superblock_extension(data, sb)? { + cache_image_state_in(&data, sb) +} + +/// [`cache_image_state`] over any [`Storage`]. +pub fn cache_image_state_in( + file: &dyn Storage, + sb: &Superblock, +) -> Result { + match read_superblock_extension_in(file, sb)? { Some(SuperblockExtension { cache_image: Some(location), .. - }) => Ok(match CacheImage::decode(data, location, sb) { + }) => Ok(match CacheImage::decode_in(file, location, sb) { Ok(image) => CacheImageState::Loaded(image), Err(e) => CacheImageState::Unloadable(e), }), @@ -566,7 +613,7 @@ mod tests { /// holds the given messages, padded to `len` bytes. fn file_with_ext(messages: &[(u16, &[u8])], len: usize) -> Vec { let mut body = Vec::new(); - for (t, d) in messages { + for &(t, d) in messages { let padded = d.len().div_ceil(8) * 8; body.extend_from_slice(&t.to_le_bytes()); body.extend_from_slice(&(padded as u16).to_le_bytes()); @@ -809,4 +856,37 @@ mod tests { // An entry cannot be its own parent. assert!(load(image_with_deps(&[(40, b"C", 1, Some(40))])).is_err()); } + + /// The extension and cache image decode identically through a + /// `read_at`-only storage, errors included. + #[test] + fn storage_parse_matches_slice_parse() { + use crate::storage::CountingStorage; + let img = image(&[(16, b"HEADER"), (40, b"NODE")]); + let mut with_image = file_with_ext(&[(MSG_MDCI, &mdci(256, img.len() as u64))], 256); + with_image.extend_from_slice(&img); + let mut bad_image = with_image.clone(); + bad_image[256] = b'X'; + let files = [ + file_with_ext(&[(MSG_FSINFO, &fsinfo_v1(4096, false, 0))], 256), + file_with_ext(&[(MSG_FSINFO, &fsinfo_v1(256, false, 0))], 256), + file_with_ext(&[(MSG_MDCI, &mdci(128, 64))], 192), + file_with_ext(&[(MSG_MDCI, &mdci(0x10100, 0x1000_0000))], 2565), + file_with_ext(&[(MSG_FSINFO, &fsinfo_v1(4096, true, 12))], 60), + with_image, + bad_image, + ]; + for f in files { + let storage = CountingStorage::new(f.clone()); + let sb = sb_v2(48); + assert_eq!( + read_superblock_extension_in(&storage, &sb), + read_superblock_extension(&f, &sb) + ); + assert_eq!(cache_image_state_in(&storage, &sb), cache_image_state(&f, &sb)); + if let Ok(CacheImageState::Loaded(image)) = cache_image_state(&f, &sb) { + assert_eq!(&*image.block_in(&storage).unwrap(), image.block(&f).unwrap()); + } + } + } }