use std::io::{Read, Write};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::archive::{Codec, DecodeOpts, EncodeOpts};
use crate::format::{CodecCaps, CorruptionDetection, FormatMeta};
use crate::source::{ReaderSource, Source};
use crate::testing::SharedBuf;
pub fn incompressible(len: usize) -> Vec<u8> {
let mut s: u32 = 1;
(0..len)
.map(|_| {
s = s.wrapping_mul(1103515245).wrapping_add(12345);
(s >> 16) as u8
})
.collect()
}
pub fn compressible(len: usize) -> Vec<u8> {
const PHRASE: &[u8] =
b"the quick brown fox jumps over the lazy dog, again and again and again. ";
PHRASE.iter().copied().cycle().take(len).collect()
}
const PROPERTY_8_PLAIN_LEN: usize = 4 * 1024 * 1024;
fn encode(codec: &dyn Codec, plain: &[u8]) -> Vec<u8> {
encode_with(codec, plain, &EncodeOpts::default())
}
fn encode_with(codec: &dyn Codec, plain: &[u8], opts: &EncodeOpts) -> Vec<u8> {
let id = codec.id();
let buf = SharedBuf::new();
let mut sink = codec
.encoder(Box::new(buf.clone()), opts)
.unwrap_or_else(|e| panic!("conformance[{id}] encoder: {e}"));
sink.write_all(plain)
.unwrap_or_else(|e| panic!("conformance[{id}] write: {e}"));
sink.finish()
.unwrap_or_else(|e| panic!("conformance[{id}] finish: {e}"));
buf.contents()
}
fn fastest_accepted_level(codec: &dyn Codec) -> Option<i32> {
[0, 1].into_iter().find(|&level| {
codec
.check_encode_opts(&EncodeOpts {
level: Some(level),
..Default::default()
})
.is_ok()
})
}
fn decode(codec: &dyn Codec, packed: Vec<u8>) -> Vec<u8> {
let id = codec.id();
let src: Box<dyn Source> = Box::new(ReaderSource::new(std::io::Cursor::new(packed)));
let mut dec = codec
.decoder(src, &DecodeOpts::default())
.unwrap_or_else(|e| panic!("conformance[{id}] decoder: {e}"));
let mut out = Vec::new();
dec.read_to_end(&mut out)
.unwrap_or_else(|e| panic!("conformance[{id}] read_to_end: {e}"));
out
}
fn test_input(
caps: CodecCaps,
fixture: Option<&[u8]>,
make: impl FnOnce() -> Vec<u8>,
) -> Option<Vec<u8>> {
if caps.encode {
Some(make())
} else {
fixture.map(<[u8]>::to_vec)
}
}
fn skip(id: crate::format::FormatId, property: u32, reason: &str) {
eprintln!("conformance[{id}] property {property}: skipped — {reason}");
}
const NO_FIXTURE: &str = "this codec cannot encode its own test input and no fixture was \
supplied; pass one via assert_codec_conforms_with";
fn check_property_10(
codec: &dyn Codec,
id: crate::format::FormatId,
caps: CodecCaps,
corruption_input: &Option<Vec<u8>>,
) {
match corruption_input {
Some(base) if base.is_empty() => {
skip(
id,
10,
"the encoded stream/fixture is empty; nothing to truncate",
);
}
Some(base) => {
let len = base.len();
let mut cuts: Vec<usize> = vec![1, len / 2, len.saturating_sub(1)];
cuts.retain(|&c| c < len);
cuts.sort_unstable();
cuts.dedup();
let full_reference = if caps.truncation_undetectable {
let src: Box<dyn Source> =
Box::new(ReaderSource::new(std::io::Cursor::new(base.clone())));
let mut dec = codec
.decoder(src, &DecodeOpts::default())
.unwrap_or_else(|e| {
panic!(
"conformance[{id}] property 10 (truncation_undetectable): \
opening the FULL, untruncated input failed: {e}"
)
});
let mut out = Vec::new();
dec.read_to_end(&mut out).unwrap_or_else(|e| {
panic!(
"conformance[{id}] property 10 (truncation_undetectable): decoding \
the FULL, untruncated fixture must succeed — it is the reference \
every truncated decode is checked against: {e}"
)
});
Some(out)
} else {
None
};
let mut last_ok_len: Option<usize> = None;
let mut largest_cut_ok_len: Option<usize> = None;
let largest_cut = cuts.last().copied();
for cut in cuts {
let truncated = base[..cut].to_vec();
let src: Box<dyn Source> =
Box::new(ReaderSource::new(std::io::Cursor::new(truncated)));
let mut dec = codec
.decoder(src, &DecodeOpts::default())
.unwrap_or_else(|e| {
panic!("conformance[{id}] property 10 decoder (cut {cut}/{len}): {e}")
});
let mut out = Vec::new();
match dec.read_to_end(&mut out) {
Ok(_) => match &full_reference {
Some(full) => {
assert!(
out.len() <= full.len() && full[..out.len()] == out[..],
"conformance[{id}] property 10 (truncation_undetectable): \
truncated input (cut to {cut} of {len} bytes) decoded to \
{} bytes that are NOT a prefix of the {}-byte reference \
decode of the full input — a codec declaring \
truncation_undetectable must still decode truncated input \
to a genuine prefix, never fabricated, reordered or padded \
bytes",
out.len(),
full.len()
);
if let Some(prev_len) = last_ok_len {
assert!(
out.len() >= prev_len,
"conformance[{id}] property 10 (truncation_undetectable): \
monotonicity violated — cut {cut} of {len} bytes (a \
LESS severe truncation than an earlier, smaller cut \
already tried in this same sweep) decoded to {} bytes, \
FEWER than that earlier cut's {prev_len} bytes; an \
honest prefix-producing decoder's output never shrinks \
as less of the stream is missing",
out.len()
);
}
last_ok_len = Some(out.len());
if Some(cut) == largest_cut {
largest_cut_ok_len = Some(out.len());
}
}
None => panic!(
"conformance[{id}] property 10: truncated input (cut to {cut} \
of {len} bytes) decoded without error; a codec failing this \
should either detect truncation or be reconsidered"
),
},
Err(e) => {
let classified = crate::Error::from_decode_io(e);
assert!(
matches!(classified, crate::Error::Corrupt(_)),
"conformance[{id}] property 10: truncated input (cut to {cut} \
of {len} bytes) raised {classified:?}, expected \
Error::Corrupt (exit 5) — NormalizeDecodeErrors should \
classify truncation as InvalidData"
);
assert_eq!(
classified.exit_code(),
5,
"conformance[{id}] property 10: Error::Corrupt must be exit \
code 5"
);
}
}
}
if let Some(full) = &full_reference
&& let Some(largest_ok_len) = largest_cut_ok_len
{
assert!(
largest_ok_len > 0 || full.is_empty(),
"conformance[{id}] property 10 (truncation_undetectable): the largest \
cut in this sweep ({} of {len} bytes) decoded without error to EMPTY \
output, but the untruncated input decodes to {} bytes — a decoder \
that always answers empty on truncation would otherwise pass the \
bare prefix check (the empty string is a prefix of everything); this \
closes that gap",
largest_cut.unwrap_or(0),
full.len()
);
}
}
None => skip(id, 10, NO_FIXTURE),
}
}
fn assert_codec_conforms_impl(codec: &dyn Codec, meta: &FormatMeta, fixture: Option<&[u8]>) {
let id = codec.id();
let caps = codec.caps();
assert_eq!(
meta.id, id,
"conformance[{id}] property 1: FormatMeta::id ({}) disagrees with Codec::id ({id})",
meta.id
);
if caps.encode && caps.decode {
for (label, plain) in [
("empty", Vec::new()),
("one byte", vec![0x42]),
("256 KiB incompressible", incompressible(256 * 1024)),
] {
let packed = encode(codec, &plain);
let back = decode(codec, packed);
assert_eq!(
back, plain,
"conformance[{id}] property 2: {label} did not round-trip"
);
}
}
if caps.encode && !meta.magics.is_empty() {
let packed = encode(codec, b"conformance");
let matched = meta.magics.iter().any(|rule| {
let start = rule.offset;
let end = start + rule.bytes.len();
packed.len() >= end && &packed[start..end] == rule.bytes
});
if !matched {
let tried: Vec<String> = meta
.magics
.iter()
.map(|r| format!("offset {} expects {:02x?}", r.offset, r.bytes))
.collect();
let seen = &packed[..packed.len().min(16)];
panic!(
"conformance[{id}] property 3: encoded output matched none of its registered \
magic rules (tried: {}); output began with {seen:02x?}",
tried.join(", ")
);
}
}
if caps.encode {
for level in [i32::MIN, -1, i32::MAX] {
let opts = EncodeOpts {
level: Some(level),
..Default::default()
};
match codec.check_encode_opts(&opts) {
Ok(()) => {
let encoder_result = codec.encoder(Box::new(SharedBuf::new()), &opts);
assert!(
!matches!(encoder_result, Err(crate::Error::Usage(_))),
"conformance[{id}] property 6: level {level} passed check_encode_opts \
but encoder() rejected it as Error::Usage — the two must agree"
);
}
Err(crate::Error::Usage(_)) => {
let encoder_result = codec.encoder(Box::new(SharedBuf::new()), &opts);
assert!(
matches!(encoder_result, Err(crate::Error::Usage(_))),
"conformance[{id}] property 6: level {level} was rejected by \
check_encode_opts as Error::Usage, but encoder() did not also reject \
it as Error::Usage — encoder() must re-validate, not rely on a \
pre-flight caller checked separately"
);
}
Err(other) => panic!(
"conformance[{id}] property 6: level {level} produced {other:?} from \
check_encode_opts, expected Ok or Error::Usage"
),
}
}
}
if caps.encode {
#[derive(Clone)]
struct FlushSpy(Arc<std::sync::atomic::AtomicBool>);
impl Write for FlushSpy {
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
Ok(b.len())
}
fn flush(&mut self) -> std::io::Result<()> {
self.0.store(true, Ordering::Relaxed);
Ok(())
}
}
let flushed = Arc::new(std::sync::atomic::AtomicBool::new(false));
let mut sink = codec
.encoder(
Box::new(FlushSpy(Arc::clone(&flushed))),
&EncodeOpts::default(),
)
.unwrap_or_else(|e| panic!("conformance[{id}] property 4 encoder: {e}"));
sink.write_all(b"conformance")
.unwrap_or_else(|e| panic!("conformance[{id}] property 4 write: {e}"));
sink.finish()
.unwrap_or_else(|e| panic!("conformance[{id}] property 4 finish: {e}"));
assert!(
flushed.load(Ordering::Relaxed),
"conformance[{id}] property 4: finish() did not flush the underlying writer"
);
struct Counting(Arc<AtomicU64>);
impl Write for Counting {
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
self.0.fetch_add(b.len() as u64, Ordering::Relaxed);
Ok(b.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let count = Arc::new(AtomicU64::new(0));
let mut sink = codec
.encoder(
Box::new(Counting(Arc::clone(&count))),
&EncodeOpts::default(),
)
.unwrap_or_else(|e| panic!("conformance[{id}] property 5 encoder: {e}"));
sink.write_all(b"conformance")
.unwrap_or_else(|e| panic!("conformance[{id}] property 5 write: {e}"));
let before_finish = count.load(Ordering::Relaxed);
sink.finish()
.unwrap_or_else(|e| panic!("conformance[{id}] property 5 finish: {e}"));
let after_finish = count.load(Ordering::Relaxed);
if after_finish > before_finish {
struct FailAfter {
budget: u64,
written: u64,
}
impl Write for FailAfter {
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
if self.written >= self.budget {
return Err(std::io::Error::other("conformance: writer failed"));
}
let n = std::cmp::min(b.len() as u64, self.budget - self.written);
self.written += n;
Ok(n as usize)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let mut sink = codec
.encoder(
Box::new(FailAfter {
budget: before_finish,
written: 0,
}),
&EncodeOpts::default(),
)
.unwrap_or_else(|e| panic!("conformance[{id}] property 5 encoder: {e}"));
sink.write_all(b"conformance").unwrap_or_else(|e| {
panic!("conformance[{id}] property 5: pre-finish writes must succeed: {e}")
});
assert!(
sink.finish().is_err(),
"conformance[{id}] property 5: finish() swallowed a write error \
that Drop would also have swallowed"
);
}
}
if caps.decode {
match test_input(caps, fixture, || encode(codec, b"conformance")) {
Some(packed) => {
let src: Box<dyn Source> =
Box::new(ReaderSource::new(std::io::Cursor::new(packed)));
let dec = codec
.decoder(src, &DecodeOpts::default())
.unwrap_or_else(|e| panic!("conformance[{id}] property 7 decoder: {e}"));
assert!(
!dec.caps().seekable || caps.frame_index,
"conformance[{id}] property 7: decoder claims seekable without frame_index"
);
}
None => skip(id, 7, NO_FIXTURE),
}
let property_8_opts = EncodeOpts {
level: fastest_accepted_level(codec),
..Default::default()
};
match test_input(caps, fixture, || {
encode_with(
codec,
&incompressible(PROPERTY_8_PLAIN_LEN),
&property_8_opts,
)
}) {
Some(big) => {
if caps.encode {
assert!(
big.len() > PROPERTY_8_PLAIN_LEN / 2,
"conformance[{id}] property 8: incompressible input did not stay large \
once encoded, so this property cannot discriminate"
);
}
let big_len = big.len() as u64;
let threshold = big_len / 4;
if threshold == 0 {
panic!(
"conformance[{id}] property 8: the encoded stream is only {big_len} \
bytes long, so big_len/4 rounds down to 0 and no decoder could ever \
satisfy this threshold — this names a sizing problem with the \
payload/fixture, not the codec"
);
}
struct Metered {
inner: std::io::Cursor<Vec<u8>>,
served: Arc<AtomicU64>,
}
impl Read for Metered {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let n = self.inner.read(buf)?;
self.served.fetch_add(n as u64, Ordering::Relaxed);
Ok(n)
}
}
impl Source for Metered {
fn caps(&self) -> crate::source::SourceCaps {
crate::source::SourceCaps {
seekable: false,
len: None,
}
}
fn as_seek(&mut self) -> Option<&mut dyn crate::source::SeekRead> {
None
}
}
let served = Arc::new(AtomicU64::new(0));
let src: Box<dyn Source> = Box::new(Metered {
inner: std::io::Cursor::new(big),
served: Arc::clone(&served),
});
let mut dec = codec
.decoder(src, &DecodeOpts::default())
.unwrap_or_else(|e| panic!("conformance[{id}] property 8 decoder: {e}"));
let mut first = [0u8; 1024];
let n = dec
.read(&mut first)
.unwrap_or_else(|e| panic!("conformance[{id}] property 8 first read: {e}"));
assert!(
n > 0,
"conformance[{id}] property 8: decoder produced no output"
);
let consumed = served.load(Ordering::Relaxed);
assert!(
consumed < threshold,
"conformance[{id}] property 8: first output arrived only after reading \
{consumed} of {big_len} bytes (threshold {threshold}); a read-to-end \
implementation looks exactly like this"
);
}
None => skip(id, 8, NO_FIXTURE),
}
let corruption_input =
test_input(caps, fixture, || encode(codec, &incompressible(64 * 1024)));
if caps.detects_corruption == CorruptionDetection::Never {
skip(
id,
9,
"this codec declares CorruptionDetection::Never — no check exists to prove",
);
} else {
let compressible_input =
test_input(caps, fixture, || encode(codec, &compressible(64 * 1024)));
for (shape, base) in [
("incompressible", &corruption_input),
("compressible", &compressible_input),
] {
match base {
Some(base) => {
let mut bytes = base.clone();
let mid = bytes.len() / 2;
bytes[mid] ^= 0xFF;
let src: Box<dyn Source> =
Box::new(ReaderSource::new(std::io::Cursor::new(bytes)));
let mut dec =
codec
.decoder(src, &DecodeOpts::default())
.unwrap_or_else(|e| {
panic!("conformance[{id}] property 9 ({shape}) decoder: {e}")
});
let mut out = Vec::new();
match dec.read_to_end(&mut out) {
Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {}
Err(e) => panic!(
"conformance[{id}] property 9 ({shape}): corrupted input gave \
{:?}, expected InvalidData — Error::from_decode_io keys exit 5 \
off that kind",
e.kind()
),
Ok(_) => panic!(
"conformance[{id}] property 9 ({shape}): corrupted input decoded \
without error, but this codec declares detects_corruption = \
{:?}",
caps.detects_corruption
),
}
}
None => skip(id, 9, NO_FIXTURE),
}
}
}
check_property_10(codec, id, caps, &corruption_input);
struct AlwaysPermissionDenied;
impl Read for AlwaysPermissionDenied {
fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"conformance: simulated disk error",
))
}
}
impl Source for AlwaysPermissionDenied {
fn caps(&self) -> crate::source::SourceCaps {
crate::source::SourceCaps {
seekable: false,
len: None,
}
}
fn as_seek(&mut self) -> Option<&mut dyn crate::source::SeekRead> {
None
}
}
let src: Box<dyn Source> = Box::new(AlwaysPermissionDenied);
let mut dec = codec
.decoder(src, &DecodeOpts::default())
.unwrap_or_else(|e| panic!("conformance[{id}] property 11 decoder: {e}"));
let mut out = Vec::new();
match dec.read_to_end(&mut out) {
Ok(_) => panic!(
"conformance[{id}] property 11: decoding from an always-failing source \
succeeded"
),
Err(io_err) => {
assert_eq!(
io_err.kind(),
std::io::ErrorKind::PermissionDenied,
"conformance[{id}] property 11: the decoder must not fold a genuine disk \
error onto InvalidData — got {:?}",
io_err.kind()
);
let classified = crate::Error::from_decode_io(io_err);
assert!(
matches!(classified, crate::Error::Io(_)),
"conformance[{id}] property 11: a genuine disk error must classify as \
Error::Io, not Error::Corrupt — got {classified:?}"
);
assert_eq!(
classified.exit_code(),
1,
"conformance[{id}] property 11: Error::Io must be exit code 1"
);
}
}
if !caps.parallel_encode {
skip(id, 12, "declares parallel_encode: false");
} else {
let plain = incompressible(PROPERTY_8_PLAIN_LEN);
let per_worker = caps.memory_per_worker.unwrap_or(0);
let gov = crate::Governor::new(4, per_worker.saturating_mul(4).max(1));
let buf = SharedBuf::new();
let mut sink = codec
.encoder(
Box::new(buf.clone()),
&EncodeOpts {
governor: Some(Arc::clone(&gov)),
..EncodeOpts::default()
},
)
.unwrap_or_else(|e| panic!("conformance[{id}] property 12: encoder: {e}"));
sink.write_all(&plain)
.unwrap_or_else(|e| panic!("conformance[{id}] property 12: write: {e}"));
assert!(
gov.outstanding() > 1,
"conformance[{id}] property 12: declares parallel_encode and a \
memory_per_worker that should leave room for {} of the governor's 4 \
workers, but only {} were held while encoding — either it is not using \
the governor at all, or memory_per_worker is declared too small for what \
encoder() actually asks per worker. Round-tripping does not demonstrate \
parallelism.",
gov.workers_for(per_worker),
gov.outstanding()
);
sink.finish()
.unwrap_or_else(|e| panic!("conformance[{id}] property 12: finish: {e}"));
assert_eq!(
gov.outstanding(),
0,
"conformance[{id}] property 12: lease not released after finish — the \
Sink must own the LeaseSet so the budget frees when the encode ends"
);
let packed = buf.contents();
let src: Box<dyn Source> = Box::new(ReaderSource::new(std::io::Cursor::new(packed)));
let mut dec = codec
.decoder(src, &DecodeOpts::default())
.unwrap_or_else(|e| panic!("conformance[{id}] property 12: decoder: {e}"));
let mut out = Vec::new();
dec.read_to_end(&mut out)
.unwrap_or_else(|e| panic!("conformance[{id}] property 12: read: {e}"));
assert_eq!(
out, plain,
"conformance[{id}] property 12: parallel-encoded stream did not round \
trip. Bytes may differ from single-threaded output; DATA may not."
);
}
}
}
pub fn assert_codec_conforms(codec: &dyn Codec, meta: &FormatMeta) {
assert_codec_conforms_impl(codec, meta, None)
}
pub fn assert_codec_conforms_with(codec: &dyn Codec, meta: &FormatMeta, fixture: Option<&[u8]>) {
assert_codec_conforms_impl(codec, meta, fixture)
}
#[cfg(test)]
fn assert_panics_naming(codec: &dyn Codec, meta: &FormatMeta, expected: &str) {
let id = codec.id();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
assert_codec_conforms(codec, meta);
}));
match result {
Ok(()) => panic!(
"conformance[{id}]: expected assert_codec_conforms to panic naming {expected:?}, \
but it passed"
),
Err(payload) => {
let message = payload
.downcast_ref::<String>()
.map(String::as_str)
.or_else(|| payload.downcast_ref::<&str>().copied())
.unwrap_or("<non-string panic payload>");
assert!(
message.contains(expected),
"conformance[{id}]: panicked, but the message did not mention {expected:?}: \
{message}"
);
}
}
}
#[cfg(test)]
fn assert_panics_naming_with_fixture(
codec: &dyn Codec,
meta: &FormatMeta,
fixture: &[u8],
expected: &str,
) {
let id = codec.id();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
assert_codec_conforms_with(codec, meta, Some(fixture));
}));
match result {
Ok(()) => panic!(
"conformance[{id}]: expected assert_codec_conforms_with to panic naming \
{expected:?}, but it passed"
),
Err(payload) => {
let message = payload
.downcast_ref::<String>()
.map(String::as_str)
.or_else(|| payload.downcast_ref::<&str>().copied())
.unwrap_or("<non-string panic payload>");
assert!(
message.contains(expected),
"conformance[{id}]: panicked, but the message did not mention {expected:?}: \
{message}"
);
}
}
}
#[cfg(test)]
mod framed_mock {
use super::*;
use crate::archive::Sink;
const TRAILER_LEN: usize = 4;
fn fold(sum: u32, byte: u8) -> u32 {
sum.wrapping_mul(31).wrapping_add(byte as u32)
}
pub struct FramedMock;
impl Codec for FramedMock {
fn id(&self) -> crate::format::FormatId {
crate::testing::MOCK_CODEC
}
fn caps(&self) -> CodecCaps {
CodecCaps {
encode: true,
decode: true,
detects_corruption: CorruptionDetection::Always,
..Default::default()
}
}
fn decoder(&self, src: Box<dyn Source>, _o: &DecodeOpts) -> crate::Result<Box<dyn Source>> {
Ok(Box::new(crate::source::StreamOnly::new(FramedReader {
inner: src,
pending: std::collections::VecDeque::new(),
sum: 0,
inner_eof: false,
trailer_ok: None,
})))
}
fn encoder(
&self,
dst: Box<dyn Write + Send>,
_o: &EncodeOpts,
) -> crate::Result<Box<dyn Sink>> {
Ok(Box::new(FramedSink { dst, sum: 0 }))
}
}
struct FramedSink {
dst: Box<dyn Write + Send>,
sum: u32,
}
impl Write for FramedSink {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let flipped: Vec<u8> = buf.iter().map(|b| b ^ 0xFF).collect();
self.dst.write_all(&flipped)?;
self.sum = flipped.iter().fold(self.sum, |acc, &b| fold(acc, b));
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
self.dst.flush()
}
}
impl Sink for FramedSink {
fn finish(mut self: Box<Self>) -> crate::Result<()> {
self.dst.write_all(&self.sum.to_be_bytes())?;
self.dst.flush()?;
Ok(())
}
}
struct FramedReader<R> {
inner: R,
pending: std::collections::VecDeque<u8>,
sum: u32,
inner_eof: bool,
trailer_ok: Option<bool>,
}
impl<R: Read> Read for FramedReader<R> {
fn read(&mut self, out: &mut [u8]) -> std::io::Result<usize> {
if self.trailer_ok == Some(false) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"framed mock: truncated or corrupted stream",
));
}
if self.trailer_ok == Some(true) && self.pending.is_empty() {
return Ok(0);
}
while !self.inner_eof && self.pending.len() <= TRAILER_LEN {
let mut chunk = [0u8; 4096];
let n = self.inner.read(&mut chunk)?;
if n == 0 {
self.inner_eof = true;
} else {
self.pending.extend(chunk[..n].iter().copied());
}
}
if self.inner_eof && self.trailer_ok.is_none() {
if self.pending.len() >= TRAILER_LEN {
let split_at = self.pending.len() - TRAILER_LEN;
let projected = self
.pending
.iter()
.take(split_at)
.fold(self.sum, |acc, &b| fold(acc, b));
let trailer_bytes: Vec<u8> =
self.pending.iter().skip(split_at).copied().collect();
let got = u32::from_be_bytes(trailer_bytes.try_into().unwrap());
self.trailer_ok = Some(got == projected);
if self.trailer_ok == Some(true) {
self.pending.truncate(split_at);
}
} else {
self.trailer_ok = Some(false);
}
if self.trailer_ok == Some(false) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"framed mock: truncated or corrupted stream",
));
}
}
let safe = if self.trailer_ok == Some(true) {
self.pending.len()
} else {
self.pending.len().saturating_sub(TRAILER_LEN)
};
let n = safe.min(out.len());
for slot in out.iter_mut().take(n) {
let b = self.pending.pop_front().expect("counted as safe above");
self.sum = fold(self.sum, b);
*slot = b ^ 0xFF;
}
Ok(n)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::format::{FormatId, FormatMeta, MagicRule};
use crate::testing::{MOCK_CODEC, MockCodec};
use framed_mock::FramedMock;
#[test]
fn framed_mock_conforms_to_every_property() {
assert_codec_conforms(&FramedMock, &FormatMeta::codec(MOCK_CODEC, &["mock"], &[]));
}
#[test]
fn mock_codec_clears_every_property_up_to_truncation() {
assert_panics_naming(
&MockCodec,
&FormatMeta::codec(MOCK_CODEC, &["mock"], &[]),
"property 10",
);
}
struct AppendSentinelReader<R> {
inner: R,
appended: bool,
}
impl<R: Read> Read for AppendSentinelReader<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let n = self.inner.read(buf)?;
if n > 0 {
return Ok(n);
}
if !self.appended {
self.appended = true;
buf[0] = 0xAA;
return Ok(1);
}
Ok(0)
}
}
#[derive(Debug)]
struct NonPrefixOnTruncation;
impl Codec for NonPrefixOnTruncation {
fn id(&self) -> FormatId {
MOCK_CODEC
}
fn caps(&self) -> CodecCaps {
CodecCaps {
decode: true,
truncation_undetectable: true,
..Default::default()
}
}
fn decoder(&self, src: Box<dyn Source>, _o: &DecodeOpts) -> crate::Result<Box<dyn Source>> {
Ok(Box::new(crate::source::StreamOnly::new(
AppendSentinelReader {
inner: src,
appended: false,
},
)))
}
fn encoder(
&self,
_dst: Box<dyn Write + Send>,
_o: &EncodeOpts,
) -> crate::Result<Box<dyn crate::archive::Sink>> {
panic!("NonPrefixOnTruncation cannot encode — the harness must never call this")
}
}
#[test]
fn a_decoder_that_returns_a_non_prefix_on_truncation_is_caught() {
let fixture: Vec<u8> = (0u32..8192).map(|i| (i % 251) as u8 + 1).collect();
assert_panics_naming_with_fixture(
&NonPrefixOnTruncation,
&FormatMeta::codec(MOCK_CODEC, &["mock"], &[]),
&fixture,
"property 10",
);
}
#[derive(Debug)]
struct AlwaysEmptyOnTruncation {
full_len: usize,
}
impl Codec for AlwaysEmptyOnTruncation {
fn id(&self) -> FormatId {
MOCK_CODEC
}
fn caps(&self) -> CodecCaps {
CodecCaps {
decode: true,
truncation_undetectable: true,
..Default::default()
}
}
fn decoder(
&self,
mut src: Box<dyn Source>,
_o: &DecodeOpts,
) -> crate::Result<Box<dyn Source>> {
let mut buf = Vec::new();
src.read_to_end(&mut buf)?;
let out = if buf.len() == self.full_len {
buf
} else {
Vec::new()
};
Ok(Box::new(crate::source::StreamOnly::new(
std::io::Cursor::new(out),
)))
}
fn encoder(
&self,
_dst: Box<dyn Write + Send>,
_o: &EncodeOpts,
) -> crate::Result<Box<dyn crate::archive::Sink>> {
panic!("AlwaysEmptyOnTruncation cannot encode — the harness must never call this")
}
}
#[test]
fn a_decoder_that_always_answers_empty_on_truncation_is_caught() {
let fixture: Vec<u8> = (0u32..8192).map(|i| (i % 251) as u8 + 1).collect();
let codec = AlwaysEmptyOnTruncation {
full_len: fixture.len(),
};
let id = codec.id();
let caps = codec.caps();
let corruption_input = Some(fixture);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
check_property_10(&codec, id, caps, &corruption_input);
}));
match result {
Ok(()) => {
panic!("expected check_property_10 to panic naming \"property 10\", but it passed")
}
Err(payload) => {
let message = payload
.downcast_ref::<String>()
.map(String::as_str)
.or_else(|| payload.downcast_ref::<&str>().copied())
.unwrap_or("<non-string panic payload>");
assert!(
message.contains("property 10"),
"panicked, but the message did not mention \"property 10\": {message}"
);
}
}
}
#[derive(Debug)]
struct StricterThanRequiredOnTruncation {
full_len: usize,
}
impl Codec for StricterThanRequiredOnTruncation {
fn id(&self) -> FormatId {
MOCK_CODEC
}
fn caps(&self) -> CodecCaps {
CodecCaps {
decode: true,
truncation_undetectable: true,
..Default::default()
}
}
fn decoder(
&self,
mut src: Box<dyn Source>,
_o: &DecodeOpts,
) -> crate::Result<Box<dyn Source>> {
let mut buf = Vec::new();
src.read_to_end(&mut buf)?;
let n = buf.len();
let out: Box<dyn std::io::Read + Send> = if n == self.full_len {
Box::new(std::io::Cursor::new(buf))
} else if n <= 1 {
Box::new(std::io::Cursor::new(Vec::new()))
} else {
Box::new(FailingReader)
};
Ok(Box::new(crate::source::StreamOnly::new(out)))
}
fn encoder(
&self,
_dst: Box<dyn Write + Send>,
_o: &EncodeOpts,
) -> crate::Result<Box<dyn crate::archive::Sink>> {
panic!(
"StricterThanRequiredOnTruncation cannot encode — the harness must never call \
this"
)
}
}
struct FailingReader;
impl std::io::Read for FailingReader {
fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"truncated stream",
))
}
}
#[test]
fn a_codec_stricter_than_truncation_undetectable_requires_is_not_punished() {
let fixture: Vec<u8> = (0u32..8192).map(|i| (i % 251) as u8 + 1).collect();
let codec = StricterThanRequiredOnTruncation {
full_len: fixture.len(),
};
let id = codec.id();
let caps = codec.caps();
let corruption_input = Some(fixture);
check_property_10(&codec, id, caps, &corruption_input);
}
#[test]
fn framed_mock_conforms_when_only_one_of_several_registered_magics_matches() {
const MAGICS: &[MagicRule] = &[
MagicRule {
offset: 0,
bytes: &[0x9C, 0x90],
format: MOCK_CODEC,
},
MagicRule {
offset: 0,
bytes: &[0x00, 0x00],
format: MOCK_CODEC,
},
];
assert_codec_conforms(
&FramedMock,
&FormatMeta::codec(MOCK_CODEC, &["mock"], MAGICS),
);
}
#[test]
fn a_decode_only_codec_wrapping_framed_mock_is_tested_from_a_fixture() {
struct DecodeOnlyMock;
impl Codec for DecodeOnlyMock {
fn id(&self) -> FormatId {
MOCK_CODEC
}
fn caps(&self) -> CodecCaps {
CodecCaps {
decode: true,
detects_corruption: CorruptionDetection::Always,
..Default::default()
}
}
fn decoder(
&self,
src: Box<dyn Source>,
o: &DecodeOpts,
) -> crate::Result<Box<dyn Source>> {
FramedMock.decoder(src, o)
}
fn encoder(
&self,
_dst: Box<dyn Write + Send>,
_o: &EncodeOpts,
) -> crate::Result<Box<dyn crate::archive::Sink>> {
panic!("DecodeOnlyMock cannot encode — the harness must never call this")
}
}
let fixture = encode(&FramedMock, &incompressible(256 * 1024));
assert_codec_conforms_with(
&DecodeOnlyMock,
&FormatMeta::codec(MOCK_CODEC, &["mock"], &[]),
Some(&fixture),
);
}
#[test]
fn a_decode_only_codec_with_no_fixture_skips_rather_than_panics() {
struct DecodeOnlyNoFixture;
impl Codec for DecodeOnlyNoFixture {
fn id(&self) -> FormatId {
MOCK_CODEC
}
fn caps(&self) -> CodecCaps {
CodecCaps {
decode: true,
..Default::default()
}
}
fn decoder(
&self,
src: Box<dyn Source>,
o: &DecodeOpts,
) -> crate::Result<Box<dyn Source>> {
MockCodec.decoder(src, o)
}
fn encoder(
&self,
_dst: Box<dyn Write + Send>,
_o: &EncodeOpts,
) -> crate::Result<Box<dyn crate::archive::Sink>> {
panic!("DecodeOnlyNoFixture cannot encode — the harness must never call this")
}
}
assert_codec_conforms(
&DecodeOnlyNoFixture,
&FormatMeta::codec(MOCK_CODEC, &["mock"], &[]),
);
}
}
#[cfg(test)]
mod broken_codecs {
use super::*;
use crate::archive::Sink;
use crate::format::{FormatId, MagicRule};
use crate::testing::{MOCK_CODEC, MockCodec};
fn mock_meta(magics: &'static [MagicRule]) -> FormatMeta {
FormatMeta::codec(MOCK_CODEC, &["mock"], magics)
}
struct BrokenRoundTrip;
struct PassthroughSink(Box<dyn Write + Send>);
impl Write for PassthroughSink {
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
self.0.write(b)
}
fn flush(&mut self) -> std::io::Result<()> {
self.0.flush()
}
}
impl Sink for PassthroughSink {
fn finish(mut self: Box<Self>) -> crate::Result<()> {
self.0.flush()?;
Ok(())
}
}
impl Codec for BrokenRoundTrip {
fn id(&self) -> FormatId {
MOCK_CODEC
}
fn caps(&self) -> CodecCaps {
MockCodec.caps()
}
fn decoder(&self, src: Box<dyn Source>, o: &DecodeOpts) -> crate::Result<Box<dyn Source>> {
MockCodec.decoder(src, o)
}
fn encoder(
&self,
dst: Box<dyn Write + Send>,
_o: &EncodeOpts,
) -> crate::Result<Box<dyn Sink>> {
Ok(Box::new(PassthroughSink(dst)))
}
}
#[test]
fn broken_round_trip_is_caught() {
assert_panics_naming(&BrokenRoundTrip, &mock_meta(&[]), "property 2");
}
#[test]
fn broken_magic_with_no_match_is_caught() {
const MAGICS: &[MagicRule] = &[
MagicRule {
offset: 0,
bytes: &[0x00, 0x00],
format: MOCK_CODEC,
},
MagicRule {
offset: 0,
bytes: &[0x11, 0x11],
format: MOCK_CODEC,
},
];
assert_panics_naming(&MockCodec, &mock_meta(MAGICS), "property 3");
}
struct BrokenFlush;
struct NoFlushSink(Box<dyn Sink>);
impl Write for NoFlushSink {
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
self.0.write(b)
}
fn flush(&mut self) -> std::io::Result<()> {
self.0.flush()
}
}
impl Sink for NoFlushSink {
fn finish(self: Box<Self>) -> crate::Result<()> {
Ok(())
}
}
impl Codec for BrokenFlush {
fn id(&self) -> FormatId {
MOCK_CODEC
}
fn caps(&self) -> CodecCaps {
MockCodec.caps()
}
fn decoder(&self, src: Box<dyn Source>, o: &DecodeOpts) -> crate::Result<Box<dyn Source>> {
MockCodec.decoder(src, o)
}
fn encoder(
&self,
dst: Box<dyn Write + Send>,
o: &EncodeOpts,
) -> crate::Result<Box<dyn Sink>> {
Ok(Box::new(NoFlushSink(MockCodec.encoder(dst, o)?)))
}
}
#[test]
fn broken_flush_is_caught() {
assert_panics_naming(&BrokenFlush, &mock_meta(&[]), "property 4");
}
struct BrokenFinishSwallowsError;
struct SwallowingSink(Box<dyn Sink>);
impl Write for SwallowingSink {
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
self.0.write(b)
}
fn flush(&mut self) -> std::io::Result<()> {
self.0.flush()
}
}
impl Sink for SwallowingSink {
fn finish(mut self: Box<Self>) -> crate::Result<()> {
let _ = self.0.write_all(b"TRAILER!");
let _ = self.0.finish();
Ok(())
}
}
impl Codec for BrokenFinishSwallowsError {
fn id(&self) -> FormatId {
MOCK_CODEC
}
fn caps(&self) -> CodecCaps {
CodecCaps {
decode: false,
..MockCodec.caps()
}
}
fn decoder(&self, src: Box<dyn Source>, o: &DecodeOpts) -> crate::Result<Box<dyn Source>> {
MockCodec.decoder(src, o)
}
fn encoder(
&self,
dst: Box<dyn Write + Send>,
o: &EncodeOpts,
) -> crate::Result<Box<dyn Sink>> {
Ok(Box::new(SwallowingSink(MockCodec.encoder(dst, o)?)))
}
}
#[test]
fn broken_finish_swallowing_a_write_error_is_caught() {
assert_panics_naming(&BrokenFinishSwallowsError, &mock_meta(&[]), "property 5");
}
struct BrokenLevelPanics;
impl Codec for BrokenLevelPanics {
fn id(&self) -> FormatId {
MOCK_CODEC
}
fn caps(&self) -> CodecCaps {
MockCodec.caps()
}
fn decoder(&self, src: Box<dyn Source>, o: &DecodeOpts) -> crate::Result<Box<dyn Source>> {
MockCodec.decoder(src, o)
}
fn encoder(
&self,
dst: Box<dyn Write + Send>,
o: &EncodeOpts,
) -> crate::Result<Box<dyn Sink>> {
let level = o.level.unwrap_or(0);
if level > 22 {
return Err(crate::Error::Usage("level too high".into()));
}
let table = [1u8, 2, 3];
let _ = table[level as usize]; MockCodec.encoder(dst, o)
}
}
#[test]
fn broken_level_validation_is_caught() {
assert_panics_naming(&BrokenLevelPanics, &mock_meta(&[]), "index out of bounds");
}
struct BrokenIncremental;
impl Codec for BrokenIncremental {
fn id(&self) -> FormatId {
MOCK_CODEC
}
fn caps(&self) -> CodecCaps {
MockCodec.caps()
}
fn decoder(
&self,
mut src: Box<dyn Source>,
_o: &DecodeOpts,
) -> crate::Result<Box<dyn Source>> {
let mut all = Vec::new();
src.read_to_end(&mut all)?; for b in &mut all {
*b ^= 0xFF;
}
Ok(Box::new(crate::source::StreamOnly::new(
std::io::Cursor::new(all),
)))
}
fn encoder(
&self,
dst: Box<dyn Write + Send>,
o: &EncodeOpts,
) -> crate::Result<Box<dyn Sink>> {
MockCodec.encoder(dst, o)
}
}
#[test]
fn broken_incremental_decode_is_caught() {
assert_panics_naming(&BrokenIncremental, &mock_meta(&[]), "property 8");
}
struct LyingAboutCorruption;
impl Codec for LyingAboutCorruption {
fn id(&self) -> FormatId {
MOCK_CODEC
}
fn caps(&self) -> CodecCaps {
CodecCaps {
detects_corruption: CorruptionDetection::Always, ..MockCodec.caps()
}
}
fn decoder(&self, src: Box<dyn Source>, o: &DecodeOpts) -> crate::Result<Box<dyn Source>> {
MockCodec.decoder(src, o)
}
fn encoder(
&self,
dst: Box<dyn Write + Send>,
o: &EncodeOpts,
) -> crate::Result<Box<dyn Sink>> {
MockCodec.encoder(dst, o)
}
}
#[test]
fn lying_about_corruption_detection_is_caught() {
assert_panics_naming(&LyingAboutCorruption, &mock_meta(&[]), "property 9");
}
#[test]
fn broken_truncation_is_caught() {
assert_panics_naming(&MockCodec, &mock_meta(&[]), "property 10");
}
struct OverNormalizingDecoder;
struct FoldsEveryErrorToInvalidData(Box<dyn Source>);
impl Read for FoldsEveryErrorToInvalidData {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.0
.read(buf)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
}
}
impl Source for FoldsEveryErrorToInvalidData {
fn caps(&self) -> crate::source::SourceCaps {
self.0.caps()
}
fn as_seek(&mut self) -> Option<&mut dyn crate::source::SeekRead> {
None
}
}
impl Codec for OverNormalizingDecoder {
fn id(&self) -> FormatId {
MOCK_CODEC
}
fn caps(&self) -> CodecCaps {
super::framed_mock::FramedMock.caps()
}
fn decoder(&self, src: Box<dyn Source>, o: &DecodeOpts) -> crate::Result<Box<dyn Source>> {
super::framed_mock::FramedMock.decoder(Box::new(FoldsEveryErrorToInvalidData(src)), o)
}
fn encoder(
&self,
dst: Box<dyn Write + Send>,
o: &EncodeOpts,
) -> crate::Result<Box<dyn Sink>> {
super::framed_mock::FramedMock.encoder(dst, o)
}
}
#[test]
fn over_normalizing_decoder_is_caught() {
assert_panics_naming(&OverNormalizingDecoder, &mock_meta(&[]), "property 11");
}
#[derive(Debug)]
struct LiesAboutParallel;
impl Codec for LiesAboutParallel {
fn id(&self) -> FormatId {
FormatId::new("lies-about-parallel")
}
fn caps(&self) -> CodecCaps {
CodecCaps {
parallel_encode: true,
memory_per_worker: Some(1024),
..super::framed_mock::FramedMock.caps()
}
}
fn decoder(&self, src: Box<dyn Source>, o: &DecodeOpts) -> crate::Result<Box<dyn Source>> {
super::framed_mock::FramedMock.decoder(src, o)
}
fn encoder(
&self,
dst: Box<dyn Write + Send>,
o: &EncodeOpts,
) -> crate::Result<Box<dyn Sink>> {
super::framed_mock::FramedMock.encoder(dst, o)
}
}
#[test]
fn property_12_catches_a_codec_that_declares_parallel_and_ignores_the_governor() {
assert_panics_naming(
&LiesAboutParallel,
&FormatMeta::codec(FormatId::new("lies-about-parallel"), &["lap"], &[]),
"property 12",
);
}
}