Skip to main content

bzip3_rs/block/
encode.rs

1//! Forward block encode pipeline with stage selection and stored-raw routing.
2
3use std::fmt::Debug;
4use std::fmt::Formatter;
5
6use crate::BlockSize;
7use crate::buffer::filled;
8use crate::cm::CmCoder;
9use crate::crc32;
10use crate::error::Error;
11use crate::error::Result;
12use crate::lzp;
13use crate::rle;
14use crate::sais;
15use crate::sais::BwtScratch;
16
17use super::MIN_CODED_LEN;
18use super::STORED_MARKER;
19use super::STORED_PREFIX_LEN;
20use super::model::Model;
21
22/// Block encoder with constructor-owned working memory.
23///
24/// The two swap buffers each hold [`compress_bound`](crate::compress_bound) of the block
25/// size, so every intermediate stage result fits. The hash table and
26/// the entropy coder state persist across calls but reset per block,
27/// exactly like the reference. Slice-based calls borrow the input and
28/// return an owned payload.
29///
30/// # Examples
31///
32/// Build a streaming frame with one block, then decode it with
33/// [`stream::decompress`](crate::stream::decompress).
34///
35/// ```
36/// use bzip3_rs::{BlockEncoder, BlockHeader, BlockSize, Jobs, StreamHeader, stream};
37///
38/// # fn main() -> Result<(), bzip3_rs::error::Error> {
39/// let input = b"hello world ".repeat(1000);
40/// let size = BlockSize::MIN;
41///
42/// let mut encoder = BlockEncoder::new(size)?;
43/// let payload = encoder.encode_block(&input)?;
44///
45/// let mut frame = StreamHeader::new(size).to_bytes().to_vec();
46/// frame.extend_from_slice(&BlockHeader::for_block(payload.len(), input.len())?.to_bytes());
47/// frame.extend_from_slice(&payload);
48///
49/// let mut plain = Vec::new();
50/// stream::decompress(frame.as_slice(), &mut plain, Jobs::SERIAL)?;
51/// assert_eq!(plain, input);
52/// # Ok(())
53/// # }
54/// ```
55pub struct BlockEncoder {
56    /// Maximum input bytes accepted by [`BlockEncoder::encode_block`].
57    block_size: BlockSize,
58    /// First ping-pong stage buffer.
59    buf_a: Vec<u8>,
60    /// Second ping-pong stage buffer.
61    buf_b: Vec<u8>,
62    /// LZP hash table, cleared by the stage on every call.
63    lzp_table: Box<lzp::HashTable>,
64    /// Entropy coder state, reset by the stage on every call.
65    /// Boxed like the reference heap state, so the encoder struct
66    /// stays small on the stack.
67    coder: Box<CmCoder>,
68    /// Suffix sort scratch, kept between blocks.
69    bwt_scratch: BwtScratch,
70}
71
72impl Debug for BlockEncoder {
73    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
74        f.debug_struct("BlockEncoder")
75            .field("block_size", &self.block_size)
76            .finish_non_exhaustive()
77    }
78}
79
80impl BlockEncoder {
81    /// Build a block encoder for `block_size`, allocating working memory once.
82    ///
83    /// # Errors
84    ///
85    /// Returns [`Error::OutOfMemory`] when any working buffer allocation fails.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use bzip3_rs::{BlockEncoder, BlockSize};
91    ///
92    /// # fn main() -> Result<(), bzip3_rs::error::Error> {
93    /// let encoder = BlockEncoder::new(BlockSize::MIN)?;
94    /// assert_eq!(encoder.block_size(), BlockSize::MIN);
95    /// # Ok(())
96    /// # }
97    /// ```
98    pub fn new(block_size: BlockSize) -> Result<Self> {
99        let capacity = crate::compress_bound(block_size.as_usize());
100        Ok(Self {
101            block_size,
102            buf_a: filled(capacity, 0).ok_or(Error::OutOfMemory)?,
103            buf_b: filled(capacity, 0).ok_or(Error::OutOfMemory)?,
104            lzp_table: lzp::new_table()?,
105            coder: Box::new(CmCoder::new()),
106            bwt_scratch: BwtScratch::default(),
107        })
108    }
109
110    /// Return the block size this block encoder was built for.
111    #[must_use]
112    #[inline]
113    pub const fn block_size(&self) -> BlockSize {
114        self.block_size
115    }
116
117    /// Encode one block into a new vector.
118    ///
119    /// Same output as [`BlockEncoder::encode_block_into`]. Use that call
120    /// to reuse one output vector across blocks.
121    ///
122    /// # Errors
123    ///
124    /// Returns the same errors as [`BlockEncoder::encode_block_into`].
125    ///
126    /// # Examples
127    ///
128    /// ```
129    /// use bzip3_rs::{BlockDecoder, BlockEncoder, BlockSize};
130    ///
131    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
132    /// let data = b"hello world ".repeat(100);
133    /// let mut encoder = BlockEncoder::new(BlockSize::MIN)?;
134    /// let payload = encoder.encode_block(&data)?;
135    ///
136    /// let mut decoder = BlockDecoder::new(BlockSize::MIN)?;
137    /// assert_eq!(decoder.decode_block(&payload, u32::try_from(data.len())?)?, data);
138    /// # Ok(())
139    /// # }
140    /// ```
141    #[must_use = "encode output must be written to the frame"]
142    #[inline]
143    pub fn encode_block(&mut self, data: &[u8]) -> Result<Vec<u8>> {
144        let mut out = Vec::new();
145        self.encode_block_into(data, &mut out)?;
146        Ok(out)
147    }
148
149    /// Encode one block through the full pipeline.
150    ///
151    /// Append the block to `out`. On an error, `out` keeps its length.
152    /// Inputs below 64 bytes take the stored-raw layout.
153    /// Larger inputs run RLE, then LZP, then BWT, then entropy coding,
154    /// with each of the first two stages kept only when it shrinks the
155    /// block. The header matches the reference byte for byte: CRC32,
156    /// BWT index, model byte, then the kept stage sizes in LZP, RLE
157    /// order, then the coded stream.
158    ///
159    /// # Errors
160    ///
161    /// Returns [`Error::BlockTooLarge`] when `data` exceeds the block size,
162    /// [`Error::OutOfMemory`] when the RLE stage cannot grow its output,
163    /// [`Error::BwtFailed`] when the transform fails.
164    ///
165    /// # Examples
166    ///
167    /// ```
168    /// use bzip3_rs::{BlockEncoder, BlockSize};
169    ///
170    /// # fn main() -> Result<(), bzip3_rs::error::Error> {
171    /// let mut encoder = BlockEncoder::new(BlockSize::MIN)?;
172    /// let mut out = Vec::new();
173    /// for chunk in [b"first block ".repeat(50), b"second block ".repeat(50)] {
174    ///     out.clear(); // Reuse one vector for every block.
175    ///     encoder.encode_block_into(&chunk, &mut out)?;
176    ///     assert!(!out.is_empty());
177    /// }
178    /// # Ok(())
179    /// # }
180    /// ```
181    pub fn encode_block_into(&mut self, data: &[u8], out: &mut Vec<u8>) -> Result<()> {
182        let limit = self.block_size.as_usize();
183        if data.len() > limit {
184            return Err(Error::BlockTooLarge);
185        }
186        if data.len() < MIN_CODED_LEN {
187            return encode_stored(data, out);
188        }
189        let crc = crc32::sum(1, data);
190        // Each kept stage output becomes the next stage input. The first
191        // stage reads `data`, so the input needs no copy. `src` names the
192        // buffer that holds the current stage output.
193        let mut src = StageBuffer::Input;
194        let mut cur_len = data.len();
195        let mut model = Model(0);
196        let mut lzp_size: u32 = 0;
197        let mut rle_size: u32 = 0;
198
199        // RLE forward transform, kept only when it shrinks the block.
200        let (input, stage_out, next) =
201            stage_buffers(src, cur_len, data, &mut self.buf_a, &mut self.buf_b);
202        if let Some(len) = rle::encode_into(input, stage_out) {
203            rle_size = u32::try_from(len).map_err(|_| Error::BlockTooLarge)?;
204            src = next;
205            cur_len = len;
206            model.set_rle();
207        }
208
209        // LZP forward transform, kept only when it shrinks the block.
210        // `None` is the reference `-1`: no saving, keep the input.
211        let (input, stage_out, next) =
212            stage_buffers(src, cur_len, data, &mut self.buf_a, &mut self.buf_b);
213        if let Some(len) = lzp::encode_into(input, stage_out, &mut self.lzp_table) {
214            lzp_size = u32::try_from(len).map_err(|_| Error::BlockTooLarge)?;
215            src = next;
216            cur_len = len;
217            model.set_lzp();
218        }
219
220        // Burrows-Wheeler transform into the free buffer.
221        let (input, bwt_out, _) =
222            stage_buffers(src, cur_len, data, &mut self.buf_a, &mut self.buf_b);
223        let bwt_idx = sais::bwt_with(input, bwt_out, &mut self.bwt_scratch)?;
224
225        // Write the header, then entropy-code the transform from the
226        // canonical start state straight after it.
227        out.try_reserve(model.header_len() + crate::compress_bound(cur_len))
228            .map_err(|_| Error::OutOfMemory)?;
229        out.extend_from_slice(&crc.to_le_bytes());
230        out.extend_from_slice(&bwt_idx.to_le_bytes());
231        out.push(model.0);
232        if model.has_lzp() {
233            out.extend_from_slice(&lzp_size.to_le_bytes());
234        }
235        if model.has_rle() {
236            out.extend_from_slice(&rle_size.to_le_bytes());
237        }
238        self.coder.encode(&bwt_out[..cur_len], out);
239        Ok(())
240    }
241}
242
243/// Buffer that holds the current stage output.
244#[derive(Clone, Copy)]
245enum StageBuffer {
246    /// The caller block, not yet transformed.
247    Input,
248    /// The first stage buffer.
249    A,
250    /// The second stage buffer.
251    B,
252}
253
254/// Pick the input slice of the next stage and its free output buffer.
255///
256/// Return the buffer tag of that output too. The output never aliases
257/// the input.
258fn stage_buffers<'a>(
259    src: StageBuffer,
260    len: usize,
261    data: &'a [u8],
262    a: &'a mut [u8],
263    b: &'a mut [u8],
264) -> (&'a [u8], &'a mut [u8], StageBuffer) {
265    match src {
266        StageBuffer::Input => (data, a, StageBuffer::A),
267        StageBuffer::A => (&a[..len], b, StageBuffer::B),
268        StageBuffer::B => (&b[..len], a, StageBuffer::A),
269    }
270}
271
272/// Encode one block through the stored-raw layout.
273///
274/// The caller routes inputs below [`MIN_CODED_LEN`](super::MIN_CODED_LEN) bytes here.
275/// Append the block to `out`.
276fn encode_stored(data: &[u8], out: &mut Vec<u8>) -> Result<()> {
277    out.try_reserve(data.len() + STORED_PREFIX_LEN)
278        .map_err(|_| Error::OutOfMemory)?;
279    out.extend_from_slice(&crc32::sum(1, data).to_le_bytes());
280    out.extend_from_slice(&STORED_MARKER.to_le_bytes());
281    out.extend_from_slice(data);
282    Ok(())
283}
284
285#[cfg(test)]
286mod tests {
287    use super::BlockEncoder;
288    use super::MIN_CODED_LEN;
289    use crate::BlockSize;
290
291    /// Smallest state used by the tests (65 KiB block).
292    fn small_state() -> BlockEncoder {
293        BlockEncoder::new(BlockSize::MIN).unwrap()
294    }
295
296    #[test]
297    fn stored_layout_matches_reference() {
298        let out = small_state().encode_block(b"A").unwrap();
299        assert_eq!(out.len(), 9);
300        assert_eq!(
301            u32::from_le_bytes(out[..4].try_into().unwrap()),
302            0x417B_1DBC
303        );
304        assert_eq!(out[4..8], [0xFF; 4]);
305        assert_eq!(out[8..], [b'A']);
306    }
307
308    #[test]
309    fn small_block_boundary_routes_stored_vs_coded() {
310        let mut state = small_state();
311        for len in 0..MIN_CODED_LEN {
312            let out = state.encode_block(&vec![b'q'; len]).unwrap();
313            assert_eq!(out.len(), len + 8, "stored len {len}");
314            assert_eq!(out[4..8], [0xFF; 4], "stored marker len {len}");
315        }
316        // `MIN_CODED_LEN` itself leaves the stored route: the RLE stage
317        // collapses the run, so a model byte replaces the raw layout.
318        let out = state.encode_block(&[b'q'; MIN_CODED_LEN]).unwrap();
319        assert_eq!(out[8], 0x04, "RLE-only model {}", out[8]);
320        assert_ne!(out[4..8], [0xFF; 4]);
321    }
322
323    #[test]
324    fn oversize_input_fails_data_too_big() {
325        let mut state = small_state();
326        let big = vec![0_u8; usize::try_from(BlockSize::MIN.get()).unwrap() + 1];
327        assert!(matches!(
328            state.encode_block(&big),
329            Err(crate::error::Error::BlockTooLarge)
330        ));
331    }
332
333    #[test]
334    fn repeated_bytes_set_rle_model_bit() {
335        let mut state = small_state();
336        let out = state.encode_block(&[b'A'; 1000]).unwrap();
337        assert_eq!(out[8] & 0x04, 0x04, "RLE bit set, model {}", out[8]);
338    }
339
340    #[test]
341    fn periodic_text_sets_lzp_without_rle() {
342        // A long-period repeat compresses via LZP but has no runs for RLE.
343        let phrase: Vec<u8> = (0..50_u8).collect();
344        let mut input = Vec::new();
345        for _ in 0..4 {
346            input.extend_from_slice(&phrase);
347        }
348        assert!(input.len() >= 72);
349        let out = small_state().encode_block(&input).unwrap();
350        assert_eq!(out[8] & 0x06, 0x02, "LZP-only model {}", out[8]);
351    }
352
353    #[test]
354    fn random_bytes_take_plain_coded_path() {
355        // Fixed-seed filler with no runs and no long matches.
356        let mut seed = 0x00C0_FFEE_u32;
357        let input: Vec<u8> = (0..1000)
358            .map(|_| {
359                seed = seed.wrapping_mul(1_103_515_245).wrapping_add(12_345);
360                u8::try_from((seed >> 16) & 0xFF).expect("masked byte fits")
361            })
362            .collect();
363        let out = small_state().encode_block(&input).unwrap();
364        assert_eq!(out[8], 0, "plain model {}", out[8]);
365        // Model zero means no size fields: the coded stream starts at 9.
366        assert!(out.len() > 9);
367    }
368
369    #[test]
370    fn state_reuse_gives_identical_bytes() {
371        let mut state = small_state();
372        let first = state.encode_block(&[b'A'; 1000]).unwrap();
373        let _ = state.encode_block(&[b'B'; 500]).unwrap();
374        let again = state.encode_block(&[b'A'; 1000]).unwrap();
375        assert_eq!(first, again);
376    }
377
378    #[test]
379    fn block_size_accessor_matches_constructor() {
380        assert_eq!(small_state().block_size(), BlockSize::MIN);
381    }
382}