use crate::error::{CompressionError, Result};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::Cursor;
#[derive(Debug, Clone)]
pub struct RleConfig {
pub max_run_length: usize,
}
impl Default for RleConfig {
fn default() -> Self {
Self {
max_run_length: 65535, }
}
}
pub struct RleCodec {
config: RleConfig,
}
impl RleCodec {
pub fn new() -> Self {
Self {
config: RleConfig::default(),
}
}
pub fn with_config(config: RleConfig) -> Result<Self> {
if config.max_run_length == 0 || config.max_run_length > u16::MAX as usize {
return Err(CompressionError::ConfigurationError(format!(
"RleConfig::max_run_length must be in 1..={}, got {}",
u16::MAX,
config.max_run_length
)));
}
Ok(Self { config })
}
pub fn compress(&self, input: &[u8]) -> Result<Vec<u8>> {
if input.is_empty() {
return Ok(Vec::new());
}
let mut output = Vec::new();
let mut i = 0;
while i < input.len() {
let value = input[i];
let mut run_length = 1;
while i + run_length < input.len()
&& input[i + run_length] == value
&& run_length < self.config.max_run_length
{
run_length += 1;
}
output.write_u16::<LittleEndian>(run_length as u16)?;
output.push(value);
i += run_length;
}
Ok(output)
}
pub fn decompress(&self, input: &[u8]) -> Result<Vec<u8>> {
if input.is_empty() {
return Ok(Vec::new());
}
let mut output = Vec::new();
let mut cursor = Cursor::new(input);
while cursor.position() < input.len() as u64 {
let run_length = cursor.read_u16::<LittleEndian>()? as usize;
let value = cursor.read_u8()?;
output.extend(std::iter::repeat_n(value, run_length));
}
Ok(output)
}
pub fn compress_byte(&self, input: &[u8]) -> Result<Vec<u8>> {
if input.is_empty() {
return Ok(Vec::new());
}
let mut output = Vec::new();
let mut i = 0;
while i < input.len() {
let value = input[i];
let mut run_length = 1;
while i + run_length < input.len() && input[i + run_length] == value && run_length < 255
{
run_length += 1;
}
output.push(run_length as u8);
output.push(value);
i += run_length;
}
Ok(output)
}
pub fn decompress_byte(&self, input: &[u8]) -> Result<Vec<u8>> {
if input.is_empty() {
return Ok(Vec::new());
}
if !input.len().is_multiple_of(2) {
return Err(CompressionError::RleError(
"Invalid RLE data: odd length".to_string(),
));
}
let mut output = Vec::new();
for chunk in input.chunks_exact(2) {
let run_length = chunk[0] as usize;
let value = chunk[1];
output.extend(std::iter::repeat_n(value, run_length));
}
Ok(output)
}
pub fn estimate_ratio(input: &[u8]) -> f64 {
if input.is_empty() {
return 1.0;
}
let mut runs = 0;
let mut i = 0;
while i < input.len() {
let value = input[i];
let mut run_length = 1;
while i + run_length < input.len() && input[i + run_length] == value {
run_length += 1;
}
runs += 1;
i += run_length;
}
let compressed_size = runs * 3;
input.len() as f64 / compressed_size as f64
}
}
impl Default for RleCodec {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rle_compress_decompress() {
let codec = RleCodec::new();
let data = vec![1u8; 100];
let compressed = codec.compress(&data).expect("Compression failed");
assert!(compressed.len() < data.len());
let decompressed = codec.decompress(&compressed).expect("Decompression failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_rle_byte_compress_decompress() {
let codec = RleCodec::new();
let data = vec![5u8; 50];
let compressed = codec.compress_byte(&data).expect("Compression failed");
assert!(compressed.len() < data.len());
let decompressed = codec
.decompress_byte(&compressed)
.expect("Decompression failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_rle_mixed_data() {
let codec = RleCodec::new();
let mut data = Vec::new();
data.extend(vec![1u8; 50]);
data.extend(vec![2u8; 30]);
data.extend(vec![3u8; 20]);
let compressed = codec.compress(&data).expect("Compression failed");
let decompressed = codec.decompress(&compressed).expect("Decompression failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_rle_empty_data() {
let codec = RleCodec::new();
let data: &[u8] = b"";
let compressed = codec.compress(data).expect("Compression failed");
assert_eq!(compressed.len(), 0);
let decompressed = codec.decompress(&compressed).expect("Decompression failed");
assert_eq!(decompressed.len(), 0);
}
#[test]
fn test_rle_estimate_ratio() {
let data = vec![1u8; 1000];
let ratio = RleCodec::estimate_ratio(&data);
assert!(ratio > 100.0); }
#[test]
fn test_rle_with_config_rejects_max_run_length_above_u16_max() {
let config = RleConfig {
max_run_length: 100_000, };
let result = RleCodec::with_config(config);
assert!(result.is_err());
}
#[test]
fn test_rle_with_config_rejects_zero_max_run_length() {
let config = RleConfig { max_run_length: 0 };
let result = RleCodec::with_config(config);
assert!(result.is_err());
}
#[test]
fn test_rle_with_config_accepts_u16_max() {
let config = RleConfig {
max_run_length: u16::MAX as usize,
};
let codec = RleCodec::with_config(config).expect("valid config should be accepted");
let data = vec![7u8; u16::MAX as usize];
let compressed = codec.compress(&data).expect("compression failed");
let decompressed = codec.decompress(&compressed).expect("decompression failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_rle_long_run_does_not_truncate_run_length() {
let codec = RleCodec::new(); let data = vec![9u8; 100_000];
let compressed = codec.compress(&data).expect("compression failed");
let decompressed = codec.decompress(&compressed).expect("decompression failed");
assert_eq!(decompressed, data);
assert_eq!(decompressed.len(), 100_000);
}
}