use pdfrum_common::{DiagKind, Diagnostics, Limits, Severity};
use weezl::{BitOrder, LzwStatus, decode::Configuration};
use crate::Error;
const CHUNK: usize = 64 * 1024;
pub fn decode_lzw(
input: &[u8],
early_change: bool,
limits: &Limits,
diags: &mut Diagnostics,
) -> Result<Vec<u8>, Error> {
let configuration = if early_change {
Configuration::with_tiff_size_switch(BitOrder::Msb, 8)
} else {
Configuration::new(BitOrder::Msb, 8)
};
let mut decoder = configuration.build();
let mut out: Vec<u8> = Vec::new();
let mut consumed = 0usize;
let mut written = 0usize;
let mut complete = false;
let mut rejected = false;
loop {
let room = limits.max_decoded_stream_len.saturating_sub(written);
if room == 0 {
return Err(Error::OutputTooLarge {
limit: limits.max_decoded_stream_len,
});
}
out.resize(written.saturating_add(CHUNK.min(room)), 0);
let Some(tail) = out.get_mut(written..) else {
break;
};
let result = decoder.decode_bytes(input.get(consumed..).unwrap_or_default(), tail);
consumed = consumed.saturating_add(result.consumed_in);
written = written.saturating_add(result.consumed_out);
match result.status {
Ok(LzwStatus::Done) => {
complete = true;
break;
}
Ok(LzwStatus::NoProgress) => break,
Ok(LzwStatus::Ok) => {
if result.consumed_in == 0 && result.consumed_out == 0 {
break;
}
}
Err(_) => {
rejected = true;
break;
}
}
}
out.truncate(written);
if rejected && out.is_empty() {
return Err(Error::LzwMalformed(
"the first code is a dictionary reference",
));
}
if out.is_empty() {
return Err(Error::LzwMalformed("the stream decodes to no bytes"));
}
if !complete {
diags.record(
Severity::Recovered,
DiagKind::UndecodableStream,
Some(consumed as u64),
);
}
out.shrink_to_fit();
Ok(out)
}
#[cfg(test)]
mod tests {
use super::decode_lzw;
use crate::Error;
use pdfrum_common::{Diagnostics, Limits};
use weezl::{BitOrder, encode::Encoder};
fn encode(payload: &[u8], early_change: bool) -> Vec<u8> {
let mut encoder = if early_change {
Encoder::with_tiff_size_switch(BitOrder::Msb, 8)
} else {
Encoder::new(BitOrder::Msb, 8)
};
encoder.encode(payload).expect("encodes")
}
fn decode(input: &[u8], early_change: bool) -> Result<(Vec<u8>, usize), Error> {
let mut diags = Diagnostics::default();
decode_lzw(input, early_change, &Limits::default(), &mut diags)
.map(|out| (out, diags.len()))
}
fn pack9(codes: &[u16]) -> Vec<u8> {
let mut bytes = vec![0u8; codes.len() * 9 / 8 + 2];
for (n, &code) in codes.iter().enumerate() {
for bit in 0..9usize {
if (code >> (8 - bit)) & 1 == 1 {
let at = n * 9 + bit;
if let Some(byte) = bytes.get_mut(at / 8) {
*byte |= 0x80 >> (at % 8);
}
}
}
}
bytes
}
#[test]
fn a_complete_stream_round_trips_under_both_early_change_settings() {
let payload: Vec<u8> = (0..3000u32).map(|i| (i % 251) as u8).collect();
for early_change in [false, true] {
let stream = encode(&payload, early_change);
let (out, diags) = decode(&stream, early_change).expect("decodes");
assert_eq!(out, payload, "early_change={early_change}");
assert_eq!(diags, 0);
}
}
#[test]
fn early_change_changes_the_decoding_of_a_long_stream() {
let payload: Vec<u8> = (0..6000u32).map(|i| (i % 37) as u8).collect();
let early = encode(&payload, true);
let late = encode(&payload, false);
assert_ne!(early, late, "the encodings differ past code 511");
let (right, _) = decode(&early, true).expect("decodes");
assert_eq!(right, payload);
let wrong = decode(&early, false).map(|(out, _)| out);
assert!(
wrong.as_ref().map_or(true, |out| *out != payload),
"the wrong rule must not reproduce the payload"
);
}
#[test]
fn a_truncated_stream_keeps_its_prefix_without_failing() {
let payload: Vec<u8> = (0..3000u32).map(|i| (i % 251) as u8).collect();
let stream = encode(&payload, true);
let cut = stream.get(..stream.len() / 2).expect("a prefix");
let (out, diags) = decode(cut, true).expect("truncation is never an error");
assert!(!out.is_empty());
assert!(payload.starts_with(&out), "the prefix is genuine");
assert_eq!(diags, 1, "the truncation is recorded");
}
#[test]
fn a_trailing_partial_code_is_discarded_silently() {
let payload = b"the quick brown fox".to_vec();
let mut stream = encode(&payload, true);
stream.push(0xff);
let (out, _) = decode(&stream, true).expect("decodes");
assert_eq!(out, payload);
}
#[test]
fn a_dictionary_code_before_any_literal_is_rejected() {
let stream = pack9(&[300]);
assert_eq!(
decode(&stream, true),
Err(Error::LzwMalformed(
"the first code is a dictionary reference"
))
);
}
#[test]
fn a_bare_end_of_data_code_is_a_failure() {
assert_eq!(
decode(&pack9(&[256, 257]), true),
Err(Error::LzwMalformed("the stream decodes to no bytes"))
);
assert_eq!(
decode(&[], true),
Err(Error::LzwMalformed("the stream decodes to no bytes"))
);
}
#[test]
fn a_stream_without_a_leading_clear_code_still_decodes() {
let (out, _) =
decode(&pack9(&[u16::from(b'A'), u16::from(b'B'), 257]), true).expect("decodes");
assert_eq!(out, b"AB");
}
#[test]
fn a_dictionary_filled_past_its_last_code_keeps_decoding() {
let payload: Vec<u8> = (0..30_000u32)
.map(|i| (i.wrapping_mul(7) % 253) as u8)
.collect();
let stream = encode(&payload, true);
let (out, diags) = decode(&stream, true).expect("a frozen table is not an error");
assert_eq!(out, payload);
assert_eq!(diags, 0);
}
#[test]
fn the_output_limit_is_enforced() {
let payload = vec![0u8; 4 * 1024 * 1024];
let stream = encode(&payload, true);
let limits = Limits {
max_decoded_stream_len: 1024,
..Limits::default()
};
let mut diags = Diagnostics::default();
assert_eq!(
decode_lzw(&stream, true, &limits, &mut diags),
Err(Error::OutputTooLarge { limit: 1024 })
);
}
#[test]
fn arbitrary_bytes_never_panic() {
let mut diags = Diagnostics::default();
for seed in 0..64u8 {
let junk: Vec<u8> = (0..200u8)
.map(|i| i.wrapping_mul(seed).wrapping_add(13))
.collect();
for early_change in [false, true] {
let _ = decode_lzw(&junk, early_change, &Limits::default(), &mut diags);
}
}
}
}