use crate::decoders::{StreamDecoder, check_output_cap};
use crate::error::{Error, Result};
pub struct RunLengthDecoder;
impl StreamDecoder for RunLengthDecoder {
fn decode(&self, input: &[u8], max_output_bytes: usize) -> Result<Vec<u8>> {
let mut output = Vec::new();
let mut i = 0;
while i < input.len() {
let length = input[i];
i += 1;
match length {
0..=127 => {
let count = length as usize + 1;
if i + count > input.len() {
return Err(Error::Decode(format!(
"RunLengthDecode: not enough data for literal run (need {}, have {})",
count,
input.len() - i
)));
}
output.extend_from_slice(&input[i..i + count]);
i += count;
}
128 => {
break;
}
129..=255 => {
let count = 257 - length as usize;
if i >= input.len() {
return Err(Error::Decode("RunLengthDecode: missing byte for run".to_string()));
}
let byte = input[i];
i += 1;
output.resize(output.len() + count, byte);
}
}
check_output_cap("RunLengthDecode", output.len(), max_output_bytes)?;
}
Ok(output)
}
fn name(&self) -> &str {
"RunLengthDecode"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_runlength_decode_literal() {
let decoder = RunLengthDecoder;
let input = vec![4, b'H', b'e', b'l', b'l', b'o'];
let output = decoder.decode(&input, 0).unwrap();
assert_eq!(output, b"Hello");
}
#[test]
fn test_runlength_decode_run() {
let decoder = RunLengthDecoder;
let input = vec![252, b'A'];
let output = decoder.decode(&input, 0).unwrap();
assert_eq!(output, b"AAAAA");
}
#[test]
fn test_runlength_decode_mixed() {
let decoder = RunLengthDecoder;
let input = vec![1, b'H', b'i', 254, b'X'];
let output = decoder.decode(&input, 0).unwrap();
assert_eq!(output, b"HiXXX");
}
#[test]
fn test_runlength_decode_eod_marker() {
let decoder = RunLengthDecoder;
let input = vec![1, b'H', b'i', 128, 99, 99, 99];
let output = decoder.decode(&input, 0).unwrap();
assert_eq!(output, b"Hi");
}
#[test]
fn test_runlength_decode_max_literal() {
let decoder = RunLengthDecoder;
let mut input = vec![127];
input.extend_from_slice(&[b'A'; 128]);
let output = decoder.decode(&input, 0).unwrap();
assert_eq!(output.len(), 128);
assert_eq!(output, vec![b'A'; 128]);
}
#[test]
fn test_runlength_decode_max_run() {
let decoder = RunLengthDecoder;
let input = vec![129, b'B'];
let output = decoder.decode(&input, 0).unwrap();
assert_eq!(output.len(), 128);
assert_eq!(output, vec![b'B'; 128]);
}
#[test]
fn test_runlength_decode_empty() {
let decoder = RunLengthDecoder;
let input = vec![];
let output = decoder.decode(&input, 0).unwrap();
assert_eq!(output, b"");
}
#[test]
fn test_runlength_decode_insufficient_data_literal() {
let decoder = RunLengthDecoder;
let input = vec![4, b'A', b'B', b'C'];
let result = decoder.decode(&input, 0);
assert!(result.is_err());
}
#[test]
fn test_runlength_decode_missing_run_byte() {
let decoder = RunLengthDecoder;
let input = vec![252];
let result = decoder.decode(&input, 0);
assert!(result.is_err());
}
#[test]
fn test_runlength_decoder_name() {
let decoder = RunLengthDecoder;
assert_eq!(decoder.name(), "RunLengthDecode");
}
#[test]
fn test_runlength_decode_aborts_before_building_full_expansion() {
const MAX_RLE_UNIT_BYTES: usize = 128;
let decoder = RunLengthDecoder;
let unit_count = 2_000_000;
let input = [129u8, b'A'].repeat(unit_count);
let full_expansion = unit_count * MAX_RLE_UNIT_BYTES;
let cap = 1_000_000;
let start = std::time::Instant::now();
let result = decoder.decode(&input, cap);
let elapsed = start.elapsed();
let err = result.expect_err("decode must reject output exceeding the cap");
let message = err.to_string();
let reported_bytes: usize = message
.split("output size ")
.nth(1)
.and_then(|rest| rest.split(' ').next())
.and_then(|n| n.parse().ok())
.unwrap_or_else(|| panic!("error message did not report an observed size: {message}"));
assert!(
reported_bytes < cap + MAX_RLE_UNIT_BYTES,
"decode reported {reported_bytes} bytes at abort, which is more than one RLE unit \
past the {cap} byte cap; the decoder built more output than the cap allows before checking"
);
assert!(
reported_bytes < full_expansion / 10,
"decode reported {reported_bytes} bytes, within an order of magnitude of the full \
{full_expansion} byte expansion; the abort did not happen early"
);
assert!(
elapsed < std::time::Duration::from_millis(500),
"decode took {elapsed:?} to abort a 1 MB-capped stream; an unbounded decode of all \
{unit_count} units would need to process 256x more data than this took"
);
}
}