use std::fmt::Debug;
use std::fmt::Formatter;
use crate::BlockSize;
use crate::buffer::filled;
use crate::cm::CmCoder;
use crate::crc32;
use crate::error::Error;
use crate::error::Result;
use crate::lzp;
use crate::rle;
use crate::sais;
use crate::sais::BwtScratch;
use super::MIN_CODED_LEN;
use super::STORED_MARKER;
use super::STORED_PREFIX_LEN;
use super::model::Model;
pub struct BlockEncoder {
block_size: BlockSize,
buf_a: Vec<u8>,
buf_b: Vec<u8>,
lzp_table: Box<lzp::HashTable>,
coder: Box<CmCoder>,
bwt_scratch: BwtScratch,
}
impl Debug for BlockEncoder {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BlockEncoder")
.field("block_size", &self.block_size)
.finish_non_exhaustive()
}
}
impl BlockEncoder {
pub fn new(block_size: BlockSize) -> Result<Self> {
let capacity = crate::compress_bound(block_size.as_usize());
Ok(Self {
block_size,
buf_a: filled(capacity, 0).ok_or(Error::OutOfMemory)?,
buf_b: filled(capacity, 0).ok_or(Error::OutOfMemory)?,
lzp_table: lzp::new_table()?,
coder: Box::new(CmCoder::new()),
bwt_scratch: BwtScratch::default(),
})
}
#[must_use]
#[inline]
pub const fn block_size(&self) -> BlockSize {
self.block_size
}
#[must_use = "encode output must be written to the frame"]
#[inline]
pub fn encode_block(&mut self, data: &[u8]) -> Result<Vec<u8>> {
let mut out = Vec::new();
self.encode_block_into(data, &mut out)?;
Ok(out)
}
pub fn encode_block_into(&mut self, data: &[u8], out: &mut Vec<u8>) -> Result<()> {
let limit = self.block_size.as_usize();
if data.len() > limit {
return Err(Error::BlockTooLarge);
}
if data.len() < MIN_CODED_LEN {
return encode_stored(data, out);
}
let crc = crc32::sum(1, data);
let mut src = StageBuffer::Input;
let mut cur_len = data.len();
let mut model = Model(0);
let mut lzp_size: u32 = 0;
let mut rle_size: u32 = 0;
let (input, stage_out, next) =
stage_buffers(src, cur_len, data, &mut self.buf_a, &mut self.buf_b);
if let Some(len) = rle::encode_into(input, stage_out) {
rle_size = u32::try_from(len).map_err(|_| Error::BlockTooLarge)?;
src = next;
cur_len = len;
model.set_rle();
}
let (input, stage_out, next) =
stage_buffers(src, cur_len, data, &mut self.buf_a, &mut self.buf_b);
if let Some(len) = lzp::encode_into(input, stage_out, &mut self.lzp_table) {
lzp_size = u32::try_from(len).map_err(|_| Error::BlockTooLarge)?;
src = next;
cur_len = len;
model.set_lzp();
}
let (input, bwt_out, _) =
stage_buffers(src, cur_len, data, &mut self.buf_a, &mut self.buf_b);
let bwt_idx = sais::bwt_with(input, bwt_out, &mut self.bwt_scratch)?;
out.try_reserve(model.header_len() + crate::compress_bound(cur_len))
.map_err(|_| Error::OutOfMemory)?;
out.extend_from_slice(&crc.to_le_bytes());
out.extend_from_slice(&bwt_idx.to_le_bytes());
out.push(model.0);
if model.has_lzp() {
out.extend_from_slice(&lzp_size.to_le_bytes());
}
if model.has_rle() {
out.extend_from_slice(&rle_size.to_le_bytes());
}
self.coder.encode(&bwt_out[..cur_len], out);
Ok(())
}
}
#[derive(Clone, Copy)]
enum StageBuffer {
Input,
A,
B,
}
fn stage_buffers<'a>(
src: StageBuffer,
len: usize,
data: &'a [u8],
a: &'a mut [u8],
b: &'a mut [u8],
) -> (&'a [u8], &'a mut [u8], StageBuffer) {
match src {
StageBuffer::Input => (data, a, StageBuffer::A),
StageBuffer::A => (&a[..len], b, StageBuffer::B),
StageBuffer::B => (&b[..len], a, StageBuffer::A),
}
}
fn encode_stored(data: &[u8], out: &mut Vec<u8>) -> Result<()> {
out.try_reserve(data.len() + STORED_PREFIX_LEN)
.map_err(|_| Error::OutOfMemory)?;
out.extend_from_slice(&crc32::sum(1, data).to_le_bytes());
out.extend_from_slice(&STORED_MARKER.to_le_bytes());
out.extend_from_slice(data);
Ok(())
}
#[cfg(test)]
mod tests {
use super::BlockEncoder;
use super::MIN_CODED_LEN;
use crate::BlockSize;
fn small_state() -> BlockEncoder {
BlockEncoder::new(BlockSize::MIN).unwrap()
}
#[test]
fn stored_layout_matches_reference() {
let out = small_state().encode_block(b"A").unwrap();
assert_eq!(out.len(), 9);
assert_eq!(
u32::from_le_bytes(out[..4].try_into().unwrap()),
0x417B_1DBC
);
assert_eq!(out[4..8], [0xFF; 4]);
assert_eq!(out[8..], [b'A']);
}
#[test]
fn small_block_boundary_routes_stored_vs_coded() {
let mut state = small_state();
for len in 0..MIN_CODED_LEN {
let out = state.encode_block(&vec![b'q'; len]).unwrap();
assert_eq!(out.len(), len + 8, "stored len {len}");
assert_eq!(out[4..8], [0xFF; 4], "stored marker len {len}");
}
let out = state.encode_block(&[b'q'; MIN_CODED_LEN]).unwrap();
assert_eq!(out[8], 0x04, "RLE-only model {}", out[8]);
assert_ne!(out[4..8], [0xFF; 4]);
}
#[test]
fn oversize_input_fails_data_too_big() {
let mut state = small_state();
let big = vec![0_u8; usize::try_from(BlockSize::MIN.get()).unwrap() + 1];
assert!(matches!(
state.encode_block(&big),
Err(crate::error::Error::BlockTooLarge)
));
}
#[test]
fn repeated_bytes_set_rle_model_bit() {
let mut state = small_state();
let out = state.encode_block(&[b'A'; 1000]).unwrap();
assert_eq!(out[8] & 0x04, 0x04, "RLE bit set, model {}", out[8]);
}
#[test]
fn periodic_text_sets_lzp_without_rle() {
let phrase: Vec<u8> = (0..50_u8).collect();
let mut input = Vec::new();
for _ in 0..4 {
input.extend_from_slice(&phrase);
}
assert!(input.len() >= 72);
let out = small_state().encode_block(&input).unwrap();
assert_eq!(out[8] & 0x06, 0x02, "LZP-only model {}", out[8]);
}
#[test]
fn random_bytes_take_plain_coded_path() {
let mut seed = 0x00C0_FFEE_u32;
let input: Vec<u8> = (0..1000)
.map(|_| {
seed = seed.wrapping_mul(1_103_515_245).wrapping_add(12_345);
u8::try_from((seed >> 16) & 0xFF).expect("masked byte fits")
})
.collect();
let out = small_state().encode_block(&input).unwrap();
assert_eq!(out[8], 0, "plain model {}", out[8]);
assert!(out.len() > 9);
}
#[test]
fn state_reuse_gives_identical_bytes() {
let mut state = small_state();
let first = state.encode_block(&[b'A'; 1000]).unwrap();
let _ = state.encode_block(&[b'B'; 500]).unwrap();
let again = state.encode_block(&[b'A'; 1000]).unwrap();
assert_eq!(first, again);
}
#[test]
fn block_size_accessor_matches_constructor() {
assert_eq!(small_state().block_size(), BlockSize::MIN);
}
}