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}