From e45c4625b4c45cc62670f33cc444a2f1fe83b394 Mon Sep 17 00:00:00 2001 From: dev-null-undefined Date: Fri, 9 Oct 2026 23:46:44 +0200 Subject: [PATCH] LR-IMP-FORMATS: ARW6 container rejection tests and tile byte ranges --- crates/raw/src/vendor/arw6.rs | 84 +++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/crates/raw/src/vendor/arw6.rs b/crates/raw/src/vendor/arw6.rs index e7615c218..ee1bb65a7 100644 --- a/crates/raw/src/vendor/arw6.rs +++ b/crates/raw/src/vendor/arw6.rs @@ -91,6 +91,16 @@ pub(crate) fn parse_tile_table(strip: &[u8], width: usize, height: usize) -> Res Ok(tiles) } +/// Tile `i`'s bytes: from its offset to the next tile's offset (the last: the strip end). +pub(crate) fn tile_bytes<'a>(strip: &'a [u8], tiles: &[TileEntry], i: usize) -> Option<&'a [u8]> { + let start = tiles.get(i)?.offset; + let end = match tiles.get(i.checked_add(1)?) { + Some(next) => next.offset, + None => strip.len(), + }; + strip.get(start..end) +} + /// One of a tile's 13 streams (positions in 16-byte words from the tile start). #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct StreamRef { @@ -294,6 +304,80 @@ mod tests { assert!(parse_tile_header(&tile, 64, 50).is_err()); // VS != th / 2 } + fn put32(b: &mut [u8], at: usize, v: u32) { + b[at..at + 4].copy_from_slice(&v.to_le_bytes()); + } + + #[test] + fn tile_table_validation_cases() { + let (tile, _) = one_tile(64, 48, 0, Qis::ZERO); + let many = |n: usize| { + let tiles: Vec<_> = (0..n).map(|i| (64 * i, 0, 64, 48, tile.clone())).collect(); + strip_of(&arw6_file(&tiles, 64 * n, 48)).to_vec() + }; + let sixteen = many(16); + assert_eq!(parse_tile_table(&sixteen, 1024, 48).unwrap().len(), 16); + let mut seventeen = sixteen.clone(); + seventeen[0] = 17; + assert!(parse_tile_table(&seventeen, 1088, 48).is_err()); + + let two = strip_of(&arw6_file(&[(0, 0, 64, 48, tile.clone()), (64, 0, 64, 48, tile.clone())], 128, 48)).to_vec(); + assert_eq!(parse_tile_table(&two, 128, 48).unwrap().len(), 2); + let mut swapped = two.clone(); + let (a, b) = (two[8..16].to_vec(), two[32..40].to_vec()); + swapped[8..16].copy_from_slice(&b); + swapped[32..40].copy_from_slice(&a); + assert!(parse_tile_table(&swapped, 128, 48).is_err()); + let mut at_end = two.clone(); + at_end[32..40].copy_from_slice(&(two.len() as u64).to_le_bytes()); + assert!(parse_tile_table(&at_end, 128, 48).is_err()); + + let one = many(1); + let mut wide = one.clone(); + put32(&mut wide, 24, 72); // w / 2 = 36 is not a multiple of 8 + assert!(parse_tile_table(&wide, 72, 48).is_err()); + let mut huge = one.clone(); + put32(&mut huge, 24, 1 << 20); + put32(&mut huge, 28, 1 << 20); + assert!(matches!(parse_tile_table(&huge, 1 << 20, 1 << 20), Err(RawError::Limit(_)))); + } + + #[test] + fn tile_bytes_span_to_the_next_offset() { + let (tile, _) = one_tile(64, 48, 0, Qis::ZERO); + let file = arw6_file(&[(0, 0, 64, 48, tile.clone()), (64, 0, 64, 48, tile.clone())], 128, 48); + let strip = strip_of(&file); + let tiles = parse_tile_table(strip, 128, 48).unwrap(); + let (t0, t1) = (tile_bytes(strip, &tiles, 0).unwrap(), tile_bytes(strip, &tiles, 1).unwrap()); + assert_eq!(t0.len(), tiles[1].offset - tiles[0].offset); + assert_eq!(t0, &tile[..]); + assert_eq!(t1.as_ptr() as usize + t1.len(), strip.as_ptr() as usize + strip.len()); + assert!(tile_bytes(strip, &tiles, 2).is_none()); + } + + #[test] + fn tile_header_and_stream_validation_cases() { + let (tile, _) = one_tile(64, 48, 0, Qis::REAL); + let h = parse_tile_header(&tile, 64, 48).unwrap(); + let mut two_streams = tile.clone(); + two_streams[3 * 16] = 2; + assert!(parse_tile_header(&two_streams, 64, 48).is_err()); + let mut big = tile.clone(); + big[3 * 16 + 1..3 * 16 + 4].copy_from_slice(&[0xff; 3]); // stream 0 larger than the tile + assert!(parse_tile_header(&big, 64, 48).is_err()); + let mut idx = tile.clone(); + idx[8 * 16..8 * 16 + 2].copy_from_slice(&[0xff, 0xff]); // index words beyond the stream + assert!(parse_tile_header(&idx, 64, 48).is_err()); + + let s = &h.streams[9]; + let mut bad_half = tile.clone(); + let idx_start = (s.word + 1) * 16; + bad_half[idx_start..idx_start + 2].copy_from_slice(&[0xff, 0xff]); // A past the data area + assert!(stream_halves(&bad_half, s, h.ntu).is_err()); + let outside = StreamRef { index_words: 0xffff, ..s.clone() }; + assert!(stream_halves(&tile, &outside, h.ntu).is_err()); + } + #[test] fn every_stream_of_an_encoded_tile_parses() { for s in [0u8, 3] {