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.
|
//! file is never copied whole.
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::{collections::BTreeSet, vec::Vec};
|
use alloc::{borrow::Cow, collections::BTreeSet, vec::Vec};
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::collections::BTreeSet;
|
use std::{borrow::Cow, collections::BTreeSet};
|
||||||
|
|
||||||
use crate::error::FormatError;
|
use crate::error::FormatError;
|
||||||
use crate::message_type::MessageType;
|
use crate::message_type::MessageType;
|
||||||
use crate::object_header::ObjectHeader;
|
use crate::object_header::ObjectHeader;
|
||||||
|
use crate::storage::{Storage, read_exact_at};
|
||||||
use crate::superblock::Superblock;
|
use crate::superblock::Superblock;
|
||||||
|
|
||||||
/// Message type of the File Space Info message.
|
/// Message type of the File Space Info message.
|
||||||
@@ -169,6 +170,15 @@ impl<'a> Cursor<'a> {
|
|||||||
pub fn read_superblock_extension(
|
pub fn read_superblock_extension(
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
sb: &Superblock,
|
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> {
|
) -> Result<Option<SuperblockExtension>, FormatError> {
|
||||||
let os = sb.offset_size;
|
let os = sb.offset_size;
|
||||||
let ls = sb.length_size;
|
let ls = sb.length_size;
|
||||||
@@ -181,8 +191,8 @@ pub fn read_superblock_extension(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
let addr = usize::try_from(addr).map_err(|_| ext_err("address out of range"))?;
|
let addr = usize::try_from(addr).map_err(|_| ext_err("address out of range"))?;
|
||||||
let header = ObjectHeader::parse(data, addr, os, ls)?;
|
let header = ObjectHeader::parse_in(file, addr as u64, os, ls)?;
|
||||||
let eoa = data.len() as u64;
|
let eoa = file.len();
|
||||||
|
|
||||||
let mut ext = SuperblockExtension::default();
|
let mut ext = SuperblockExtension::default();
|
||||||
for msg in &header.messages {
|
for msg in &header.messages {
|
||||||
@@ -340,12 +350,21 @@ impl CacheImage {
|
|||||||
data: &[u8],
|
data: &[u8],
|
||||||
location: CacheImageLocation,
|
location: CacheImageLocation,
|
||||||
sb: &Superblock,
|
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> {
|
) -> Result<Self, FormatError> {
|
||||||
let (offset_size, length_size) = (sb.offset_size, sb.length_size);
|
let (offset_size, length_size) = (sb.offset_size, sb.length_size);
|
||||||
let bad = FormatError::InvalidCacheImage;
|
let bad = FormatError::InvalidCacheImage;
|
||||||
let block = image_block(data, location)?;
|
let block = image_block_in(file, location)?;
|
||||||
let eoa = data.len() as u64;
|
let eoa = file.len();
|
||||||
let mut c = Cursor::new(block, bad(RAN_OFF));
|
let mut c = Cursor::new(&block, bad(RAN_OFF));
|
||||||
|
|
||||||
// Header: signature, version, flags, image data length, entry count.
|
// Header: signature, version, flags, image data length, entry count.
|
||||||
if c.take(4)? != MDCI_SIGNATURE {
|
if c.take(4)? != MDCI_SIGNATURE {
|
||||||
@@ -463,6 +482,11 @@ impl CacheImage {
|
|||||||
image_block(data, self.location)
|
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
|
/// 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
|
/// on (as long as the `data` the image was decoded from), taking the
|
||||||
/// entries from `block` (the image block, see [`Self::block`]). `block`
|
/// 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> {
|
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 bad = FormatError::InvalidCacheImage;
|
||||||
let start = usize::try_from(location.address).map_err(|_| bad("address out of range"))?;
|
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"))?;
|
let len = usize::try_from(location.length).map_err(|_| bad("length out of range"))?;
|
||||||
start
|
start
|
||||||
.checked_add(len)
|
.checked_add(len)
|
||||||
.and_then(|end| data.get(start..end))
|
.filter(|&end| end as u64 <= file_len)
|
||||||
.ok_or(bad("image block extends past the end of the file"))
|
.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
|
/// 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
|
/// ([`CacheImage::decode`]). `data` is the file from the superblock on, up
|
||||||
/// to its recorded end of file.
|
/// to its recorded end of file.
|
||||||
pub fn cache_image_state(data: &[u8], sb: &Superblock) -> Result<CacheImageState, FormatError> {
|
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 {
|
Some(SuperblockExtension {
|
||||||
cache_image: Some(location),
|
cache_image: Some(location),
|
||||||
..
|
..
|
||||||
}) => Ok(match CacheImage::decode(data, location, sb) {
|
}) => Ok(match CacheImage::decode_in(file, location, sb) {
|
||||||
Ok(image) => CacheImageState::Loaded(image),
|
Ok(image) => CacheImageState::Loaded(image),
|
||||||
Err(e) => CacheImageState::Unloadable(e),
|
Err(e) => CacheImageState::Unloadable(e),
|
||||||
}),
|
}),
|
||||||
@@ -566,7 +613,7 @@ mod tests {
|
|||||||
/// holds the given messages, padded to `len` bytes.
|
/// holds the given messages, padded to `len` bytes.
|
||||||
fn file_with_ext(messages: &[(u16, &[u8])], len: usize) -> Vec<u8> {
|
fn file_with_ext(messages: &[(u16, &[u8])], len: usize) -> Vec<u8> {
|
||||||
let mut body = Vec::new();
|
let mut body = Vec::new();
|
||||||
for (t, d) in messages {
|
for &(t, d) in messages {
|
||||||
let padded = d.len().div_ceil(8) * 8;
|
let padded = d.len().div_ceil(8) * 8;
|
||||||
body.extend_from_slice(&t.to_le_bytes());
|
body.extend_from_slice(&t.to_le_bytes());
|
||||||
body.extend_from_slice(&(padded as u16).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.
|
// An entry cannot be its own parent.
|
||||||
assert!(load(image_with_deps(&[(40, b"C", 1, Some(40))])).is_err());
|
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