format: read the superblock extension and cache image over Storage
read_superblock_extension_in, cache_image_state_in, CacheImage::decode_in and CacheImage::block_in take &dyn Storage (whose length is the end of file); the image block is one bounded read. The &[u8] functions are wrappers; applying an image in place still needs the bytes in memory. New test: extension messages, a cache image and a corrupt one decode to the same results through a read_at-only CountingStorage. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -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<Option<SuperblockExtension>, 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<Option<SuperblockExtension>, 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, FormatError> {
|
||||
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<Self, FormatError> {
|
||||
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<Cow<'a, [u8]>, 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<Cow<'_, [u8]>, 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<CacheImageState, FormatError> {
|
||||
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<CacheImageState, FormatError> {
|
||||
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<u8> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user