use crate::error::NsfError;
const MIN_SUPPORTED_MODE: u8 = 3;
const START_BIT_INDEX: u8 = 3;
const END_OF_STREAM_LENGTH: u32 = 0x0102;
const MIN_MATCH_LENGTH: u32 = 2;
const MAX_RUN_WIDTH: u32 = 9;
struct BitReader<'a> {
data: &'a [u8],
pos: usize,
bit: u8,
}
impl<'a> BitReader<'a> {
fn new(data: &'a [u8], start_bit: u8) -> Self {
Self {
data,
pos: 0,
bit: start_bit,
}
}
fn exhausted(&self) -> bool {
self.pos >= self.data.len()
}
fn read_bit(&mut self) -> Result<u8, NsfError> {
let byte = *self.data.get(self.pos).ok_or(NsfError::DecompressionFailed {
detail: "compressed stream truncated mid-token",
})?;
let value = (byte >> self.bit) & 0x01;
self.bit += 1;
if self.bit >= 8 {
self.bit = 0;
self.pos += 1;
}
Ok(value)
}
fn read_bits(&mut self, n: u32) -> Result<u32, NsfError> {
let mut value = 0u32;
for i in 0..n {
value |= u32::from(self.read_bit()?) << i;
}
Ok(value)
}
fn read_run(&mut self) -> Result<u32, NsfError> {
let mut zeros = 0u32;
while zeros < (MAX_RUN_WIDTH - 1) && self.read_bit()? == 0 {
zeros += 1;
}
if zeros == MAX_RUN_WIDTH - 1 {
let _ = self.read_bit()?;
}
let w = zeros + 1;
let value = self.read_bits(w)?;
Ok(((1u32 << w) - 1) + value)
}
}
pub fn decompress(compressed: &[u8], expected_size: usize) -> Result<Vec<u8>, NsfError> {
let first = *compressed.first().ok_or(NsfError::DecompressionFailed {
detail: "empty compressed stream",
})?;
if (first & 0x07) <= MIN_SUPPORTED_MODE {
return Err(NsfError::CompressionUnsupported {
structure: "CX stream",
compression_type: u16::from(first & 0x07),
});
}
let mut out: Vec<u8> = Vec::with_capacity(expected_size);
let mut reader = BitReader::new(compressed, START_BIT_INDEX);
while !reader.exhausted() {
let tag = reader.read_bit()?;
if tag == 0 {
let byte = reader.read_bits(8)? as u8;
if out.len() >= expected_size {
return Err(NsfError::DecompressionFailed {
detail: "output exceeded declared uncompressed size",
});
}
out.push(byte);
continue;
}
let mut length = if reader.read_bit()? == 0 {
reader.read_run()?
} else {
0
};
length += MIN_MATCH_LENGTH;
if length >= END_OF_STREAM_LENGTH {
break;
}
let mut offset = 0u32;
if length > MIN_MATCH_LENGTH {
if reader.read_bit()? == 0 {
offset = reader.read_run()? << 8;
}
}
offset |= reader.read_bits(8)?;
let offset = offset as usize;
let length = length as usize;
if offset == 0 || offset > out.len() {
return Err(NsfError::DecompressionFailed {
detail: "back-reference offset points before start of output",
});
}
if out.len() + length > expected_size {
return Err(NsfError::DecompressionFailed {
detail: "back-reference would exceed declared uncompressed size",
});
}
for _ in 0..length {
let src = out.len() - offset;
out.push(out[src]);
}
}
Ok(out)
}
pub fn decompress_chained(body: &[u8], expected_size: usize) -> Result<Vec<u8>, NsfError> {
let mut out: Vec<u8> = Vec::with_capacity(expected_size);
let mut cursor = 0usize;
while out.len() < expected_size {
let Some(len_bytes) = body.get(cursor..cursor + 4) else {
break;
};
let seg_len =
u32::from_le_bytes([len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]]) as usize;
if seg_len == 0 {
break;
}
let seg_start = cursor + 4;
let seg_end = seg_start
.checked_add(seg_len)
.ok_or(NsfError::DecompressionFailed {
detail: "CX segment length overflow",
})?;
let seg = body.get(seg_start..seg_end).ok_or(NsfError::TooShort {
actual: body.len(),
required: seg_end,
})?;
let remaining = expected_size - out.len();
let part = decompress(seg, remaining)?;
if part.is_empty() {
break;
}
out.extend_from_slice(&part);
cursor = seg_end;
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_empty_stream() {
assert!(matches!(
decompress(&[], 16),
Err(NsfError::DecompressionFailed { .. })
));
}
#[test]
fn rejects_unsupported_mode_header() {
assert!(matches!(
decompress(&[0b0000_0010, 0, 0], 16),
Err(NsfError::CompressionUnsupported { .. })
));
}
#[test]
fn rejects_backreference_before_output_start() {
let stream = [0b0001_1111u8, 0xFF, 0xFF, 0xFF];
assert!(matches!(
decompress(&stream, 64),
Err(NsfError::DecompressionFailed { .. })
));
}
#[test]
fn bitreader_reads_lsb_first() {
let data = [0b1011_0100u8];
let mut r = BitReader::new(&data, 0);
assert_eq!(r.read_bit().unwrap(), 0);
assert_eq!(r.read_bit().unwrap(), 0);
assert_eq!(r.read_bit().unwrap(), 1);
assert_eq!(r.read_bits(5).unwrap(), 0b1011_0); }
#[test]
fn bitreader_exhaustion_is_detected() {
let data = [0xFFu8];
let mut r = BitReader::new(&data, 0);
for _ in 0..8 {
r.read_bit().unwrap();
}
assert!(r.exhausted());
assert!(matches!(
r.read_bit(),
Err(NsfError::DecompressionFailed { .. })
));
}
}