use crate::error::{CompressionError, Result};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::Cursor;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeltaDataType {
I8,
I16,
I32,
I64,
U8,
U16,
U32,
U64,
F32,
F64,
}
impl DeltaDataType {
pub fn size(&self) -> usize {
match self {
DeltaDataType::I8 | DeltaDataType::U8 => 1,
DeltaDataType::I16 | DeltaDataType::U16 => 2,
DeltaDataType::I32 | DeltaDataType::U32 | DeltaDataType::F32 => 4,
DeltaDataType::I64 | DeltaDataType::U64 | DeltaDataType::F64 => 8,
}
}
}
#[derive(Debug, Clone)]
pub struct DeltaConfig {
pub data_type: DeltaDataType,
pub order: usize,
}
impl Default for DeltaConfig {
fn default() -> Self {
Self {
data_type: DeltaDataType::I32,
order: 1,
}
}
}
impl DeltaConfig {
pub fn with_data_type(data_type: DeltaDataType) -> Self {
Self {
data_type,
..Default::default()
}
}
pub fn with_order(mut self, order: usize) -> Self {
self.order = order;
self
}
}
fn effective_order(order: usize, value_count: usize) -> usize {
if value_count <= 1 {
0
} else {
order.min(value_count - 1).min(u8::MAX as usize)
}
}
fn diff_pass_i32(values: &mut [i32], passes: usize) {
for _ in 0..passes {
for i in (1..values.len()).rev() {
values[i] = values[i].wrapping_sub(values[i - 1]);
}
}
}
fn cumsum_pass_i32(values: &mut [i32], passes: usize) {
for _ in 0..passes {
for i in 1..values.len() {
values[i] = values[i].wrapping_add(values[i - 1]);
}
}
}
fn diff_pass_i64(values: &mut [i64], passes: usize) {
for _ in 0..passes {
for i in (1..values.len()).rev() {
values[i] = values[i].wrapping_sub(values[i - 1]);
}
}
}
fn cumsum_pass_i64(values: &mut [i64], passes: usize) {
for _ in 0..passes {
for i in 1..values.len() {
values[i] = values[i].wrapping_add(values[i - 1]);
}
}
}
pub struct DeltaCodec {
config: DeltaConfig,
}
impl DeltaCodec {
pub fn new() -> Self {
Self {
config: DeltaConfig::default(),
}
}
pub fn with_config(config: DeltaConfig) -> Self {
Self { config }
}
pub fn compress(&self, input: &[u8]) -> Result<Vec<u8>> {
if input.is_empty() {
return Ok(Vec::new());
}
match self.config.data_type {
DeltaDataType::I32 => self.compress_i32(input),
DeltaDataType::I64 => self.compress_i64(input),
DeltaDataType::F32 => self.compress_f32(input),
DeltaDataType::F64 => self.compress_f64(input),
_ => Err(CompressionError::UnsupportedDataType(format!(
"Delta encoding not implemented for {:?}",
self.config.data_type
))),
}
}
pub fn decompress(&self, input: &[u8]) -> Result<Vec<u8>> {
if input.is_empty() {
return Ok(Vec::new());
}
match self.config.data_type {
DeltaDataType::I32 => self.decompress_i32(input),
DeltaDataType::I64 => self.decompress_i64(input),
DeltaDataType::F32 => self.decompress_f32(input),
DeltaDataType::F64 => self.decompress_f64(input),
_ => Err(CompressionError::UnsupportedDataType(format!(
"Delta decoding not implemented for {:?}",
self.config.data_type
))),
}
}
fn compress_i32(&self, input: &[u8]) -> Result<Vec<u8>> {
let mut cursor = Cursor::new(input);
let mut values = Vec::new();
while let Ok(value) = cursor.read_i32::<LittleEndian>() {
values.push(value);
}
if values.is_empty() {
return Ok(Vec::new());
}
let order = effective_order(self.config.order, values.len());
diff_pass_i32(&mut values, order);
let mut output = Vec::with_capacity(values.len() * 4 + 1);
output.push(order as u8);
for value in &values {
output.write_i32::<LittleEndian>(*value)?;
}
Ok(output)
}
fn decompress_i32(&self, input: &[u8]) -> Result<Vec<u8>> {
let mut cursor = Cursor::new(input);
let order = cursor.read_u8()? as usize;
let first = cursor.read_i32::<LittleEndian>()?;
let mut values = vec![first];
while let Ok(value) = cursor.read_i32::<LittleEndian>() {
values.push(value);
}
cumsum_pass_i32(&mut values, order);
let mut output = Vec::with_capacity(values.len() * 4);
for value in values {
output.write_i32::<LittleEndian>(value)?;
}
Ok(output)
}
fn compress_i64(&self, input: &[u8]) -> Result<Vec<u8>> {
let mut cursor = Cursor::new(input);
let mut values = Vec::new();
while let Ok(value) = cursor.read_i64::<LittleEndian>() {
values.push(value);
}
if values.is_empty() {
return Ok(Vec::new());
}
let order = effective_order(self.config.order, values.len());
diff_pass_i64(&mut values, order);
let mut output = Vec::with_capacity(values.len() * 8 + 1);
output.push(order as u8);
for value in &values {
output.write_i64::<LittleEndian>(*value)?;
}
Ok(output)
}
fn decompress_i64(&self, input: &[u8]) -> Result<Vec<u8>> {
let mut cursor = Cursor::new(input);
let order = cursor.read_u8()? as usize;
let first = cursor.read_i64::<LittleEndian>()?;
let mut values = vec![first];
while let Ok(value) = cursor.read_i64::<LittleEndian>() {
values.push(value);
}
cumsum_pass_i64(&mut values, order);
let mut output = Vec::with_capacity(values.len() * 8);
for value in values {
output.write_i64::<LittleEndian>(value)?;
}
Ok(output)
}
fn compress_f32(&self, input: &[u8]) -> Result<Vec<u8>> {
let mut cursor = Cursor::new(input);
let mut values = Vec::new();
while let Ok(value) = cursor.read_f32::<LittleEndian>() {
values.push(value.to_bits() as i32);
}
if values.is_empty() {
return Ok(Vec::new());
}
let order = effective_order(self.config.order, values.len());
diff_pass_i32(&mut values, order);
let mut output = Vec::with_capacity(values.len() * 4 + 1);
output.push(order as u8);
for value in &values {
output.write_i32::<LittleEndian>(*value)?;
}
Ok(output)
}
fn decompress_f32(&self, input: &[u8]) -> Result<Vec<u8>> {
let mut cursor = Cursor::new(input);
let order = cursor.read_u8()? as usize;
let first = cursor.read_i32::<LittleEndian>()?;
let mut values = vec![first];
while let Ok(value) = cursor.read_i32::<LittleEndian>() {
values.push(value);
}
cumsum_pass_i32(&mut values, order);
let mut output = Vec::with_capacity(values.len() * 4);
for value in values {
output.write_f32::<LittleEndian>(f32::from_bits(value as u32))?;
}
Ok(output)
}
fn compress_f64(&self, input: &[u8]) -> Result<Vec<u8>> {
let mut cursor = Cursor::new(input);
let mut values = Vec::new();
while let Ok(value) = cursor.read_f64::<LittleEndian>() {
values.push(value.to_bits() as i64);
}
if values.is_empty() {
return Ok(Vec::new());
}
let order = effective_order(self.config.order, values.len());
diff_pass_i64(&mut values, order);
let mut output = Vec::with_capacity(values.len() * 8 + 1);
output.push(order as u8);
for value in &values {
output.write_i64::<LittleEndian>(*value)?;
}
Ok(output)
}
fn decompress_f64(&self, input: &[u8]) -> Result<Vec<u8>> {
let mut cursor = Cursor::new(input);
let order = cursor.read_u8()? as usize;
let first = cursor.read_i64::<LittleEndian>()?;
let mut values = vec![first];
while let Ok(value) = cursor.read_i64::<LittleEndian>() {
values.push(value);
}
cumsum_pass_i64(&mut values, order);
let mut output = Vec::with_capacity(values.len() * 8);
for value in values {
output.write_f64::<LittleEndian>(f64::from_bits(value as u64))?;
}
Ok(output)
}
}
impl Default for DeltaCodec {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_delta_i32() {
let config = DeltaConfig::with_data_type(DeltaDataType::I32);
let codec = DeltaCodec::with_config(config);
let mut data = Vec::new();
for i in 0..100 {
data.write_i32::<LittleEndian>(i * 10).ok();
}
let compressed = codec.compress(&data).expect("Compression failed");
let decompressed = codec.decompress(&compressed).expect("Decompression failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_delta_f64() {
let config = DeltaConfig::with_data_type(DeltaDataType::F64);
let codec = DeltaCodec::with_config(config);
let mut data = Vec::new();
for i in 0..100 {
data.write_f64::<LittleEndian>(i as f64 * 0.1).ok();
}
let compressed = codec.compress(&data).expect("Compression failed");
let decompressed = codec.decompress(&compressed).expect("Decompression failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_delta_i32_multi_order_roundtrip() {
let mut data = Vec::new();
for i in 0..64i32 {
data.write_i32::<LittleEndian>(i * i).ok();
}
for order in [1usize, 2, 3] {
let config = DeltaConfig::with_data_type(DeltaDataType::I32).with_order(order);
let codec = DeltaCodec::with_config(config);
let compressed = codec
.compress(&data)
.unwrap_or_else(|e| panic!("compression failed for order {order}: {e}"));
let decompressed = codec
.decompress(&compressed)
.unwrap_or_else(|e| panic!("decompression failed for order {order}: {e}"));
assert_eq!(decompressed, data, "round-trip mismatch at order {order}");
}
}
#[test]
fn test_delta_f64_multi_order_roundtrip() {
let mut data = Vec::new();
for i in 0..64i64 {
data.write_f64::<LittleEndian>((i * i) as f64 * 0.5).ok();
}
for order in [1usize, 2, 3] {
let config = DeltaConfig::with_data_type(DeltaDataType::F64).with_order(order);
let codec = DeltaCodec::with_config(config);
let compressed = codec
.compress(&data)
.unwrap_or_else(|e| panic!("compression failed for order {order}: {e}"));
let decompressed = codec
.decompress(&compressed)
.unwrap_or_else(|e| panic!("decompression failed for order {order}: {e}"));
assert_eq!(decompressed, data, "round-trip mismatch at order {order}");
}
}
#[test]
fn test_delta_order_changes_encoded_bytes() {
let mut data = Vec::new();
for i in 0..32i32 {
data.write_i32::<LittleEndian>(i * i).ok();
}
let codec_order1 =
DeltaCodec::with_config(DeltaConfig::with_data_type(DeltaDataType::I32).with_order(1));
let codec_order2 =
DeltaCodec::with_config(DeltaConfig::with_data_type(DeltaDataType::I32).with_order(2));
let compressed1 = codec_order1
.compress(&data)
.expect("order=1 compression failed");
let compressed2 = codec_order2
.compress(&data)
.expect("order=2 compression failed");
assert_ne!(
compressed1, compressed2,
"order=1 and order=2 must produce different encoded bytes for quadratic input"
);
let decompressed1 = codec_order1
.decompress(&compressed1)
.expect("order=1 decompression failed");
let decompressed2 = codec_order2
.decompress(&compressed2)
.expect("order=2 decompression failed");
assert_eq!(decompressed1, data);
assert_eq!(decompressed2, data);
}
#[test]
fn test_delta_decompress_self_describing_ignores_decoder_config_order() {
let mut data = Vec::new();
for i in 0..40i32 {
data.write_i32::<LittleEndian>(i * i * i).ok();
}
let encoder =
DeltaCodec::with_config(DeltaConfig::with_data_type(DeltaDataType::I32).with_order(3));
let compressed = encoder.compress(&data).expect("compression failed");
let decoder =
DeltaCodec::with_config(DeltaConfig::with_data_type(DeltaDataType::I32).with_order(0));
let decompressed = decoder
.decompress(&compressed)
.expect("decompression failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_delta_order_clamped_for_small_and_oversized_requests() {
let mut single = Vec::new();
single.write_i32::<LittleEndian>(42).ok();
let codec =
DeltaCodec::with_config(DeltaConfig::with_data_type(DeltaDataType::I32).with_order(9));
let compressed = codec.compress(&single).expect("compression failed");
assert_eq!(compressed[0], 0, "order must clamp to 0 for a single value");
let decompressed = codec.decompress(&compressed).expect("decompression failed");
assert_eq!(decompressed, single);
let mut data = Vec::new();
for i in 0..10i32 {
data.write_i32::<LittleEndian>(i).ok();
}
let codec = DeltaCodec::with_config(
DeltaConfig::with_data_type(DeltaDataType::I32).with_order(1000),
);
let compressed = codec.compress(&data).expect("compression failed");
assert_eq!(compressed[0], 9, "order must clamp to value_count - 1");
let decompressed = codec.decompress(&compressed).expect("decompression failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_delta_order_zero_is_pass_through() {
let mut data = Vec::new();
for i in 0..20i32 {
data.write_i32::<LittleEndian>(i * 7 + 3).ok();
}
let codec =
DeltaCodec::with_config(DeltaConfig::with_data_type(DeltaDataType::I32).with_order(0));
let compressed = codec.compress(&data).expect("compression failed");
assert_eq!(compressed[0], 0);
let mut expected = vec![0u8]; expected.extend_from_slice(&data);
assert_eq!(compressed, expected);
let decompressed = codec.decompress(&compressed).expect("decompression failed");
assert_eq!(decompressed, data);
}
}