Skip to main content

marlin_binary_transfer/
compression.rs

1//! Optional heatshrink payload compression.
2//!
3//! Behind the `heatshrink` feature flag. Wraps the `embedded-heatshrink`
4//! crate's stream-style API into byte-slice helpers that match the chunk
5//! sizes our file-transfer code works in.
6//!
7//! The host compresses each chunk before passing it to
8//! [`FileTransfer::write`](crate::file_transfer::FileTransfer::write); the
9//! device decompresses on the fly using the same parameters. Window and
10//! lookahead must match what the device advertised during QUERY.
11
12#[cfg(feature = "heatshrink")]
13mod imp {
14    use std::io::Cursor;
15
16    use thiserror::Error;
17
18    /// Errors returned by the heatshrink wrappers.
19    #[derive(Debug, Error)]
20    pub enum HeatshrinkError {
21        /// Window or lookahead parameters were rejected by the heatshrink
22        /// implementation as out-of-range.
23        #[error("invalid heatshrink parameters: window={window}, lookahead={lookahead}")]
24        InvalidParameters {
25            /// log2 sliding-window size that was rejected.
26            window: u8,
27            /// log2 lookahead size that was rejected.
28            lookahead: u8,
29        },
30    }
31
32    /// Compress `input` using heatshrink with the given window/lookahead
33    /// parameters. The parameters MUST match what the device advertised
34    /// during QUERY.
35    pub fn compress(input: &[u8], window: u8, lookahead: u8) -> Result<Vec<u8>, HeatshrinkError> {
36        validate(window, lookahead)?;
37        let mut input_cursor = Cursor::new(input);
38        let mut output = Vec::with_capacity(input.len());
39        embedded_heatshrink::encode(window, lookahead, &mut input_cursor, &mut output);
40        Ok(output)
41    }
42
43    /// Decompress `input`. The device decompresses during real transfers;
44    /// this is here for the sake of round-trip tests.
45    pub fn decompress(input: &[u8], window: u8, lookahead: u8) -> Result<Vec<u8>, HeatshrinkError> {
46        validate(window, lookahead)?;
47        let mut input_cursor = Cursor::new(input);
48        let mut output = Vec::with_capacity(input.len() * 2);
49        embedded_heatshrink::decode(window, lookahead, &mut input_cursor, &mut output);
50        Ok(output)
51    }
52
53    fn validate(window: u8, lookahead: u8) -> Result<(), HeatshrinkError> {
54        use embedded_heatshrink::{
55            HEATSHRINK_MAX_WINDOW_BITS, HEATSHRINK_MIN_LOOKAHEAD_BITS, HEATSHRINK_MIN_WINDOW_BITS,
56        };
57        if !(HEATSHRINK_MIN_WINDOW_BITS..=HEATSHRINK_MAX_WINDOW_BITS).contains(&window)
58            || lookahead < HEATSHRINK_MIN_LOOKAHEAD_BITS
59            || lookahead >= window
60        {
61            return Err(HeatshrinkError::InvalidParameters { window, lookahead });
62        }
63        Ok(())
64    }
65}
66
67#[cfg(feature = "heatshrink")]
68pub use imp::{compress, decompress, HeatshrinkError};