#![crate_type = "rlib"]
#![no_std]
#![deny(warnings)]
#![forbid(unsafe_code)]
#![deny(missing_docs)]
pub mod decoder;
pub mod encoder;
#[cfg(feature = "embedded-io")]
pub mod io;
pub const DEFAULT_WINDOW_BITS: usize = 8;
pub const DEFAULT_LOOKAHEAD_BITS: usize = 4;
pub const DEFAULT_INPUT_BUFFER_SIZE: usize = 32;
pub type DefaultEncoder = encoder::HeatshrinkEncoder<
DEFAULT_WINDOW_BITS,
DEFAULT_LOOKAHEAD_BITS,
512, >;
pub type DefaultDecoder = decoder::HeatshrinkDecoder<
DEFAULT_WINDOW_BITS,
DEFAULT_LOOKAHEAD_BITS,
DEFAULT_INPUT_BUFFER_SIZE,
256, >;
#[derive(Debug, PartialEq, Eq)]
pub enum SinkError {
Full,
Misuse,
}
#[derive(Debug, PartialEq, Eq)]
pub enum PollError {
Misuse,
}
#[derive(Debug, PartialEq, Eq)]
pub enum Poll {
More(usize),
Empty(usize),
}
impl Poll {
#[inline]
pub fn bytes_written(&self) -> usize {
match self {
Poll::More(n) | Poll::Empty(n) => *n,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Finish {
Done,
More,
}
#[derive(Debug)]
pub enum CodecError {
OutputFull,
Internal,
}
#[cfg(test)]
mod test {
use super::{decoder, encoder};
fn compare(src: &[u8]) {
let mut compressed_buffer: [u8; 512] = [0; 512];
let mut uncompressed_buffer: [u8; 1024] = [0; 1024];
let out1 = encoder::encode(src, &mut compressed_buffer).unwrap();
let out2 = decoder::decode(out1, &mut uncompressed_buffer).unwrap();
assert_eq!(src, out2);
}
#[test]
fn alpha() {
let src = [
33, 82, 149, 84, 52, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 147, 2, 0, 0, 0, 0, 0, 0, 242, 2, 241, 2, 240,
2, 0, 0, 0, 0, 0, 0, 47, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0,
];
compare(&src);
}
#[test]
fn alpha2() {
let src = [
33, 82, 149, 84, 52, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 147, 2, 0, 0, 0, 0, 0, 0, 242, 2, 241, 2, 240,
2, 0, 0, 0, 0, 0, 0, 47, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
12, 17,
];
compare(&src);
}
#[test]
fn beta() {
let src = [
189, 160, 51, 163, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 199, 0, 0, 0, 0, 0, 0, 0, 166, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0,
];
compare(&src);
}
#[test]
fn beta2() {
let src: [u8; 512] = core::array::from_fn(|i| i as u8);
compare(&src);
}
#[test]
fn clib_compatibility() {
use hex_literal::hex;
let src = hex!("90D4B2B549A4082BE00F000E4C46DF2817C605F005B4BE0825F00280");
let expected = hex!(
"21529554340200000000000000000000000000000000000000000000000000000000000000000 0009302000000000000F202F102F0020000000000002F0400000000000000000000000000000000000000000000"
);
let mut dst = [0u8; 100];
let out = decoder::decode(&src, &mut dst).unwrap();
assert_eq!(expected, out);
}
}
#[cfg(test)]
mod test_streaming {
use super::*;
use super::{Poll, SinkError};
use decoder::HeatshrinkDecoder;
use encoder::HeatshrinkEncoder;
pub(super) fn stream_encode<const W: usize, const L: usize, const BUF: usize>(
src: &[u8],
dst: &mut [u8],
) -> usize {
let mut enc = HeatshrinkEncoder::<W, L, BUF>::new();
let mut total_in = 0;
let mut total_out = 0;
loop {
if total_in < src.len() {
match enc.sink(&src[total_in..]) {
Ok(n) => total_in += n,
Err(SinkError::Full) => {}
Err(SinkError::Misuse) => panic!("encoder sink misuse"),
}
}
if total_in == src.len() {
enc.finish();
}
match enc.poll(&mut dst[total_out..]) {
Ok(Poll::More(n)) => total_out += n,
Ok(Poll::Empty(n)) => {
total_out += n;
if total_in == src.len() {
break;
}
}
Err(_) => panic!("encoder poll misuse"),
}
}
total_out
}
pub(super) fn stream_decode<
const W: usize,
const L: usize,
const I: usize,
const WIN: usize,
>(
encoded: &[u8],
dst: &mut [u8],
) -> usize {
let mut dec = HeatshrinkDecoder::<W, L, I, WIN>::new();
let mut total_in = 0;
let mut total_out = 0;
let mut iters = 0usize;
loop {
iters += 1;
assert!(
iters < 1_000_000,
"stream_decode infinite loop (W={W} L={L} I={I}): \
iter={iters} total_in={total_in}/{} total_out={total_out}",
encoded.len()
);
if total_in < encoded.len() {
match dec.sink(&encoded[total_in..]) {
Ok(n) => total_in += n,
Err(SinkError::Full) => {}
Err(SinkError::Misuse) => panic!("decoder sink misuse"),
}
}
match dec.poll(&mut dst[total_out..]) {
Ok(Poll::More(n)) => total_out += n,
Ok(Poll::Empty(n)) => {
total_out += n;
if total_in == encoded.len() {
break;
}
}
Err(_) => panic!("decoder poll misuse"),
}
}
total_out
}
pub(super) fn roundtrip<const W: usize, const L: usize, const BUF: usize, const WIN: usize>(
src: &[u8],
) {
let mut compressed = [0u8; 32768];
let mut decompressed = [0u8; 32768];
let n_enc = stream_encode::<W, L, BUF>(src, &mut compressed);
let encoded = &compressed[..n_enc];
let n_dec16 = stream_decode::<W, L, 16, WIN>(encoded, &mut decompressed);
assert_eq!(
src,
&decompressed[..n_dec16],
"W={W} L={L} I=16 roundtrip failed"
);
decompressed = [0u8; 32768];
let n_dec32 = stream_decode::<W, L, 32, WIN>(encoded, &mut decompressed);
assert_eq!(
src,
&decompressed[..n_dec32],
"W={W} L={L} I=32 roundtrip failed"
);
}
}
#[cfg(test)]
mod test_params {
use super::test_streaming::roundtrip;
fn payloads() -> (&'static [u8], [u8; 8192], [u8; 8192]) {
let short = b"hello heatshrink - parametric test" as &[u8];
let repetitive: [u8; 8192] = core::array::from_fn(|i| (i % 251) as u8);
let pseudo_random: [u8; 8192] = core::array::from_fn(|i| {
(i.wrapping_mul(6364136223846793005usize)
.wrapping_add(1442695040888963407)
>> 56) as u8
});
(short, repetitive, pseudo_random)
}
macro_rules! param_test {
($name:ident, W=$w:literal, L=$l:literal) => {
#[test]
fn $name() {
const BUF: usize = 2 << $w;
const WIN: usize = 1 << $w;
let (short, rep, rnd) = payloads();
roundtrip::<$w, $l, BUF, WIN>(short);
roundtrip::<$w, $l, BUF, WIN>(&rep);
roundtrip::<$w, $l, BUF, WIN>(&rnd);
}
};
}
param_test!(default_w8_l4, W = 8, L = 4);
param_test!(minimum_w4_l3, W = 4, L = 3);
param_test!(maximum_w15_l14, W = 15, L = 14);
param_test!(bug_b5_w6_l3, W = 6, L = 3);
param_test!(bug_b6_w10_l9, W = 10, L = 9);
param_test!(bug_b8_w11_l10, W = 11, L = 10);
param_test!(midrange_w12_l7, W = 12, L = 7);
}