general_purpose_paracletic_hyper_cube 0.1.0

General-purpose paracletic hyper cube compression toolkit.
Documentation
mod chromoharmonic;
mod delta_pulse;
mod rle;

use std::error::Error;
use std::fmt;

use chromoharmonic::Chromoharmonic;
use delta_pulse::DeltaPulse;
use rle::RunLength;

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Codec {
    Chromoharmonic,
    DeltaPulse,
    RunLength,
}

impl Codec {
    pub const ALL: [Codec; 3] = [Codec::Chromoharmonic, Codec::DeltaPulse, Codec::RunLength];

    pub fn name(self) -> &'static str {
        match self {
            Codec::Chromoharmonic => "chromoharmonic",
            Codec::DeltaPulse => "delta-pulse",
            Codec::RunLength => "run-length",
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CompressionError {
    CorruptStream(&'static str),
    ValueOverflow,
}

impl fmt::Display for CompressionError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            CompressionError::CorruptStream(msg) => write!(f, "corrupt stream: {msg}"),
            CompressionError::ValueOverflow => {
                write!(f, "decoded value overflowed byte range")
            }
        }
    }
}

impl Error for CompressionError {}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CompressionStats {
    pub input_bytes: usize,
    pub output_bytes: usize,
}

impl CompressionStats {
    pub fn ratio(self) -> f64 {
        if self.input_bytes == 0 {
            return 1.0;
        }
        self.output_bytes as f64 / self.input_bytes as f64
    }
}

pub trait Compressor: Sync {
    fn name(&self) -> &'static str;
    fn compress(&self, input: &[u8]) -> Vec<u8>;
    fn decompress(&self, input: &[u8]) -> Result<Vec<u8>, CompressionError>;
    fn stats(&self, input: &[u8]) -> Result<CompressionStats, CompressionError> {
        let compressed = self.compress(input);
        let decoded = self.decompress(&compressed)?;
        if decoded != input {
            return Err(CompressionError::CorruptStream(
                "self-check roundtrip failed",
            ));
        }
        Ok(CompressionStats {
            input_bytes: input.len(),
            output_bytes: compressed.len(),
        })
    }
}

static CHROMOHARMONIC: Chromoharmonic = Chromoharmonic;
static DELTA_PULSE: DeltaPulse = DeltaPulse;
static RUN_LENGTH: RunLength = RunLength;

pub fn compressor_for(codec: Codec) -> &'static dyn Compressor {
    match codec {
        Codec::Chromoharmonic => &CHROMOHARMONIC,
        Codec::DeltaPulse => &DELTA_PULSE,
        Codec::RunLength => &RUN_LENGTH,
    }
}

pub fn compress_with(codec: Codec, input: &[u8]) -> Vec<u8> {
    compressor_for(codec).compress(input)
}

pub fn decompress_with(codec: Codec, input: &[u8]) -> Result<Vec<u8>, CompressionError> {
    compressor_for(codec).decompress(input)
}

pub fn stats_with(codec: Codec, input: &[u8]) -> Result<CompressionStats, CompressionError> {
    compressor_for(codec).stats(input)
}