Skip to main content

bzip3_rs/block/
decode.rs

1//! Inverse block decode pipeline with check order, fallback, and checksum last.
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::frame::read_i32;
13use crate::frame::read_u32;
14use crate::lzp;
15use crate::rle;
16use crate::sais;
17use crate::sais::UnbwtScratch;
18
19use super::FILLER_CAP;
20use super::MODEL_LEN;
21use super::PREFIX_LEN;
22use super::PayloadPrefix;
23use super::STORED_MARKER;
24use super::STORED_MAX_BODY;
25use super::STORED_PREFIX_LEN;
26use super::model::Model;
27
28/// State of the bytes that [`BlockDecoder::recover_block_into`] appended.
29#[derive(Debug)]
30pub enum BlockStatus {
31    /// The block decoded with no fault.
32    Intact,
33    /// Only the checksum is bad. The appended bytes are the decoded bytes.
34    BadChecksum,
35    /// The block has another fault. The appended bytes are zero.
36    Zeroed(Error),
37}
38
39/// Block decoder with constructor-owned working memory.
40///
41/// This type mirrors [`BlockEncoder`](super::BlockEncoder) on the decode side and never touches
42/// the encoder. The hash table and the entropy coder state persist
43/// across calls but reset per block, exactly like the reference. One
44/// call decodes one block payload through the inverse pipeline:
45/// entropy decode, inverse Burrows-Wheeler transform, then inverse
46/// Lempel-Ziv prediction and inverse run-length transform as the model
47/// byte selects. Every check runs in reference order, first failure
48/// wins. Like the reference, a short entropy stream reads filler bytes
49/// past its end; the port retries with equivalent filler and fails
50/// downstream exactly as the reference does.
51///
52/// # Examples
53///
54/// ```
55/// use bzip3_rs::{BlockDecoder, BlockEncoder, BlockSize};
56///
57/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
58/// let data = b"hello world ".repeat(100);
59/// let size = BlockSize::MIN;
60/// let payload = BlockEncoder::new(size)?.encode_block(&data)?;
61///
62/// let mut decoder = BlockDecoder::new(size)?;
63/// let mut plain = Vec::new();
64/// decoder.decode_block_into(&payload, u32::try_from(data.len())?, &mut plain)?;
65/// assert_eq!(plain, data);
66/// # Ok(())
67/// # }
68/// ```
69pub struct BlockDecoder {
70    /// Block size recorded in the frame header.
71    block_size: BlockSize,
72    /// LZP hash table, cleared by the stage on every call.
73    lzp_table: Box<lzp::HashTable>,
74    /// Entropy coder state, reset by the stage on every call.
75    /// Boxed like the reference heap state, so the decoder struct
76    /// stays small on the stack.
77    coder: Box<CmCoder>,
78    /// First ping-pong stage buffer, [`compress_bound`](crate::compress_bound) bytes.
79    buf_a: Vec<u8>,
80    /// Second ping-pong stage buffer, [`compress_bound`](crate::compress_bound) bytes.
81    buf_b: Vec<u8>,
82    /// Inverse transform scratch, kept between blocks.
83    unbwt_scratch: UnbwtScratch,
84}
85
86impl Debug for BlockDecoder {
87    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
88        f.debug_struct("BlockDecoder")
89            .field("block_size", &self.block_size)
90            .finish_non_exhaustive()
91    }
92}
93
94impl BlockDecoder {
95    /// Build a decoder for `block_size`, allocating working memory once.
96    ///
97    /// # Errors
98    ///
99    /// Returns [`Error::OutOfMemory`] when any working buffer allocation fails.
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// use bzip3_rs::{BlockDecoder, BlockSize};
105    ///
106    /// # fn main() -> Result<(), bzip3_rs::error::Error> {
107    /// let decoder = BlockDecoder::new(BlockSize::MIN)?;
108    /// assert_eq!(decoder.block_size(), BlockSize::MIN);
109    /// # Ok(())
110    /// # }
111    /// ```
112    pub fn new(block_size: BlockSize) -> Result<Self> {
113        let capacity = crate::compress_bound(block_size.as_usize());
114        Ok(Self {
115            block_size,
116            lzp_table: lzp::new_table()?,
117            coder: Box::new(CmCoder::new()),
118            buf_a: filled(capacity, 0).ok_or(Error::OutOfMemory)?,
119            buf_b: filled(capacity, 0).ok_or(Error::OutOfMemory)?,
120            unbwt_scratch: UnbwtScratch::default(),
121        })
122    }
123
124    /// Return the block size this decoder was built for.
125    #[must_use]
126    #[inline]
127    pub const fn block_size(&self) -> BlockSize {
128        self.block_size
129    }
130
131    /// Decode one block payload into a new vector.
132    ///
133    /// Same result as [`BlockDecoder::decode_block_into`]. Use that call
134    /// to reuse one output vector across blocks.
135    ///
136    /// # Errors
137    ///
138    /// Returns the same errors as [`BlockDecoder::decode_block_into`].
139    ///
140    /// # Examples
141    ///
142    /// ```
143    /// use bzip3_rs::{BlockDecoder, BlockEncoder, BlockSize};
144    ///
145    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
146    /// let data = b"hello world ".repeat(100);
147    /// let payload = BlockEncoder::new(BlockSize::MIN)?.encode_block(&data)?;
148    /// let mut decoder = BlockDecoder::new(BlockSize::MIN)?;
149    /// let block = decoder.decode_block(&payload, u32::try_from(data.len())?)?;
150    /// assert_eq!(block, data);
151    /// # Ok(())
152    /// # }
153    /// ```
154    #[must_use = "decoded output must be written to the frame"]
155    #[inline]
156    pub fn decode_block(&mut self, payload: &[u8], orig_size: u32) -> Result<Vec<u8>> {
157        let mut out = Vec::new();
158        self.decode_block_into(payload, orig_size, &mut out)?;
159        Ok(out)
160    }
161
162    /// Decode one block payload and append its original bytes to `out`.
163    ///
164    /// On an error, `out` keeps its length. The call checks the sizes
165    /// before it grows `out`, so a hostile `orig_size` cannot force a
166    /// large allocation.
167    ///
168    /// # Errors
169    ///
170    /// Returns the errors of [`BlockDecoder::decode_block_to`], and
171    /// [`Error::OutOfMemory`] when `out` cannot grow.
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// use bzip3_rs::{BlockDecoder, BlockEncoder, BlockSize};
177    ///
178    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
179    /// let first = b"first block ".repeat(50);
180    /// let second = b"second block ".repeat(50);
181    /// let mut encoder = BlockEncoder::new(BlockSize::MIN)?;
182    /// let mut decoder = BlockDecoder::new(BlockSize::MIN)?;
183    ///
184    /// let mut plain = Vec::new();
185    /// for block in [&first, &second] {
186    ///     let payload = encoder.encode_block(block)?;
187    ///     decoder.decode_block_into(&payload, u32::try_from(block.len())?, &mut plain)?;
188    /// }
189    /// assert_eq!(plain, [first, second].concat());
190    /// # Ok(())
191    /// # }
192    /// ```
193    pub fn decode_block_into(
194        &mut self,
195        payload: &[u8],
196        orig_size: u32,
197        out: &mut Vec<u8>,
198    ) -> Result<()> {
199        let head = self.read_prefix(payload, orig_size)?;
200        let old_len = out.len();
201        out.try_reserve(head.original_len)
202            .map_err(|_| Error::OutOfMemory)?;
203        out.resize(old_len + head.original_len, 0);
204        let result = self.decode_payload(payload, &head, &mut out[old_len..]);
205        if result.is_err() {
206            out.truncate(old_len);
207        }
208        result
209    }
210
211    /// Decode one block payload and always append `orig_size` bytes to `out`.
212    ///
213    /// Use this call to recover the data of a damaged stream. A checksum
214    /// fault keeps the decoded bytes, because the checksum is the last
215    /// check. Any other block fault appends zero bytes, because the
216    /// decode stages leave partial data. The returned [`BlockStatus`]
217    /// tells which case happened. A size above the block size appends the
218    /// block size.
219    ///
220    /// # Errors
221    ///
222    /// Returns [`Error::OutOfMemory`] when `out` cannot grow.
223    /// Then `out` keeps its length. Block faults are not errors. They
224    /// come back in the [`BlockStatus`] value.
225    ///
226    /// # Examples
227    ///
228    /// ```
229    /// use bzip3_rs::error::Error;
230    /// use bzip3_rs::{BlockDecoder, BlockEncoder, BlockStatus, BlockSize};
231    ///
232    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
233    /// let data = b"hello world ".repeat(100);
234    /// let orig_size = u32::try_from(data.len())?;
235    /// let mut payload = BlockEncoder::new(BlockSize::MIN)?.encode_block(&data)?;
236    /// let mut decoder = BlockDecoder::new(BlockSize::MIN)?;
237    ///
238    /// // A wrong checksum keeps the decoded bytes.
239    /// payload[0] ^= 1;
240    /// let mut plain = Vec::new();
241    /// let result = decoder.recover_block_into(&payload, orig_size, &mut plain)?;
242    /// assert!(matches!(result, BlockStatus::BadChecksum));
243    /// assert_eq!(plain, data);
244    ///
245    /// // A short payload gives zero bytes.
246    /// let mut plain = Vec::new();
247    /// let result = decoder.recover_block_into(&payload[..4], orig_size, &mut plain)?;
248    /// assert!(matches!(result, BlockStatus::Zeroed(Error::TruncatedHeader)));
249    /// assert_eq!(plain, vec![0_u8; data.len()]);
250    /// # Ok(())
251    /// # }
252    /// ```
253    pub fn recover_block_into(
254        &mut self,
255        payload: &[u8],
256        orig_size: u32,
257        out: &mut Vec<u8>,
258    ) -> Result<BlockStatus> {
259        let fill = usize::try_from(orig_size)
260            .unwrap_or(usize::MAX)
261            .min(self.block_size.as_usize());
262        let old_len = out.len();
263        out.try_reserve(fill).map_err(|_| Error::OutOfMemory)?;
264        out.resize(old_len + fill, 0);
265        let plain = &mut out[old_len..];
266        let decoded = self
267            .read_prefix(payload, orig_size)
268            .and_then(|head| self.decode_body(payload, &head, plain).map(|()| head));
269        Ok(match decoded {
270            Ok(head) if check_crc(plain, &head).is_ok() => BlockStatus::Intact,
271            Ok(_) => BlockStatus::BadChecksum,
272            Err(fault) => {
273                plain.fill(0);
274                BlockStatus::Zeroed(fault)
275            }
276        })
277    }
278
279    /// Decode one block payload into `out`.
280    ///
281    /// `payload` holds the block bytes (`compressed_size` long) and
282    /// `orig_size` holds the claimed original size. `out` must be
283    /// exactly `orig_size` bytes long. The call runs the reference
284    /// check order: buffer fit, compressed-size range, original-size
285    /// range, stored-raw path, header-size fit, stage sizes,
286    /// intermediate-size fit, entropy decode, index range, inverse
287    /// transform, inverse stages, final-size match, checksum. The call
288    /// does not allocate after the first block, except on a starved
289    /// entropy stream.
290    ///
291    /// # Errors
292    ///
293    /// Returns [`Error::BufferTooSmall`] when a size exceeds the decode buffer,
294    /// [`Error::OutputSizeMismatch`] when `out` has the wrong length,
295    /// [`Error::TruncatedHeader`] on a short payload,
296    /// [`Error::InvalidHeader`] on bad sizes,
297    /// [`Error::BwtFailed`] when the inverse transform fails,
298    /// [`Error::ChecksumMismatch`] on checksum mismatch,
299    /// [`Error::CorruptStageData`] on corrupt RLE or LZP data,
300    /// [`Error::OutOfMemory`] when working memory allocation fails.
301    ///
302    /// # Examples
303    ///
304    /// ```
305    /// use bzip3_rs::error::Error;
306    /// use bzip3_rs::{BlockDecoder, BlockEncoder, BlockSize};
307    ///
308    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
309    /// let data = b"hello world ".repeat(100);
310    /// let orig_size = u32::try_from(data.len())?;
311    /// let payload = BlockEncoder::new(BlockSize::MIN)?.encode_block(&data)?;
312    /// let mut decoder = BlockDecoder::new(BlockSize::MIN)?;
313    ///
314    /// let mut out = vec![0_u8; data.len()];
315    /// decoder.decode_block_to(&payload, orig_size, &mut out)?;
316    /// assert_eq!(out, data);
317    ///
318    /// // A wrong output length fails.
319    /// let mut short = vec![0_u8; data.len() - 1];
320    /// let result = decoder.decode_block_to(&payload, orig_size, &mut short);
321    /// assert!(matches!(result, Err(Error::OutputSizeMismatch)));
322    /// # Ok(())
323    /// # }
324    /// ```
325    pub fn decode_block_to(
326        &mut self,
327        payload: &[u8],
328        orig_size: u32,
329        out: &mut [u8],
330    ) -> Result<()> {
331        let head = self.read_prefix(payload, orig_size)?;
332        if out.len() != head.original_len {
333            return Err(Error::OutputSizeMismatch);
334        }
335        self.decode_payload(payload, &head, out)
336    }
337
338    /// Run the first reference checks and read the fixed prefix.
339    fn read_prefix(&self, payload: &[u8], orig_size: u32) -> Result<PayloadPrefix> {
340        let block = self.block_size.as_usize();
341        // The buffer holds `compress_bound(block)` bytes, which exceeds every
342        // fixed header. Buffer fit runs before any header read.
343        let limit = crate::compress_bound(block);
344        if limit < payload.len() {
345            return Err(Error::BufferTooSmall);
346        }
347        let Some(prefix) = payload.get(..PREFIX_LEN) else {
348            return Err(Error::TruncatedHeader);
349        };
350        let crc = read_u32(prefix);
351        let bwt_index = read_i32(&prefix[4..]);
352        let original_len = usize::try_from(orig_size).unwrap_or(usize::MAX);
353        if original_len > block {
354            return Err(Error::InvalidHeader);
355        }
356        Ok(PayloadPrefix {
357            original_len,
358            crc,
359            bwt_index,
360            limit,
361        })
362    }
363
364    /// Decode a payload that passed [`BlockDecoder::read_prefix`].
365    #[inline]
366    fn decode_payload(
367        &mut self,
368        payload: &[u8],
369        head: &PayloadPrefix,
370        out: &mut [u8],
371    ) -> Result<()> {
372        self.decode_body(payload, head, out)?;
373        check_crc(out, head)
374    }
375
376    /// Decode a payload through every stage, without the checksum.
377    ///
378    /// `out` holds the full block when this call succeeds.
379    #[inline]
380    fn decode_body(&mut self, payload: &[u8], head: &PayloadPrefix, out: &mut [u8]) -> Result<()> {
381        if head.bwt_index == STORED_MARKER {
382            return decode_stored(payload, head, out);
383        }
384        self.decode_stages(payload, head, out)
385    }
386
387    /// Decode a non-stored block payload through every inverse stage.
388    ///
389    /// `limit` is `compress_bound` of the block size. The reference passes the
390    /// same value as both the compressed-size bound and the decode
391    /// buffer size. The buffers here have exactly that size, so a
392    /// stage size inside `0..=limit` always fits.
393    fn decode_stages(
394        &mut self,
395        payload: &[u8],
396        head: &PayloadPrefix,
397        out: &mut [u8],
398    ) -> Result<()> {
399        let PayloadPrefix {
400            original_len,
401            bwt_index,
402            limit,
403            ..
404        } = *head;
405        let Some(&raw) = payload.get(PREFIX_LEN) else {
406            return Err(Error::TruncatedHeader);
407        };
408        let model = Model(raw);
409        let header_len = model.header_len();
410        let Some(header) = payload.get(..header_len) else {
411            return Err(Error::TruncatedHeader);
412        };
413        let mut sizes = &header[PREFIX_LEN + MODEL_LEN..];
414        let lzp_size = if model.has_lzp() {
415            let size = read_stage_size(sizes, limit)?;
416            sizes = &sizes[4..];
417            Some(size)
418        } else {
419            None
420        };
421        let rle_size = if model.has_rle() {
422            Some(read_stage_size(sizes, limit)?)
423        } else {
424            None
425        };
426        let bwt_len = lzp_size.or(rle_size).unwrap_or(original_len);
427        let coded = &payload[header_len..];
428        self.decode_entropy(coded, bwt_len, bwt_index, model)?;
429        if usize::try_from(bwt_index).is_ok_and(|index| index > bwt_len) {
430            return Err(Error::InvalidHeader);
431        }
432
433        // Inverse Burrows-Wheeler transform. A block without LZP and RLE
434        // has `bwt_len == original_len`, so it goes straight into `out`.
435        if !model.has_lzp() && !model.has_rle() {
436            sais::unbwt_with(
437                &self.buf_a[..bwt_len],
438                out,
439                bwt_index,
440                &mut self.unbwt_scratch,
441            )?;
442        } else {
443            sais::unbwt_with(
444                &self.buf_a[..bwt_len],
445                &mut self.buf_b[..bwt_len],
446                bwt_index,
447                &mut self.unbwt_scratch,
448            )?;
449            let mut current = &self.buf_b[..bwt_len];
450            if model.has_lzp() {
451                let size =
452                    lzp::decode_into(current, &mut self.buf_a[..limit], &mut self.lzp_table)?;
453                current = &self.buf_a[..size];
454            }
455            if model.has_rle() {
456                rle::decode_into(current, out)?;
457            } else {
458                if current.len() != original_len {
459                    return Err(Error::InvalidHeader);
460                }
461                out.copy_from_slice(current);
462            }
463        }
464        Ok(())
465    }
466
467    /// Entropy-decode `coded` into the first `bwt_len` bytes of `buf_a`,
468    /// emulating filler.
469    ///
470    /// Runs before the index check, exactly like the reference. A
471    /// short stream reads filler bytes past its end in the reference,
472    /// and `0xFF` bytes feed the decoder state exactly as the
473    /// reference `-1` reads do. The decoder therefore reads the same
474    /// filler and reproduces the reference garbage byte for byte while
475    /// the filler lasts. When even the capped filler runs out, the
476    /// claimed sizes dwarf the stream, so the fallback keeps the
477    /// reference order on what is known: index range first, then the
478    /// modal downstream outcome.
479    ///
480    /// # Errors
481    ///
482    /// Returns the ordered fallback variant on a starved stream.
483    fn decode_entropy(
484        &mut self,
485        coded: &[u8],
486        bwt_len: usize,
487        bwt_index: i32,
488        model: Model,
489    ) -> Result<()> {
490        let filler = bwt_len
491            .saturating_mul(32)
492            .saturating_add(64)
493            .min(FILLER_CAP);
494        match self.coder.decode(coded, filler, &mut self.buf_a[..bwt_len]) {
495            Err(Error::TruncatedData) => {
496                let index = i64::from(bwt_index);
497                let span = i64::try_from(bwt_len).unwrap_or(i64::MAX);
498                if index > span {
499                    return Err(Error::InvalidHeader);
500                }
501                if index < 1 {
502                    return Err(Error::BwtFailed);
503                }
504                if model.has_lzp() && !model.has_rle() {
505                    return Err(Error::InvalidHeader);
506                }
507                if model.has_rle() {
508                    return Err(Error::CorruptStageData);
509                }
510                Err(Error::ChecksumMismatch)
511            }
512            outcome => outcome,
513        }
514    }
515}
516
517/// Check the checksum of a decoded block. This is the last check of a decode.
518///
519/// # Errors
520///
521/// Returns [`Error::ChecksumMismatch`] when the sums differ.
522#[inline]
523fn check_crc(plain: &[u8], head: &PayloadPrefix) -> Result<()> {
524    if crc32::sum(1, plain) == head.crc {
525        Ok(())
526    } else {
527        Err(Error::ChecksumMismatch)
528    }
529}
530
531/// Read one stage size field.
532///
533/// # Errors
534///
535/// Returns [`Error::InvalidHeader`] when the size is outside `0..=limit`.
536#[inline]
537fn read_stage_size(bytes: &[u8], limit: usize) -> Result<usize> {
538    usize::try_from(read_i32(bytes))
539        .ok()
540        .filter(|&size| size <= limit)
541        .ok_or(Error::InvalidHeader)
542}
543
544/// Decode a stored-raw block payload.
545///
546/// The body must hold exactly `orig` bytes within the 64-byte small
547/// block bound. The caller checks the checksum, so size faults win over it.
548#[inline]
549fn decode_stored(payload: &[u8], head: &PayloadPrefix, out: &mut [u8]) -> Result<()> {
550    let original_len = head.original_len;
551    let Some(body) = payload.get(STORED_PREFIX_LEN..) else {
552        return Err(Error::TruncatedHeader);
553    };
554    if body.len() > STORED_MAX_BODY || body.len() != original_len {
555        return Err(Error::InvalidHeader);
556    }
557    out.copy_from_slice(body);
558    Ok(())
559}
560
561#[cfg(test)]
562mod tests {
563    use super::BlockDecoder;
564    use super::BlockStatus;
565    use super::decode_stored;
566    use crate::BlockSize;
567    use crate::block::BlockEncoder;
568    use crate::compress_bound;
569    use crate::error::Error;
570
571    /// Smallest decoder used by the tests (65 KiB block).
572    fn small_decoder() -> BlockDecoder {
573        BlockDecoder::new(BlockSize::MIN).unwrap()
574    }
575
576    /// Smallest state used by the tests (65 KiB block).
577    fn small_state() -> BlockEncoder {
578        BlockEncoder::new(BlockSize::MIN).unwrap()
579    }
580
581    #[test]
582    fn decoder_restores_stored_blocks() {
583        let mut encoder = small_state();
584        let mut decoder = small_decoder();
585        let sixty_three = b"x".repeat(63);
586        for input in [b"A".as_slice(), b"".as_slice(), sixty_three.as_slice()] {
587            let payload = encoder.encode_block(input).unwrap();
588            let len = u32::try_from(input.len()).unwrap();
589            assert_eq!(decoder.decode_block(&payload, len).unwrap(), input);
590        }
591    }
592
593    #[test]
594    fn decoder_restores_every_model() {
595        let mut seed = 0x00C0_FFEE_u32;
596        let random: Vec<u8> = (0..1000)
597            .map(|_| {
598                seed = seed.wrapping_mul(1_103_515_245).wrapping_add(12_345);
599                u8::try_from((seed >> 16) & 0xFF).expect("masked byte fits")
600            })
601            .collect();
602        let phrase: Vec<u8> = (0..50_u8).collect();
603        let mut periodic = Vec::new();
604        for _ in 0..20 {
605            periodic.extend_from_slice(&phrase);
606            periodic.extend_from_slice(&phrase);
607        }
608        let inputs: Vec<Vec<u8>> = vec![
609            vec![b'A'; 1000],
610            periodic,
611            random,
612            [vec![b'B'; 300], (0..50_u8).collect::<Vec<u8>>().repeat(10)].concat(),
613        ];
614        let mut models = std::collections::HashSet::new();
615        for input in &inputs {
616            let payload = small_state().encode_block(input).unwrap();
617            models.insert(payload[8]);
618            let len = u32::try_from(input.len()).unwrap();
619            let got = small_decoder().decode_block(&payload, len).unwrap();
620            assert_eq!(&got, input);
621        }
622        // Runs set RLE, periodic text sets LZP, filler stays plain.
623        assert!(models.contains(&0x04));
624        assert!(models.contains(&0x02));
625        assert!(models.contains(&0x00));
626    }
627
628    #[test]
629    fn decoder_enforces_check_order() {
630        let mut decoder = small_decoder();
631        let payload = small_state().encode_block(b"A").unwrap();
632        assert_eq!(decoder.decode_block(&payload, 1).unwrap(), b"A");
633        // Short payload and body/size mismatch are malformed.
634        assert!(matches!(
635            decoder.decode_block(&payload[..4], 1),
636            Err(crate::error::Error::TruncatedHeader)
637        ));
638        assert!(matches!(
639            decoder.decode_block(&payload, 2),
640            Err(crate::error::Error::InvalidHeader)
641        ));
642        // Body above 64 bytes is malformed even with matching size.
643        let mut big = payload.clone();
644        big.extend_from_slice(&[0; 64]);
645        assert!(matches!(
646            decoder.decode_block(&big, 65),
647            Err(crate::error::Error::InvalidHeader)
648        ));
649        // Corrupt body fails the checksum.
650        let mut bad = payload.clone();
651        bad[8] ^= 0x01;
652        assert!(matches!(
653            decoder.decode_block(&bad, 1),
654            Err(crate::error::Error::ChecksumMismatch)
655        ));
656        // Original size above the block size is malformed.
657        assert!(matches!(
658            decoder.decode_block(&payload, 65 * 1024 + 1),
659            Err(crate::error::Error::InvalidHeader)
660        ));
661    }
662
663    #[test]
664    fn recover_keeps_bytes_on_checksum_fault() {
665        let data = [b'A'; 1000];
666        let mut decoder = small_decoder();
667        // A coded block and a stored block.
668        for input in [data.as_slice(), b"short".as_slice()] {
669            let mut payload = small_state().encode_block(input).unwrap();
670            payload[0] ^= 0x01;
671            let len = u32::try_from(input.len()).unwrap();
672            let mut out = b"head".to_vec();
673            let result = decoder.recover_block_into(&payload, len, &mut out);
674            assert!(matches!(result, Ok(BlockStatus::BadChecksum)));
675            assert_eq!(out[..4], *b"head");
676            assert_eq!(out[4..], *input);
677        }
678    }
679
680    #[test]
681    fn recover_writes_zeros_on_stage_fault() {
682        let payload = small_state().encode_block(&[b'A'; 1000]).unwrap();
683        let mut bad = payload.clone();
684        bad[13] ^= 0xFF;
685        let mut out = Vec::new();
686        let result = small_decoder().recover_block_into(&bad, 1000, &mut out);
687        assert!(matches!(
688            result,
689            Ok(BlockStatus::Zeroed(crate::error::Error::CorruptStageData))
690        ));
691        assert_eq!(out, vec![0_u8; 1000]);
692
693        // A short payload and a size above the block size.
694        let mut out = Vec::new();
695        let result = small_decoder().recover_block_into(&payload[..4], 10, &mut out);
696        assert!(matches!(
697            result,
698            Ok(BlockStatus::Zeroed(crate::error::Error::TruncatedHeader))
699        ));
700        assert_eq!(out, vec![0_u8; 10]);
701        let mut out = Vec::new();
702        let result = small_decoder().recover_block_into(&payload, u32::MAX, &mut out);
703        assert!(matches!(
704            result,
705            Ok(BlockStatus::Zeroed(crate::error::Error::InvalidHeader))
706        ));
707        assert_eq!(out.len(), BlockSize::MIN.as_usize());
708
709        // A bad index of the transform gives zero bytes.
710        let mut bad = payload.clone();
711        bad[4..8].copy_from_slice(&i32::MAX.to_le_bytes());
712        let mut out = Vec::new();
713        let result = small_decoder().recover_block_into(&bad, 1000, &mut out);
714        assert!(matches!(
715            result,
716            Ok(BlockStatus::Zeroed(crate::error::Error::InvalidHeader))
717        ));
718        assert_eq!(out, vec![0_u8; 1000]);
719    }
720
721    #[test]
722    fn recover_matches_decode_on_good_blocks() {
723        let mut decoder = small_decoder();
724        for input in [b"A".as_slice(), &[b'B'; 1000], &[]] {
725            let payload = small_state().encode_block(input).unwrap();
726            let len = u32::try_from(input.len()).unwrap();
727            let mut out = Vec::new();
728            let result = decoder.recover_block_into(&payload, len, &mut out);
729            assert!(matches!(result, Ok(BlockStatus::Intact)));
730            assert_eq!(out, input);
731        }
732    }
733
734    #[test]
735    fn decoder_enforces_coded_check_order() {
736        // RLE-model payload exercises the coded path.
737        let payload = small_state().encode_block(&[b'A'; 1000]).unwrap();
738        assert_eq!(payload[8] & 0x04, 0x04);
739        let mut decoder = small_decoder();
740        assert_eq!(decoder.decode_block(&payload, 1000).unwrap().len(), 1000);
741        // Bad stage size beats the checksum.
742        let mut bad = payload.clone();
743        bad[9..13].copy_from_slice(&i32::MAX.to_le_bytes());
744        assert!(matches!(
745            decoder.decode_block(&bad, 1000),
746            Err(crate::error::Error::InvalidHeader)
747        ));
748        // Bad index beats the corrupt stream.
749        let mut bad = payload.clone();
750        bad[4..8].copy_from_slice(&i32::MAX.to_le_bytes());
751        let last = bad.len() - 1;
752        bad[last] ^= 0xFF;
753        assert!(matches!(
754            decoder.decode_block(&bad, 1000),
755            Err(crate::error::Error::InvalidHeader)
756        ));
757        // Corrupt stream fails the RLE stage or the checksum. The first coded byte
758        // feeds the entropy decoder; the tail holds flush bytes the
759        // decoder may never read, so the flip targets the head.
760        let mut bad = payload.clone();
761        bad[13] ^= 0xFF;
762        assert!(matches!(
763            decoder.decode_block(&bad, 1000),
764            Err(crate::error::Error::CorruptStageData)
765        ));
766    }
767
768    #[test]
769    fn decoder_final_size_mismatch_is_malformed() {
770        // LZP-only payload: shrinking the claim breaks the final match.
771        let phrase: Vec<u8> = (0..50_u8).collect();
772        let mut input = Vec::new();
773        for _ in 0..4 {
774            input.extend_from_slice(&phrase);
775        }
776        let payload = small_state().encode_block(&input).unwrap();
777        assert_eq!(payload[8] & 0x06, 0x02);
778        let short = u32::try_from(input.len() - 1).unwrap();
779        assert!(matches!(
780            small_decoder().decode_block(&payload, short),
781            Err(crate::error::Error::InvalidHeader)
782        ));
783    }
784
785    #[test]
786    fn decoder_reuse_gives_identical_bytes() {
787        let payload = small_state().encode_block(&[b'A'; 1000]).unwrap();
788        let mut decoder = small_decoder();
789        let first = decoder.decode_block(&payload, 1000).unwrap();
790        let other = small_state().encode_block(&[b'B'; 500]).unwrap();
791        let _ = decoder.decode_block(&other, 500).unwrap();
792        let again = decoder.decode_block(&payload, 1000).unwrap();
793        assert_eq!(first, again);
794    }
795
796    #[test]
797    fn decoder_block_size_accessor_matches_constructor() {
798        assert_eq!(small_decoder().block_size(), BlockSize::MIN);
799    }
800
801    #[test]
802    fn debug_names_type_and_block_size() {
803        let encoder_text = format!("{:?}", small_state());
804        assert!(encoder_text.contains("BlockEncoder"));
805        assert!(encoder_text.contains("block_size"));
806        let decoder_text = format!("{:?}", small_decoder());
807        assert!(decoder_text.contains("BlockDecoder"));
808        assert!(decoder_text.contains("block_size"));
809    }
810
811    #[test]
812    fn decode_block_to_rejects_wrong_output_length() {
813        let data = [b'A'; 1000];
814        let payload = small_state().encode_block(&data).unwrap();
815        let mut exact = vec![0_u8; data.len()];
816        small_decoder()
817            .decode_block_to(&payload, 1000, &mut exact)
818            .unwrap();
819        assert_eq!(exact, data);
820        for len in [0, data.len() - 1, data.len() + 1] {
821            let mut out = vec![0_u8; len];
822            assert!(matches!(
823                small_decoder().decode_block_to(&payload, 1000, &mut out),
824                Err(Error::OutputSizeMismatch)
825            ));
826        }
827    }
828
829    #[test]
830    fn oversize_payload_fails_buffer_too_small() {
831        let limit = compress_bound(BlockSize::MIN.as_usize());
832        let big = vec![0_u8; limit + 1];
833        assert!(matches!(
834            small_decoder().decode_block(&big, 1),
835            Err(Error::BufferTooSmall)
836        ));
837        let mut out = Vec::new();
838        assert!(matches!(
839            small_decoder().recover_block_into(&big, 1, &mut out),
840            Ok(BlockStatus::Zeroed(Error::BufferTooSmall))
841        ));
842    }
843
844    #[test]
845    fn eight_byte_prefix_fails_truncated_header() {
846        // The prefix passes the fixed-size check, but no model byte follows.
847        let mut payload = vec![0_u8; 8];
848        payload[4..8].copy_from_slice(&0_i32.to_le_bytes());
849        assert!(matches!(
850            small_decoder().decode_block(&payload, 100),
851            Err(Error::TruncatedHeader)
852        ));
853    }
854
855    #[test]
856    fn short_stage_header_fails_truncated_header() {
857        // A set stage bit claims a 4-byte size that the payload lacks.
858        for model in [0x02_u8, 0x04_u8] {
859            let mut payload = vec![0_u8; 9];
860            payload[4..8].copy_from_slice(&0_i32.to_le_bytes());
861            payload[8] = model;
862            assert!(matches!(
863                small_decoder().decode_block(&payload, 100),
864                Err(Error::TruncatedHeader)
865            ));
866        }
867    }
868
869    #[test]
870    fn stored_prefix_short_fails_truncated_header() {
871        // `read_prefix` always passes at least 8 bytes, so this arm runs
872        // only through a direct call.
873        let head = crate::block::PayloadPrefix {
874            original_len: 4,
875            crc: 0,
876            bwt_index: -1,
877            limit: 64,
878        };
879        let mut out = [0_u8; 4];
880        assert!(matches!(
881            decode_stored(&[0_u8; 4], &head, &mut out),
882            Err(Error::TruncatedHeader)
883        ));
884    }
885}