use std::io;
use crate::entry::Block;
use crate::footer::Footer;
fn invalid(msg: &'static str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, msg)
}
fn in_bounds(off: u64, len: u64, file_size: u64) -> bool {
off.checked_add(len).is_some_and(|end| end <= file_size)
}
pub fn validate_footer(footer: &Footer, file_size: u64) -> io::Result<()> {
if !in_bounds(
footer.manifest_offset,
footer.manifest_compressed,
file_size,
) {
return Err(invalid("manifest region out of bounds"));
}
if !in_bounds(footer.payload_offset, footer.payload_size, file_size) {
return Err(invalid("payload region out of bounds"));
}
if footer.manifest_original > file_size.saturating_mul(MAX_MANIFEST_EXPANSION) {
return Err(invalid("manifest expands implausibly"));
}
if footer.dict_size > 0 && !in_bounds(footer.dict_offset, footer.dict_size as u64, file_size) {
return Err(invalid("dictionary region out of bounds"));
}
Ok(())
}
const MAX_MANIFEST_EXPANSION: u64 = 1024;
pub fn block_extent(footer: &Footer, block: &Block) -> io::Result<(u64, usize)> {
let abs = footer
.payload_offset
.checked_add(block.payload_offset)
.ok_or_else(|| invalid("payload offset overflow"))?;
let payload_end = footer
.payload_offset
.checked_add(footer.payload_size)
.ok_or_else(|| invalid("payload region overflow"))?;
if !in_bounds(abs, block.compressed_size, payload_end) {
return Err(invalid("block extends past the payload region"));
}
let len = usize::try_from(block.compressed_size)
.map_err(|_| invalid("block larger than this address space"))?;
Ok((abs, len))
}
pub fn block_original_size(block: &Block) -> io::Result<usize> {
if block.original_size > MAX_BLOCK_ORIGINAL {
return Err(invalid("block decompresses to an implausible size"));
}
usize::try_from(block.original_size)
.map_err(|_| invalid("block larger than this address space"))
}
const MAX_BLOCK_ORIGINAL: u64 = 256 * 1024 * 1024;
#[cfg(test)]
mod tests {
use super::*;
use crate::footer::Flags;
fn footer() -> Footer {
Footer {
format_version: 1,
flags: Flags::empty(),
manifest_offset: 100,
manifest_compressed: 50,
manifest_original: 200,
payload_offset: 200,
payload_size: 300,
dict_offset: 0,
dict_size: 0,
manifest_checksum: [0; 4],
}
}
fn block(offset: u64, compressed: u64, original: u64) -> Block {
Block {
payload_offset: offset,
compressed_size: compressed,
original_size: original,
content_hash: [0u8; 32],
}
}
#[test]
fn well_formed_footer_passes() {
assert!(validate_footer(&footer(), 1000).is_ok());
}
#[test]
fn regions_past_the_file_are_rejected() {
let mut f = footer();
f.manifest_compressed = u64::MAX;
assert!(validate_footer(&f, 1000).is_err());
let mut f = footer();
f.payload_size = 10_000;
assert!(validate_footer(&f, 1000).is_err());
let mut f = footer();
f.dict_size = 900;
f.dict_offset = 500;
assert!(validate_footer(&f, 1000).is_err());
}
#[test]
fn overflowing_offsets_are_rejected() {
let mut f = footer();
f.manifest_offset = u64::MAX;
f.manifest_compressed = 1;
assert!(validate_footer(&f, 1000).is_err());
}
#[test]
fn absurd_manifest_expansion_is_rejected() {
let mut f = footer();
f.manifest_original = u64::MAX;
assert!(validate_footer(&f, 1000).is_err());
}
#[test]
fn block_within_payload_resolves() {
let f = footer();
let (abs, len) = block_extent(&f, &block(10, 20, 40)).unwrap();
assert_eq!((abs, len), (210, 20));
}
#[test]
fn block_past_the_payload_is_rejected() {
let f = footer();
assert!(block_extent(&f, &block(290, 20, 40)).is_err());
assert!(block_extent(&f, &block(u64::MAX, 1, 1)).is_err());
assert!(block_extent(&f, &block(0, u64::MAX, 1)).is_err());
}
#[test]
fn absurd_block_size_is_rejected() {
assert!(block_original_size(&block(0, 10, 40)).is_ok());
assert!(block_original_size(&block(0, 1, u64::MAX)).is_err());
assert!(block_original_size(&block(0, 4096, 4096)).is_ok());
}
#[test]
fn extreme_but_real_compression_ratios_are_accepted() {
let compressed = 30u64;
let original = 256 * 1024u64;
assert!(original / compressed > 4096, "fixture must be extreme");
assert!(block_original_size(&block(0, compressed, original)).is_ok());
}
}