use super::*;
use crate::test_support::{CancelAfter, stage_of};
use std::io::Write;
use std::{cell::Cell, rc::Rc};
enum Cancel<'a> {
Never,
Checks(&'a CancelAfter),
Flag(Rc<Cell<bool>>),
}
impl Cancellation for Cancel<'_> {
fn is_cancelled(&self) -> bool {
match self {
Self::Never => false,
Self::Checks(checks) => checks.is_cancelled(),
Self::Flag(flag) => flag.get(),
}
}
}
fn image(width: u32, height: u32, coded: &[u8]) -> Vec<u8> {
let mut bytes = vec![0; DIB_BYTES as usize];
bytes[0..4].copy_from_slice(&40_u32.to_le_bytes());
bytes[4..8].copy_from_slice(&width.to_le_bytes());
bytes[8..12].copy_from_slice(&height.to_le_bytes());
bytes[12..14].copy_from_slice(&1_u16.to_le_bytes());
bytes[14..16].copy_from_slice(&1_u16.to_le_bytes());
bytes[40..43].fill(0xff);
bytes.extend_from_slice(coded);
bytes
}
fn table() -> QmTable {
QmTable::standard()
}
fn coded(rows: &[Vec<bool>], copy_rows: bool) -> Vec<u8> {
let mut encoder = crate::test_support::qm_encoder();
encoder.type0_rows(rows, copy_rows);
let mut bytes = encoder.finish();
if bytes.len() < 3 {
bytes.resize(3, 0);
}
bytes
}
fn white(width: u32, height: u32) -> Vec<u8> {
coded(&vec![vec![false; width as usize]; height as usize], false)
}
#[derive(Default)]
struct BytesSink {
bytes: Vec<u8>,
max_write: usize,
fail: bool,
flush_error: Option<fn() -> Error>,
cancel_on_flush: Option<Rc<Cell<bool>>>,
}
impl Write for BytesSink {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
if self.fail {
return Err(Error::invalid("synthetic sink failure").into());
}
let n = bytes.len().min(self.max_write.max(1));
self.bytes.extend_from_slice(&bytes[..n]);
Ok(n)
}
fn flush(&mut self) -> std::io::Result<()> {
if let Some(flag) = &self.cancel_on_flush {
flag.set(true);
}
match self.flush_error {
Some(error) => Err(error().into()),
None => Ok(()),
}
}
}
#[test]
fn ten_context_positions_and_edges_have_fixed_indices() {
let width = 11;
let x = 5;
let positions = [
(2, 3),
(2, 4),
(1, 3),
(1, 4),
(1, 5),
(1, 6),
(1, 7),
(0, 4),
(0, 5),
(0, 6),
];
for (bit, (row, column)) in positions.into_iter().enumerate() {
let mut rows = [vec![0; 2], vec![0; 2], vec![0; 2]];
rows[row][column / 8] = 0x80 >> (column % 8);
assert_eq!(
three_line_context(&rows[0], &rows[1], &rows[2], width, x),
1 << (9 - bit),
"neighbor {bit}"
);
}
let mut previous = [0_u8; 2];
previous[0] = 0x20;
assert_eq!(
three_line_context(&[0; 2], &previous, &[0; 2], width, 0),
1 << 3
);
}
#[test]
fn zero_rows_at_boundary_widths_are_stride_padded_and_sequential() {
for width in [7, 8, 9, 31, 32, 33] {
let bytes = image(width, 3, &white(width, 3));
let limits = Limits::default();
let source = bytes.clone();
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let mut sink = BytesSink {
max_write: 1,
..BytesSink::default()
};
let table = table();
let mut decoder = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type: 0,
offset: 0,
length: bytes.len() as u64,
},
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
)
.unwrap();
let stride = (width.div_ceil(32) * 4) as usize;
assert_eq!(decoder.progress().info.dib_stride, stride);
for row in 1..=3 {
assert!(decoder.decode_next_row().unwrap());
assert_eq!(decoder.progress().rows_written, row);
}
assert!(!decoder.decode_next_row().unwrap());
let report = decoder.finish().unwrap();
assert_eq!(
report.progress.arithmetic.symbols_decoded,
3 * (u64::from(width) + 1)
);
assert_eq!(report.progress.output_bytes_written, (stride * 3) as u64);
assert_eq!(sink.bytes, vec![0; stride * 3]);
}
}
#[test]
fn hand_derived_first_lps_produces_one_black_pixel() {
let bytes = image(1, 1, &[0x00, 0x00, 0x00]);
let limits = Limits::default();
let source = bytes.clone();
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let mut sink = BytesSink::default();
let table = table();
let mut decoder = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type: 0,
offset: 0,
length: bytes.len() as u64,
},
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
)
.unwrap();
assert!(decoder.decode_next_row().unwrap());
let report = decoder.finish().unwrap();
assert_eq!(report.progress.arithmetic.symbols_decoded, 2);
assert_eq!(sink.bytes, [0x80, 0, 0, 0]);
}
#[test]
fn black_row_is_copied_without_decoding_its_pixels() {
let bytes = image(1, 2, &coded(&[vec![true], vec![true]], true));
let limits = Limits::default();
let table = table();
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let source = bytes.clone();
let mut sink = BytesSink::default();
let mut decoder = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type: 0,
offset: 0,
length: bytes.len() as u64,
},
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
)
.unwrap();
assert!(decoder.decode_next_row().unwrap());
assert!(decoder.decode_next_row().unwrap());
let report = decoder.finish().unwrap();
assert_eq!(report.progress.arithmetic.symbols_decoded, 3);
assert_eq!(sink.bytes, [0x80, 0, 0, 0, 0x80, 0, 0, 0]);
}
#[test]
fn image_contexts_reset_and_first_row_copy_is_blank() {
let bytes = image(1, 1, &coded(&[vec![false]], true));
let table = table();
let limits = Limits::default();
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let earlier = [0xc0, 0, 0];
let mut decoder = crate::qm::ArithmeticDecoder::new(
Payload::from(&earlier[..]),
crate::qm::CodedSpan {
offset: 0,
length: 3,
},
&table,
&mut contexts,
&limits,
)
.unwrap();
assert!(decoder.decode_symbol(CONTROL_CONTEXT).unwrap());
decoder.finish(1).unwrap();
assert!(contexts.get(CONTROL_CONTEXT).unwrap().mps);
for _ in 0..2 {
let source = bytes.clone();
let mut sink = BytesSink::default();
let mut decoder = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type: 0,
offset: 0,
length: bytes.len() as u64,
},
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
)
.unwrap();
assert!(decoder.decode_next_row().unwrap());
let report = decoder.finish().unwrap();
assert_eq!(report.progress.arithmetic.symbols_decoded, 1);
assert_eq!(sink.bytes, [0, 0, 0, 0]);
}
}
#[test]
fn malformed_wrapper_and_preallocation_limits_fail_before_decoding() {
let limits = Limits::default();
let base = image(9, 1, &white(9, 1));
let info = checked_info(
(&base[..48]).try_into().unwrap(),
Type0Span {
record_type: 0,
offset: 17,
length: base.len() as u64,
},
&limits,
)
.unwrap();
assert_eq!((info.dib_stride, info.visible_bytes), (4, 2));
let mut cases = Vec::new();
let mut wrong = base.clone();
wrong[0] = 39;
cases.push(wrong);
let mut wrong = base.clone();
wrong[4..8].copy_from_slice(&0_u32.to_le_bytes());
cases.push(wrong);
let mut wrong = base.clone();
wrong[12] = 2;
cases.push(wrong);
let mut wrong = base.clone();
wrong[14] = 8;
cases.push(wrong);
let mut wrong = base.clone();
wrong[16] = 1;
cases.push(wrong);
let mut wrong = base.clone();
wrong[32] = 3;
cases.push(wrong);
let mut wrong = base.clone();
wrong[40] = 0;
cases.push(wrong);
let mut wrong = base.clone();
wrong[20] = 5;
cases.push(wrong);
for bytes in cases {
assert!(
checked_info(
(&bytes[..48]).try_into().unwrap(),
Type0Span {
record_type: 0,
offset: 0,
length: bytes.len() as u64
},
&limits,
)
.is_err()
);
}
let tiny = Limits {
max_allocation_bytes: 100,
..limits
};
assert!(matches!(
checked_info(
(&base[..48]).try_into().unwrap(),
Type0Span {
record_type: 0,
offset: 0,
length: base.len() as u64
},
&tiny,
)
.unwrap_err(),
Error {
kind: ErrorKind::LimitExceeded {
resource: "working allocation bytes",
..
},
..
}
));
let tiny = Limits {
max_output_bytes: 3,
..limits
};
assert!(matches!(
checked_info(
(&base[..48]).try_into().unwrap(),
Type0Span {
record_type: 0,
offset: 0,
length: base.len() as u64
},
&tiny,
)
.unwrap_err(),
Error {
kind: ErrorKind::LimitExceeded {
resource: "output bytes",
..
},
..
}
));
}
#[test]
fn non_type_zero_outer_record_is_rejected_at_image_start() {
let bytes = image(1, 1, &white(1, 1));
let limits = Limits::default();
let table = table();
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
for record_type in [1, 2, 3, u32::MAX] {
let source = bytes.clone();
let mut sink = BytesSink::default();
let error = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type,
offset: 0,
length: bytes.len() as u64,
},
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
)
.err()
.expect("non-type-zero image must be rejected");
assert_eq!(error.offset, Some(0));
assert!(matches!(
error,
Error {
kind: ErrorKind::UnsupportedFormat,
reason: "HN/C8 image record type",
..
}
));
assert!(sink.bytes.is_empty());
}
}
#[test]
fn independent_preflight_limits_identify_the_resource() {
let bytes = image(9, 1, &white(9, 1));
let header: &[u8; 48] = (&bytes[..48]).try_into().unwrap();
let span = Type0Span {
record_type: 0,
offset: 19,
length: bytes.len() as u64,
};
let limits = Limits {
max_image_pixels: 8,
..Limits::default()
};
assert!(matches!(
checked_info(header, span, &limits).unwrap_err(),
Error {
kind: ErrorKind::LimitExceeded {
resource: "image pixels",
limit: 8,
attempted: 9,
},
..
}
));
}
#[test]
fn errors_are_located_hnc8_failures_and_sink_errors_are_pdf_failures() {
use std::error::Error as _;
let error = unsupported(71, "DIB planes");
assert_eq!(
error.to_string(),
"unsupported HN/C8 at byte 71: DIB planes"
);
assert!(error.source().is_none());
let write = Error::from(ErrorKind::Io(std::io::Error::other("write")));
let error = sink(write, 71);
assert_eq!(
(error.offset, stage_of(&error)),
(Some(71), Some(Hnc8Stage::Pdf))
);
assert!(error.source().is_some());
let error = sink(Error::cancelled(), 71);
assert_eq!((error.offset, stage_of(&error)), (Some(71), None));
assert!(matches!(error.kind, ErrorKind::Cancelled));
}
#[test]
fn sink_failure_and_incomplete_finish_are_explicit() {
let bytes = image(7, 2, &white(7, 2));
let limits = Limits::default();
let table = table();
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let source = bytes.clone();
let mut sink = BytesSink {
fail: true,
..BytesSink::default()
};
let mut decoder = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type: 0,
offset: 0,
length: bytes.len() as u64,
},
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
)
.unwrap();
assert!(stage_of(&decoder.decode_next_row().unwrap_err()) == Some(Hnc8Stage::Pdf));
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let source = bytes.clone();
let mut sink = BytesSink::default();
let decoder = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type: 0,
offset: 0,
length: bytes.len() as u64,
},
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
)
.unwrap();
assert!(decoder.finish().unwrap_err().reason == "not all type-0 rows were decoded");
}
struct BadSink;
impl Write for BadSink {
fn write(&mut self, _: &[u8]) -> std::io::Result<usize> {
Ok(0)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[test]
fn zero_sink_writes_are_typed_errors() {
let bytes = image(7, 1, &white(7, 1));
let limits = Limits::default();
let table = table();
{
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let source = bytes.clone();
let mut sink = BadSink;
let mut decoder = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type: 0,
offset: 0,
length: bytes.len() as u64,
},
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
)
.unwrap();
let error = decoder.decode_next_row().unwrap_err();
assert!(stage_of(&error) == Some(Hnc8Stage::Pdf));
}
}
#[test]
fn span_bounds_and_context_counts_are_rejected() {
let bytes = image(9, 1, &white(9, 1));
let limits = Limits::default();
let table = table();
let source = bytes.clone();
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let mut sink = BytesSink::default();
let error = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type: 0,
offset: 1,
length: bytes.len() as u64,
},
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
)
.err()
.unwrap();
assert!(matches!(
error,
Error {
kind: ErrorKind::Malformed,
..
}
));
let source = bytes.clone();
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let mut sink = BytesSink::default();
let error = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type: 0,
offset: u64::MAX,
length: 1,
},
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
)
.err()
.unwrap();
assert!(matches!(
error,
Error {
kind: ErrorKind::Malformed,
..
}
));
for count in [CONTEXT_COUNT - 1, CONTEXT_COUNT + 1] {
let source = bytes.clone();
let mut contexts = ContextBank::new(count, &limits).unwrap();
let mut sink = BytesSink::default();
let error = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type: 0,
offset: 0,
length: bytes.len() as u64,
},
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
)
.err()
.unwrap();
assert!(matches!(
error,
Error {
kind: ErrorKind::Malformed,
reason: "expected exactly 1024 arithmetic contexts",
..
}
));
}
}
struct CancellingSink(Rc<Cell<bool>>);
impl Write for CancellingSink {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
self.0.set(true);
Ok(bytes.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[test]
fn cancellation_after_row_write_preserves_byte_progress() {
let bytes = image(7, 1, &white(7, 1));
let limits = Limits::default();
let table = table();
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let source = bytes.clone();
let flag = Rc::new(Cell::new(false));
let cancel = Cancel::Flag(flag.clone());
let mut sink = CancellingSink(flag);
let mut decoder = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type: 0,
offset: 0,
length: bytes.len() as u64,
},
&table,
&mut contexts,
&mut sink,
&limits,
&cancel,
)
.unwrap();
let error = decoder.decode_next_row().unwrap_err();
assert!(matches!(
error,
Error {
kind: ErrorKind::Cancelled,
..
}
));
}
#[test]
fn arithmetic_virtual_padding_and_work_limit_remain_bounded() {
let bytes = image(33, 3, &[0xa0]);
let limits = Limits::default();
let table = table();
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let source = bytes.clone();
let mut sink = BytesSink::default();
let mut decoder = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type: 0,
offset: 0,
length: bytes.len() as u64,
},
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
)
.unwrap();
for _ in 0..3 {
assert!(decoder.decode_next_row().unwrap());
}
let report = decoder.finish().unwrap();
assert!(report.progress.arithmetic.synthesized_inputs > 0);
assert_eq!(report.progress.arithmetic.input_offset, DIB_BYTES + 1);
}
#[test]
fn report_separates_consumed_and_virtual_bytes() {
let bytes = image(1, 1, &[0; 10]);
let limits = Limits::default();
let table = table();
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let source = bytes.clone();
let mut sink = BytesSink::default();
let mut decoder = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type: 0,
offset: 0,
length: bytes.len() as u64,
},
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
)
.unwrap();
assert!(decoder.decode_next_row().unwrap());
let snapshot = decoder.finish().unwrap().progress.arithmetic;
assert_eq!(snapshot.input_offset, DIB_BYTES + 3);
assert_eq!(snapshot.synthesized_inputs, 0);
}
#[test]
fn input_mutations_return_without_panic() {
let original = image(9, 2, &[0x90, 0, 0]);
let limits = Limits::default();
let table = table();
for seed in 0..128_usize {
let mut bytes = original.clone();
let index = (seed * 17) % bytes.len();
bytes[index] ^= 1 << (seed % 8);
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let source = bytes.clone();
let mut sink = BytesSink::default();
if let Ok(mut decoder) = Type0Decoder::new(
Payload::from(&source[..]),
Type0Span {
record_type: 0,
offset: 0,
length: bytes.len() as u64,
},
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
) {
for _ in 0..2 {
if decoder.decode_next_row().is_err() {
break;
}
}
}
}
}
fn span_of(bytes: &[u8]) -> Type0Span {
Type0Span {
record_type: 0,
offset: 0,
length: bytes.len() as u64,
}
}
#[test]
fn preflight_rejects_cancelled_short_and_oversized_spans_before_reading() {
let bytes = image(3, 1, &white(3, 1));
let table = table();
let limits = Limits::default();
let oversized = Limits {
max_input_bytes: bytes.len() as u64 - 1,
..limits
};
let short = Type0Span {
record_type: 0,
offset: 5,
length: DIB_BYTES,
};
let cases = [
(span_of(&bytes), limits, true),
(short, limits, false),
(span_of(&bytes), oversized, false),
];
let mut errors = Vec::new();
for (span, limits, cancel) in cases {
let source = bytes.clone();
let mut contexts = ContextBank::new(CONTEXT_COUNT, &Limits::default()).unwrap();
let mut sink = BytesSink::default();
let cancellation = if cancel {
CancelAfter::always()
} else {
CancelAfter::never()
};
let error = Type0Decoder::new(
Payload::from(&source[..]),
span,
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Checks(&cancellation),
)
.err()
.expect("preflight must fail");
assert!(sink.bytes.is_empty());
errors.push(error);
}
assert_eq!(errors[0].offset, Some(0));
assert!(matches!(
errors[0],
Error {
kind: ErrorKind::Cancelled,
..
}
));
assert_eq!(errors[1].offset, Some(5 + DIB_BYTES));
assert!(matches!(
errors[1],
Error {
kind: ErrorKind::Truncated { .. },
reason: "DIB and coded bytes",
..
}
));
assert_eq!(errors[2].offset, Some(0));
assert!(matches!(errors[2], Error { kind: ErrorKind::LimitExceeded {
resource: "image span bytes",
limit,
attempted,
}, .. } if limit == bytes.len() as u64 - 1 && attempted == bytes.len() as u64));
}
#[test]
fn maximal_dimensions_are_refused_by_the_pixel_limit_without_allocating() {
let bytes = image(i32::MAX as u32, i32::MAX as u32, &[]);
let header: &[u8; 48] = (&bytes[..48]).try_into().unwrap();
let error = checked_info(
header,
Type0Span {
record_type: 0,
offset: 100,
length: 49,
},
&Limits::default(),
)
.unwrap_err();
assert_eq!(error.offset, Some(104));
assert!(matches!(
error,
Error {
kind: ErrorKind::LimitExceeded {
resource: "image pixels",
..
},
..
}
));
}
fn decode_every_row<W: Write, C: Cancellation>(
mut decoder: Type0Decoder<'_, W, C>,
) -> crate::Result<Type0Report> {
while decoder.decode_next_row()? {}
decoder.finish()
}
fn decode_with_checks(bytes: &[u8], cancel: &CancelAfter) -> (crate::Result<Type0Report>, Vec<u8>) {
let limits = Limits {
io_chunk_bytes: 1,
..Limits::default()
};
let table = table();
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let source = bytes.to_vec();
let mut sink = BytesSink::default();
let result = Type0Decoder::new(
Payload::from(&source[..]),
span_of(bytes),
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Checks(cancel),
)
.and_then(decode_every_row);
(result, sink.bytes)
}
#[test]
fn cancellation_observed_at_any_check_is_reported_as_cancelled() {
let bytes = image(3, 2, &white(3, 2));
let baseline = CancelAfter::never();
let (report, output) = decode_with_checks(&bytes, &baseline);
assert_eq!(report.unwrap().progress.rows_written, 2);
assert_eq!(output, [0; 8]);
let checks = baseline.queries();
assert!(checks > 10, "only {checks} cancellation checks");
let mut previous_bytes = 0;
for allowed in 0..checks {
let cancel = CancelAfter::new(allowed);
let (result, output) = decode_with_checks(&bytes, &cancel);
let error = result.expect_err("every check point must stop the image");
assert!(
matches!(
error,
Error {
kind: ErrorKind::Cancelled,
..
}
),
"check {} returned {error}",
allowed + 1
);
assert!((previous_bytes..=8).contains(&output.len()));
previous_bytes = output.len();
}
assert_eq!(previous_bytes, 8);
}
#[test]
fn early_finish_is_refused_before_the_terminal_check() {
let bytes = image(3, 2, &white(3, 2));
let limits = Limits::default();
let table = table();
let checks = CancelAfter::never();
let cancel = Cancel::Checks(&checks);
let decoder = |fail: bool| {
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let source = bytes.clone();
let mut sink = BytesSink {
fail,
..BytesSink::default()
};
let mut decoder = Type0Decoder::new(
Payload::from(&source[..]),
span_of(&bytes),
&table,
&mut contexts,
&mut sink,
&limits,
&cancel,
)
.unwrap();
let row = decoder.decode_next_row();
(
row.map_err(|error| stage_of(&error)),
decoder.finish().unwrap_err(),
)
};
let (row, incomplete) = decoder(false);
assert!(matches!(row, Ok(true)));
assert!(incomplete.reason == "not all type-0 rows were decoded");
let (row, failed) = decoder(true);
assert!(matches!(row, Err(Some(Hnc8Stage::Pdf))));
assert!(failed.reason == "not all type-0 rows were decoded");
}
#[test]
fn final_flush_failure_and_late_cancellation_keep_row_progress() {
let bytes = image(5, 1, &white(5, 1));
let limits = Limits::default();
let table = table();
let failing: fn() -> Error = || Error::invalid("synthetic flush failure");
let cancelled: fn() -> Error = || Error::cancelled();
let cases = [
(Some(failing), false, true),
(Some(cancelled), false, false),
(None, true, false),
];
for (flush_error, cancel_on_flush, expect_sink_error) in cases {
let flag = Rc::new(Cell::new(false));
let cancel = Cancel::Flag(flag.clone());
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let source = bytes.clone();
let mut sink = BytesSink {
max_write: usize::MAX,
flush_error,
cancel_on_flush: cancel_on_flush.then_some(flag),
..BytesSink::default()
};
let mut decoder = Type0Decoder::new(
Payload::from(&source[..]),
span_of(&bytes),
&table,
&mut contexts,
&mut sink,
&limits,
&cancel,
)
.unwrap();
assert!(decoder.decode_next_row().unwrap());
let error = decoder.finish().unwrap_err();
assert_eq!(error.offset, Some(DIB_BYTES + 3));
if expect_sink_error {
assert_eq!(stage_of(&error), Some(Hnc8Stage::Pdf));
assert_eq!(error.reason, "synthetic flush failure");
} else {
assert!(matches!(
error,
Error {
kind: ErrorKind::Cancelled,
..
}
));
}
assert_eq!(sink.bytes, [0; 4]);
}
}
#[test]
fn finish_rejects_a_failed_decoder_and_cancellation_before_flush() {
let bytes = image(5, 1, &white(5, 1));
let limits = Limits::default();
let table = table();
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let source = bytes.clone();
let mut sink = BytesSink {
fail: true,
..BytesSink::default()
};
let mut decoder = Type0Decoder::new(
Payload::from(&source[..]),
span_of(&bytes),
&table,
&mut contexts,
&mut sink,
&limits,
&Cancel::Never,
)
.unwrap();
assert!(stage_of(&decoder.decode_next_row().unwrap_err()) == Some(Hnc8Stage::Pdf));
let error = decoder.finish().unwrap_err();
assert!(error.reason == "not all type-0 rows were decoded");
let flag = Rc::new(Cell::new(false));
let cancel = Cancel::Flag(flag.clone());
let mut contexts = ContextBank::new(CONTEXT_COUNT, &limits).unwrap();
let source = bytes.clone();
let mut sink = BytesSink {
max_write: usize::MAX,
flush_error: Some(|| Error::invalid("flush must not run after cancellation")),
..BytesSink::default()
};
let mut decoder = Type0Decoder::new(
Payload::from(&source[..]),
span_of(&bytes),
&table,
&mut contexts,
&mut sink,
&limits,
&cancel,
)
.unwrap();
assert!(decoder.decode_next_row().unwrap());
flag.set(true);
let error = decoder.finish().unwrap_err();
assert!(matches!(
error,
Error {
kind: ErrorKind::Cancelled,
..
}
));
}