lightcraft/crates/raw/tests/vendor_robust.rs

305 lines
12 KiB
Rust

//! Malformed vendor raw files (NEF, ARW, PEF, ORF, RW2 of every raw format, CR2, RAF) must decode to an error, never panic.
use lightcraft_tiff::tags as t;
use lightcraft_tiff::{ByteOrder, IfdBuilder, ImageData, TiffWriter, Value};
use proptest::prelude::*;
const W: u32 = 32;
const H: u32 = 8;
/// A CFA TIFF the way most vendors lay it out: IFD0 (make) + a SubIFD holding the raw strip.
fn cfa_tiff(make: &str, compression: u16, bits: u16, order: ByteOrder) -> Vec<u8> {
let px = (W * H) as usize;
// Sony's ARW2 (32767) packs a sample per byte
let len = if compression == 32767 { px } else { px * 2 };
let strip: Vec<u8> = (0..len).map(|i| (i * 37 % 251) as u8).collect();
let mut raw = IfdBuilder::new();
raw.set(t::NEW_SUBFILE_TYPE, Value::Long(vec![0]));
raw.set(t::IMAGE_WIDTH, Value::Long(vec![W]));
raw.set(t::IMAGE_LENGTH, Value::Long(vec![H]));
raw.set(t::BITS_PER_SAMPLE, Value::Short(vec![bits]));
raw.set(t::COMPRESSION, Value::Short(vec![compression]));
raw.set(t::PHOTOMETRIC, Value::Short(vec![t::photometric::CFA]));
raw.set(t::CFA_REPEAT_PATTERN_DIM, Value::Short(vec![2, 2]));
raw.set(t::CFA_PATTERN_EP, Value::Byte(vec![0, 1, 1, 2]));
raw.set_image(ImageData::Strips { rows_per_strip: H, strips: vec![strip] });
let mut ifd0 = IfdBuilder::new();
ifd0.set(t::MAKE, Value::Ascii(make.into()));
ifd0.set(t::MODEL, Value::Ascii("TEST".into()));
ifd0.add_sub_ifd(raw);
TiffWriter::new(order, false).write(&[ifd0]).unwrap()
}
/// An Olympus ORF (TIFF with an `IIRO` magic) or Panasonic RW2 (`IIU\0`), raw image in IFD0.
fn magic_tiff(magic: &[u8; 4], make: &str) -> Vec<u8> {
let px = (W * H) as usize;
let mut ifd = IfdBuilder::new();
ifd.set(t::IMAGE_WIDTH, Value::Long(vec![W]));
ifd.set(t::IMAGE_LENGTH, Value::Long(vec![H]));
ifd.set(t::BITS_PER_SAMPLE, Value::Short(vec![16]));
ifd.set(t::COMPRESSION, Value::Short(vec![1]));
ifd.set(t::PHOTOMETRIC, Value::Short(vec![1]));
ifd.set(t::MAKE, Value::Ascii(make.into()));
ifd.set_image(ImageData::Strips { rows_per_strip: H, strips: vec![(0..px * 2).map(|i| (i * 13) as u8).collect()] });
let mut b = TiffWriter::new(ByteOrder::Little, false).write(&[ifd]).unwrap();
b[..4].copy_from_slice(magic);
b
}
/// A Panasonic RW2: sensor size, bit depth and raw format in IFD0, 12-bit packed blocks.
fn rw2() -> Vec<u8> {
let mut ifd = IfdBuilder::new();
ifd.set(0x0002, Value::Short(vec![W as u16]));
ifd.set(0x0003, Value::Short(vec![H as u16]));
ifd.set(0x0004, Value::Short(vec![1]));
ifd.set(0x0005, Value::Short(vec![2]));
ifd.set(0x0006, Value::Short(vec![H as u16 - 1]));
ifd.set(0x0007, Value::Short(vec![W as u16 - 2]));
ifd.set(0x000a, Value::Short(vec![12]));
ifd.set(0x002d, Value::Short(vec![5]));
ifd.set(0x001c, Value::Short(vec![64]));
ifd.set(0x001d, Value::Short(vec![64]));
ifd.set(0x001e, Value::Short(vec![64]));
ifd.set(t::MAKE, Value::Ascii("Panasonic".into()));
let need = (W * H).div_ceil(10) as usize * 16;
ifd.set_image(ImageData::Strips { rows_per_strip: H, strips: vec![(0..need).map(|i| (i * 29) as u8).collect()] });
let mut b = TiffWriter::new(ByteOrder::Little, false).write(&[ifd]).unwrap();
b[..4].copy_from_slice(b"IIU\0");
b
}
/// A Panasonic RW2 of raw format `format` (or, with `format` 0, the oldest 16-bit-word kind): sensor size, borders,
/// bit depth, black levels, an in-camera crop and `n` bytes of payload at the end, at `RawDataOffset`. Format 8 also
/// gets two strips covering the payload and a code table.
fn rw2_of(format: u16, bits: u16, n: usize) -> Vec<u8> {
let u16s = |v: &[u16]| Value::Undefined(v.iter().flat_map(|x| x.to_le_bytes()).collect());
let build = |off: u32| {
let mut ifd = IfdBuilder::new();
for (tag, v) in [(2, W as u16), (3, H as u16), (4, 2), (5, 4), (6, H as u16), (7, W as u16), (9, 2), (0x0a, bits), (0x1c, 128), (0x1d, 128)] {
ifd.set(tag, Value::Short(vec![v]));
}
for (tag, v) in [(0x2f, 2u16), (0x30, 6), (0x31, H as u16 - 2), (0x32, W as u16 - 2)] {
ifd.set(tag, Value::Short(vec![v]));
}
if format == 0 {
ifd.set(0x000b, Value::Short(vec![34828]));
ifd.set(t::STRIP_OFFSETS, Value::Long(vec![off]));
} else {
ifd.set(0x002d, Value::Short(vec![format]));
ifd.set(0x0118, Value::Long(vec![off]));
}
if format == 8 {
let table = [
(6, 62),
(7, 126),
(6, 61),
(5, 28),
(4, 12),
(3, 4),
(3, 2),
(2, 0),
(3, 3),
(3, 5),
(4, 13),
(5, 29),
(6, 60),
(8, 254),
(8, 255),
(12, 4094),
(12, 4095),
];
ifd.set(0x0040, u16s(&std::iter::once(17).chain(table.iter().flat_map(|&(l, c)| [l, c])).collect::<Vec<u16>>()));
let half = (n / 2) as u32;
let (a, b) = (off, off + half);
ifd.set(0x0044, u16s(&[2, a as u16, (a >> 16) as u16, b as u16, (b >> 16) as u16]));
ifd.set(0x0045, u16s(&[2, 0, 0, W as u16 / 2, 0]));
ifd.set(0x0046, u16s(&[2, (half * 8) as u16, ((half * 8) >> 16) as u16, (half * 8) as u16, ((half * 8) >> 16) as u16]));
ifd.set(0x0047, u16s(&[2, W as u16 / 2, W as u16 / 2]));
ifd.set(0x0048, u16s(&[2, H as u16, H as u16]));
}
ifd.set(t::MAKE, Value::Ascii("Panasonic".into()));
let mut b = TiffWriter::new(ByteOrder::Little, false).write(&[ifd]).unwrap();
b[..4].copy_from_slice(b"IIU\0");
b
};
let off = build(0).len();
let mut b = build(off as u32);
b.extend((0..n).map(|i| (i * 89 % 253) as u8));
b
}
/// A Pentax PEF: the raw image in IFD0, wide enough for the dark-border trim.
fn pef(compression: u16) -> Vec<u8> {
let (w, h) = (264u32, 4u32);
let mut ifd = IfdBuilder::new();
ifd.set(t::MAKE, Value::Ascii("PENTAX".into()));
ifd.set(t::IMAGE_WIDTH, Value::Long(vec![w]));
ifd.set(t::IMAGE_LENGTH, Value::Long(vec![h]));
ifd.set(t::BITS_PER_SAMPLE, Value::Short(vec![16]));
ifd.set(t::COMPRESSION, Value::Short(vec![compression]));
ifd.set(t::PHOTOMETRIC, Value::Short(vec![t::photometric::CFA]));
let strip: Vec<u8> = (0..(w * h * 2) as usize).map(|i| if i % 528 < 8 { 0 } else { (i * 7 % 200) as u8 }).collect();
ifd.set_image(ImageData::Strips { rows_per_strip: h, strips: vec![strip] });
TiffWriter::new(ByteOrder::Big, false).write(&[ifd]).unwrap()
}
/// A Canon CR2: three placeholder IFDs and IFD3 with a 2-component lossless JPEG frame.
fn cr2() -> Vec<u8> {
let img: Vec<u16> = (0..(W * H) as usize).map(|i| 1024 + (i * 7919 % 12000) as u16).collect();
let enc = lightcraft_raw::ljpeg::encode(&img, W as usize / 2, H as usize, 2, 14, 1, 0);
let mut ifd0 = IfdBuilder::new();
ifd0.set(t::MAKE, Value::Ascii("Canon".into()));
let mut ifd3 = IfdBuilder::new();
ifd3.set(t::COMPRESSION, Value::Short(vec![6]));
ifd3.set(0xc640, Value::Short(vec![1, 16, 16]));
ifd3.set_image(ImageData::Strips { rows_per_strip: H, strips: vec![enc] });
let blank = || IfdBuilder::new().with(1, Value::Short(vec![0]));
TiffWriter::new(ByteOrder::Little, false).write(&[ifd0, blank(), blank(), ifd3]).unwrap()
}
/// A Fujifilm RAF: header, records, raw block (LE TIFF whose IFD0 points to a 0xf000 raw IFD).
fn raf_file(w: u32, h: u32, bits: u32, strip: Vec<u8>, layout: Option<[u8; 36]>, jpeg: &[u8]) -> Vec<u8> {
let mut block = b"II*\0\x08\0\0\0".to_vec();
let sub_off: u32 = 8 + 2 + 12 + 4;
block.extend_from_slice(&1u16.to_le_bytes());
block.extend_from_slice(&[0x00, 0xf0, 13, 0, 1, 0, 0, 0]);
block.extend_from_slice(&sub_off.to_le_bytes());
block.extend_from_slice(&0u32.to_le_bytes());
let n = 7u32;
let wb_off = sub_off + 2 + 12 * n + 4;
let strip_off = wb_off + 12;
let entries: [(u16, u32, u32); 7] = [
(0xf001, 1, w),
(0xf002, 1, h),
(0xf003, 1, bits),
(0xf007, 1, strip_off),
(0xf008, 1, strip.len() as u32),
(0xf00a, 1, 64),
(0xf00e, 3, wb_off),
];
block.extend_from_slice(&(n as u16).to_le_bytes());
for (tag, count, v) in entries {
block.extend_from_slice(&tag.to_le_bytes());
block.extend_from_slice(&4u16.to_le_bytes());
block.extend_from_slice(&count.to_le_bytes());
block.extend_from_slice(&v.to_le_bytes());
}
block.extend_from_slice(&0u32.to_le_bytes());
for v in [300u32, 600, 450] {
block.extend_from_slice(&v.to_le_bytes());
}
block.extend_from_slice(&strip);
let mut recs: Vec<u8> = Vec::new();
let mut nrec = 0u32;
let mut add = |tag: u16, d: &[u8]| {
recs.extend_from_slice(&tag.to_be_bytes());
recs.extend_from_slice(&(d.len() as u16).to_be_bytes());
recs.extend_from_slice(d);
nrec += 1;
};
add(0x0100, &[(h >> 8) as u8, h as u8, (w >> 8) as u8, w as u8]);
add(0x0110, &[0, 2, 0, 4]);
add(0x0111, &[0, (h - 2) as u8, 0, (w - 4) as u8]);
if let Some(l) = layout {
add(0x0131, &l);
}
let mut dir = nrec.to_be_bytes().to_vec();
dir.extend_from_slice(&recs);
let mut out = b"FUJIFILMCCD-RAW 0201FF000000TEST".to_vec();
out.resize(108, 0);
let jpeg_off = 160u32;
let dir_off = jpeg_off + jpeg.len() as u32;
let raw_off = dir_off + dir.len() as u32;
for (i, v) in [jpeg_off, jpeg.len() as u32, dir_off, dir.len() as u32, raw_off, block.len() as u32].iter().enumerate() {
out[84 + i * 4..88 + i * 4].copy_from_slice(&v.to_be_bytes());
}
out.resize(160, 0);
out.extend_from_slice(jpeg);
out.extend_from_slice(&dir);
out.extend_from_slice(&block);
out
}
fn raf() -> Vec<u8> {
let strip: Vec<u8> = (0..(W * H * 2) as usize).map(|i| (i * 11) as u8).collect();
let layout: [u8; 36] = std::array::from_fn(|i| (i % 3) as u8);
raf_file(W, H, 16, strip, Some(layout), b"\xff\xd8\xff\xd9")
}
fn samples() -> Vec<Vec<u8>> {
vec![
cfa_tiff("NIKON CORPORATION", 1, 12, ByteOrder::Big),
cfa_tiff("NIKON CORPORATION", 34713, 14, ByteOrder::Big),
cfa_tiff("SONY", 32767, 12, ByteOrder::Little),
cfa_tiff("SONY", 1, 14, ByteOrder::Little),
cfa_tiff("SONY", 32766, 14, ByteOrder::Little),
pef(1),
pef(65535),
cfa_tiff("SAMSUNG", 1, 12, ByteOrder::Little),
cfa_tiff("ACME", 1, 16, ByteOrder::Little),
magic_tiff(b"IIRO", "OLYMPUS IMAGING CORP."),
rw2(),
cr2(),
raf(),
]
.into_iter()
.chain(rw2_formats())
.collect()
}
/// One RW2 per encoding with a sample decoder of its own (formats 4, 6 at 14 and 12 bits, 8, and 16-bit words).
fn rw2_formats() -> Vec<Vec<u8>> {
vec![
rw2_of(4, 12, (W * H).div_ceil(14) as usize * 16),
rw2_of(6, 14, (W * H).div_ceil(11) as usize * 16),
rw2_of(6, 12, (W * H).div_ceil(14) as usize * 16),
rw2_of(8, 14, (W * H) as usize * 2),
rw2_of(0, 12, (W * H) as usize * 2),
]
}
fn exercise(bytes: &[u8]) {
let _ = lightcraft_raw::probe(bytes);
let _ = lightcraft_raw::probe_info(bytes);
let _ = lightcraft_raw::embedded_preview(bytes);
if let Ok(img) = lightcraft_raw::decode(bytes)
&& img.width * img.height <= 1 << 16
{
let _ = img.develop(lightcraft_raw::Method::Ahd);
}
}
#[test]
fn vendor_samples_truncated_at_every_length() {
for s in samples() {
exercise(&s);
for n in (0..s.len()).step_by(3) {
exercise(&s[..n]);
}
}
}
proptest! {
#![proptest_config(ProptestConfig { cases: 2000, .. ProptestConfig::default() })]
#[test]
fn mutated_vendor_files_never_panic(kind in 0usize..18, flips in proptest::collection::vec((any::<usize>(), any::<u8>()), 1..16), cut in any::<usize>()) {
let mut data = samples().swap_remove(kind);
let n = data.len();
for (i, v) in flips {
data[i % n] = v;
}
let keep = if cut % 3 == 0 { cut % n } else { n };
exercise(&data[..keep]);
}
}
/// The RW2 samples decode as they are, so the mutations above reach every format's sample decoder.
#[test]
fn rw2_samples_decode_unmutated() {
for s in &rw2_formats() {
let r = lightcraft_raw::decode(s).unwrap();
assert_eq!((r.width, r.height), (W as usize, H as usize));
}
}