use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CompressionError {
BufferTooSmall { required: usize, available: usize },
InvalidData(String),
Internal(String),
UnexpectedEof,
AlreadyFinished,
}
impl fmt::Display for CompressionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CompressionError::BufferTooSmall {
required,
available,
} => {
write!(
f,
"buffer too small: required {} bytes, available {} bytes",
required, available
)
}
CompressionError::InvalidData(msg) => write!(f, "invalid data: {}", msg),
CompressionError::Internal(msg) => write!(f, "internal error: {}", msg),
CompressionError::UnexpectedEof => write!(f, "unexpected end of input"),
CompressionError::AlreadyFinished => write!(f, "compression already finished"),
}
}
}
impl std::error::Error for CompressionError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionStatus {
Continue {
consumed: usize,
produced: usize,
},
Complete {
consumed: usize,
produced: usize,
},
OutputFull {
consumed: usize,
produced: usize,
},
}
impl CompressionStatus {
#[inline]
pub fn consumed(&self) -> usize {
match self {
CompressionStatus::Continue { consumed, .. } => *consumed,
CompressionStatus::Complete { consumed, .. } => *consumed,
CompressionStatus::OutputFull { consumed, .. } => *consumed,
}
}
#[inline]
pub fn produced(&self) -> usize {
match self {
CompressionStatus::Continue { produced, .. } => *produced,
CompressionStatus::Complete { produced, .. } => *produced,
CompressionStatus::OutputFull { produced, .. } => *produced,
}
}
#[inline]
pub fn is_complete(&self) -> bool {
matches!(self, CompressionStatus::Complete { .. })
}
#[inline]
pub fn is_output_full(&self) -> bool {
matches!(self, CompressionStatus::OutputFull { .. })
}
}
pub trait Compressor {
fn compress(
&mut self,
input: &[u8],
output: &mut [u8],
) -> Result<CompressionStatus, CompressionError>;
fn finish(&mut self, output: &mut [u8]) -> Result<CompressionStatus, CompressionError>;
fn reset(&mut self);
}
pub trait Decompressor {
fn decompress(
&mut self,
input: &[u8],
output: &mut [u8],
) -> Result<CompressionStatus, CompressionError>;
fn reset(&mut self);
}
#[derive(Debug, Clone, Default)]
pub struct NoCompression {
finished: bool,
}
impl NoCompression {
pub fn new() -> Self {
Self { finished: false }
}
}
impl Compressor for NoCompression {
fn compress(
&mut self,
input: &[u8],
output: &mut [u8],
) -> Result<CompressionStatus, CompressionError> {
if self.finished {
return Err(CompressionError::AlreadyFinished);
}
let len = input.len().min(output.len());
output[..len].copy_from_slice(&input[..len]);
if len < input.len() {
Ok(CompressionStatus::OutputFull {
consumed: len,
produced: len,
})
} else {
Ok(CompressionStatus::Continue {
consumed: len,
produced: len,
})
}
}
fn finish(&mut self, _output: &mut [u8]) -> Result<CompressionStatus, CompressionError> {
if self.finished {
return Err(CompressionError::AlreadyFinished);
}
self.finished = true;
Ok(CompressionStatus::Complete {
consumed: 0,
produced: 0,
})
}
fn reset(&mut self) {
self.finished = false;
}
}
impl Decompressor for NoCompression {
fn decompress(
&mut self,
input: &[u8],
output: &mut [u8],
) -> Result<CompressionStatus, CompressionError> {
let len = input.len().min(output.len());
output[..len].copy_from_slice(&input[..len]);
if len < input.len() {
Ok(CompressionStatus::OutputFull {
consumed: len,
produced: len,
})
} else if input.is_empty() {
Ok(CompressionStatus::Complete {
consumed: 0,
produced: 0,
})
} else {
Ok(CompressionStatus::Continue {
consumed: len,
produced: len,
})
}
}
fn reset(&mut self) {
self.finished = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compression_status_consumed() {
assert_eq!(
CompressionStatus::Continue {
consumed: 10,
produced: 8
}
.consumed(),
10
);
assert_eq!(
CompressionStatus::Complete {
consumed: 5,
produced: 3
}
.consumed(),
5
);
assert_eq!(
CompressionStatus::OutputFull {
consumed: 7,
produced: 6
}
.consumed(),
7
);
}
#[test]
fn test_compression_status_produced() {
assert_eq!(
CompressionStatus::Continue {
consumed: 10,
produced: 8
}
.produced(),
8
);
assert_eq!(
CompressionStatus::Complete {
consumed: 5,
produced: 3
}
.produced(),
3
);
assert_eq!(
CompressionStatus::OutputFull {
consumed: 7,
produced: 6
}
.produced(),
6
);
}
#[test]
fn test_compression_status_is_complete() {
assert!(
!CompressionStatus::Continue {
consumed: 0,
produced: 0
}
.is_complete()
);
assert!(
CompressionStatus::Complete {
consumed: 0,
produced: 0
}
.is_complete()
);
assert!(
!CompressionStatus::OutputFull {
consumed: 0,
produced: 0
}
.is_complete()
);
}
#[test]
fn test_compression_status_is_output_full() {
assert!(
!CompressionStatus::Continue {
consumed: 0,
produced: 0
}
.is_output_full()
);
assert!(
!CompressionStatus::Complete {
consumed: 0,
produced: 0
}
.is_output_full()
);
assert!(
CompressionStatus::OutputFull {
consumed: 0,
produced: 0
}
.is_output_full()
);
}
#[test]
fn test_no_compression_compress() {
let mut comp = NoCompression::new();
let input = b"Hello, World!";
let mut output = vec![0u8; 32];
let status = comp.compress(input, &mut output).unwrap();
assert_eq!(status.consumed(), 13);
assert_eq!(status.produced(), 13);
assert_eq!(&output[..13], input);
}
#[test]
fn test_no_compression_compress_output_full() {
let mut comp = NoCompression::new();
let input = b"Hello, World!";
let mut output = vec![0u8; 5];
let status = comp.compress(input, &mut output).unwrap();
assert!(status.is_output_full());
assert_eq!(status.consumed(), 5);
assert_eq!(status.produced(), 5);
assert_eq!(&output[..5], b"Hello");
}
#[test]
fn test_no_compression_finish() {
let mut comp = NoCompression::new();
let mut output = vec![0u8; 32];
let status = comp.finish(&mut output).unwrap();
assert!(status.is_complete());
assert_eq!(status.consumed(), 0);
assert_eq!(status.produced(), 0);
}
#[test]
fn test_no_compression_already_finished() {
let mut comp = NoCompression::new();
let mut output = vec![0u8; 32];
comp.finish(&mut output).unwrap();
assert_eq!(
comp.finish(&mut output).unwrap_err(),
CompressionError::AlreadyFinished
);
assert_eq!(
comp.compress(b"test", &mut output).unwrap_err(),
CompressionError::AlreadyFinished
);
}
#[test]
fn test_no_compression_reset_compressor() {
let mut comp = NoCompression::new();
let mut output = vec![0u8; 32];
comp.finish(&mut output).unwrap();
Compressor::reset(&mut comp);
let status = comp.compress(b"test", &mut output).unwrap();
assert_eq!(status.consumed(), 4);
}
#[test]
fn test_no_compression_reset_decompressor() {
let mut decomp = NoCompression::new();
Decompressor::reset(&mut decomp);
let mut output = vec![0u8; 32];
let status = decomp.decompress(b"test", &mut output).unwrap();
assert_eq!(status.consumed(), 4);
}
#[test]
fn test_no_compression_decompress() {
let mut decomp = NoCompression::new();
let input = b"Hello, World!";
let mut output = vec![0u8; 32];
let status = decomp.decompress(input, &mut output).unwrap();
assert_eq!(status.consumed(), 13);
assert_eq!(status.produced(), 13);
assert_eq!(&output[..13], input);
}
#[test]
fn test_no_compression_decompress_output_full() {
let mut decomp = NoCompression::new();
let input = b"Hello, World!";
let mut output = vec![0u8; 5];
let status = decomp.decompress(input, &mut output).unwrap();
assert!(status.is_output_full());
assert_eq!(status.consumed(), 5);
assert_eq!(status.produced(), 5);
}
#[test]
fn test_no_compression_decompress_complete() {
let mut decomp = NoCompression::new();
let mut output = vec![0u8; 32];
let status = decomp.decompress(&[], &mut output).unwrap();
assert!(status.is_complete());
}
#[test]
fn test_compression_error_display() {
assert_eq!(
CompressionError::BufferTooSmall {
required: 100,
available: 50
}
.to_string(),
"buffer too small: required 100 bytes, available 50 bytes"
);
assert_eq!(
CompressionError::InvalidData("bad data".to_string()).to_string(),
"invalid data: bad data"
);
assert_eq!(
CompressionError::Internal("oops".to_string()).to_string(),
"internal error: oops"
);
assert_eq!(
CompressionError::UnexpectedEof.to_string(),
"unexpected end of input"
);
assert_eq!(
CompressionError::AlreadyFinished.to_string(),
"compression already finished"
);
}
}