use crate::coding::{checked_count, checked_decode_count};
use crate::error::{EncodingError, Result};
const VALUE_TYPE_I64: u8 = 0;
const VALUE_TYPE_U64: u8 = 1;
const VALUE_TYPE_F64: u8 = 2;
const VALUE_TYPE_BOOL: u8 = 3;
const VALUE_TYPE_STRING: u8 = 4;
pub struct RleEncoder;
pub struct RleDecoder;
impl RleEncoder {
pub fn encode_i64(values: &[i64]) -> Result<Vec<u8>> {
if values.is_empty() {
return Err(EncodingError::EmptyInput {
context: "RLE i64 encoder",
});
}
let num_values = checked_count(values.len())?;
let runs = build_runs(values, |a, b| a == b);
let num_runs = checked_count(runs.len())?;
let mut buf = Vec::with_capacity(9 + runs.len() * 12);
buf.push(VALUE_TYPE_I64);
buf.extend_from_slice(&num_values.to_le_bytes());
buf.extend_from_slice(&num_runs.to_le_bytes());
for (val, run_len) in &runs {
buf.extend_from_slice(&val.to_le_bytes());
buf.extend_from_slice(&run_len.to_le_bytes());
}
Ok(buf)
}
pub fn encode_u64(values: &[u64]) -> Result<Vec<u8>> {
if values.is_empty() {
return Err(EncodingError::EmptyInput {
context: "RLE u64 encoder",
});
}
let num_values = checked_count(values.len())?;
let runs = build_runs(values, |a, b| a == b);
let num_runs = checked_count(runs.len())?;
let mut buf = Vec::with_capacity(9 + runs.len() * 12);
buf.push(VALUE_TYPE_U64);
buf.extend_from_slice(&num_values.to_le_bytes());
buf.extend_from_slice(&num_runs.to_le_bytes());
for (val, run_len) in &runs {
buf.extend_from_slice(&val.to_le_bytes());
buf.extend_from_slice(&run_len.to_le_bytes());
}
Ok(buf)
}
pub fn encode_f64(values: &[f64]) -> Result<Vec<u8>> {
if values.is_empty() {
return Err(EncodingError::EmptyInput {
context: "RLE f64 encoder",
});
}
let num_values = checked_count(values.len())?;
let runs = build_runs(values, |a, b| a.to_bits() == b.to_bits());
let num_runs = checked_count(runs.len())?;
let mut buf = Vec::with_capacity(9 + runs.len() * 12);
buf.push(VALUE_TYPE_F64);
buf.extend_from_slice(&num_values.to_le_bytes());
buf.extend_from_slice(&num_runs.to_le_bytes());
for (val, run_len) in &runs {
buf.extend_from_slice(&val.to_bits().to_le_bytes());
buf.extend_from_slice(&run_len.to_le_bytes());
}
Ok(buf)
}
pub fn encode_bool(values: &[bool]) -> Result<Vec<u8>> {
if values.is_empty() {
return Err(EncodingError::EmptyInput {
context: "RLE bool encoder",
});
}
let num_values = checked_count(values.len())?;
let runs = build_runs(values, |a, b| a == b);
let num_runs = checked_count(runs.len())?;
let mut buf = Vec::with_capacity(9 + runs.len() * 5);
buf.push(VALUE_TYPE_BOOL);
buf.extend_from_slice(&num_values.to_le_bytes());
buf.extend_from_slice(&num_runs.to_le_bytes());
for (val, run_len) in &runs {
buf.push(u8::from(*val));
buf.extend_from_slice(&run_len.to_le_bytes());
}
Ok(buf)
}
pub fn encode_string(values: &[&str]) -> Result<Vec<u8>> {
if values.is_empty() {
return Err(EncodingError::EmptyInput {
context: "RLE string encoder",
});
}
let num_values = checked_count(values.len())?;
let runs = build_runs(values, |a, b| a == b);
let num_runs = checked_count(runs.len())?;
let mut buf = Vec::with_capacity(9 + runs.len() * 16);
buf.push(VALUE_TYPE_STRING);
buf.extend_from_slice(&num_values.to_le_bytes());
buf.extend_from_slice(&num_runs.to_le_bytes());
for (val, run_len) in &runs {
let s_bytes = val.as_bytes();
let s_len = checked_count(s_bytes.len())?;
buf.extend_from_slice(&s_len.to_le_bytes());
buf.extend_from_slice(s_bytes);
buf.extend_from_slice(&run_len.to_le_bytes());
}
Ok(buf)
}
}
impl RleDecoder {
pub fn value_type(data: &[u8]) -> Result<u8> {
if data.is_empty() {
return Err(corrupt("RLE payload empty — no value type byte"));
}
Ok(data[0])
}
pub fn decode_i64(data: &[u8]) -> Result<Vec<i64>> {
let (num_values, num_runs, mut pos) = read_header(data)?;
let mut result = Vec::with_capacity(num_values);
for _ in 0..num_runs {
if pos + 12 > data.len() {
return Err(corrupt("i64 run data truncated"));
}
let val = i64::from_le_bytes(
data[pos..pos + 8]
.try_into()
.map_err(|_| corrupt("i64 value bytes"))?,
);
pos += 8;
let run_len = u32::from_le_bytes(
data[pos..pos + 4]
.try_into()
.map_err(|_| corrupt("i64 run length bytes"))?,
) as usize;
pos += 4;
checked_run(result.len(), run_len, num_values)?;
result.extend(std::iter::repeat_n(val, run_len));
}
verify_count(num_values, result.len())?;
Ok(result)
}
pub fn decode_u64(data: &[u8]) -> Result<Vec<u64>> {
let (num_values, num_runs, mut pos) = read_header(data)?;
let mut result = Vec::with_capacity(num_values);
for _ in 0..num_runs {
if pos + 12 > data.len() {
return Err(corrupt("u64 run data truncated"));
}
let val = u64::from_le_bytes(
data[pos..pos + 8]
.try_into()
.map_err(|_| corrupt("u64 value bytes"))?,
);
pos += 8;
let run_len = u32::from_le_bytes(
data[pos..pos + 4]
.try_into()
.map_err(|_| corrupt("u64 run length bytes"))?,
) as usize;
pos += 4;
checked_run(result.len(), run_len, num_values)?;
result.extend(std::iter::repeat_n(val, run_len));
}
verify_count(num_values, result.len())?;
Ok(result)
}
pub fn decode_f64(data: &[u8]) -> Result<Vec<f64>> {
let (num_values, num_runs, mut pos) = read_header(data)?;
let mut result = Vec::with_capacity(num_values);
for _ in 0..num_runs {
if pos + 12 > data.len() {
return Err(corrupt("f64 run data truncated"));
}
let bits = u64::from_le_bytes(
data[pos..pos + 8]
.try_into()
.map_err(|_| corrupt("f64 value bytes"))?,
);
let val = f64::from_bits(bits);
pos += 8;
let run_len = u32::from_le_bytes(
data[pos..pos + 4]
.try_into()
.map_err(|_| corrupt("f64 run length bytes"))?,
) as usize;
pos += 4;
checked_run(result.len(), run_len, num_values)?;
result.extend(std::iter::repeat_n(val, run_len));
}
verify_count(num_values, result.len())?;
Ok(result)
}
pub fn decode_bool(data: &[u8]) -> Result<Vec<bool>> {
let (num_values, num_runs, mut pos) = read_header(data)?;
let mut result = Vec::with_capacity(num_values);
for _ in 0..num_runs {
if pos + 5 > data.len() {
return Err(corrupt("bool run data truncated"));
}
let val = data[pos] != 0;
pos += 1;
let run_len = u32::from_le_bytes(
data[pos..pos + 4]
.try_into()
.map_err(|_| corrupt("bool run length bytes"))?,
) as usize;
pos += 4;
checked_run(result.len(), run_len, num_values)?;
result.extend(std::iter::repeat_n(val, run_len));
}
verify_count(num_values, result.len())?;
Ok(result)
}
pub fn decode_string(data: &[u8]) -> Result<Vec<String>> {
let (num_values, num_runs, mut pos) = read_header(data)?;
let mut result = Vec::with_capacity(num_values);
for _ in 0..num_runs {
if pos + 4 > data.len() {
return Err(corrupt("string run: string_len truncated"));
}
let s_len = u32::from_le_bytes(
data[pos..pos + 4]
.try_into()
.map_err(|_| corrupt("string length bytes"))?,
) as usize;
pos += 4;
if pos + s_len > data.len() {
return Err(corrupt("string run: string bytes truncated"));
}
let s = std::str::from_utf8(&data[pos..pos + s_len])
.map_err(|e| EncodingError::CorruptData {
detail: format!("RLE string: invalid UTF-8: {e}"),
})?
.to_owned();
pos += s_len;
if pos + 4 > data.len() {
return Err(corrupt("string run: run_length truncated"));
}
let run_len = u32::from_le_bytes(
data[pos..pos + 4]
.try_into()
.map_err(|_| corrupt("string run length bytes"))?,
) as usize;
pos += 4;
checked_run(result.len(), run_len, num_values)?;
result.extend(std::iter::repeat_n(s, run_len));
}
verify_count(num_values, result.len())?;
Ok(result)
}
}
fn build_runs<T: Copy, F: Fn(&T, &T) -> bool>(values: &[T], eq: F) -> Vec<(T, u32)> {
let mut runs = Vec::new();
if values.is_empty() {
return runs;
}
let mut current = values[0];
let mut count: u32 = 1;
for &v in &values[1..] {
if eq(&v, ¤t) {
if count == u32::MAX {
runs.push((current, count));
count = 1;
} else {
count += 1;
}
} else {
runs.push((current, count));
current = v;
count = 1;
}
}
runs.push((current, count));
runs
}
const MIN_RUN_BYTES: usize = 5;
fn read_header(data: &[u8]) -> Result<(usize, usize, usize)> {
if data.len() < 9 {
return Err(corrupt("RLE header too short"));
}
let num_values = checked_decode_count(
u32::from_le_bytes(
data[1..5]
.try_into()
.map_err(|_| corrupt("header num_values bytes"))?,
) as usize,
"RLE",
)?;
let num_runs = u32::from_le_bytes(
data[5..9]
.try_into()
.map_err(|_| corrupt("header num_runs bytes"))?,
) as usize;
let max_runs = (data.len() - 9) / MIN_RUN_BYTES;
if num_runs > max_runs {
return Err(corrupt(
"RLE block declares more runs than the payload can hold",
));
}
Ok((num_values, num_runs, 9))
}
#[inline]
fn checked_run(decoded: usize, run_len: usize, num_values: usize) -> Result<()> {
if decoded + run_len > num_values {
return Err(corrupt(
"RLE run lengths exceed the declared value count — header is corrupt",
));
}
Ok(())
}
fn verify_count(expected: usize, actual: usize) -> Result<()> {
if expected != actual {
return Err(EncodingError::CountMismatch { expected, actual });
}
Ok(())
}
fn corrupt(detail: &str) -> EncodingError {
EncodingError::CorruptData {
detail: detail.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn i64_constant_roundtrip() {
let values = vec![42_i64; 1000];
let encoded = RleEncoder::encode_i64(&values).unwrap();
assert!(encoded.len() < values.len() * 8);
let decoded = RleDecoder::decode_i64(&encoded).unwrap();
assert_eq!(values, decoded);
}
#[test]
fn i64_varied_roundtrip() {
let values: Vec<i64> = (0..500).collect();
let encoded = RleEncoder::encode_i64(&values).unwrap();
let decoded = RleDecoder::decode_i64(&encoded).unwrap();
assert_eq!(values, decoded);
}
#[test]
fn i64_runs_roundtrip() {
let mut values = Vec::new();
values.extend(std::iter::repeat_n(1_i64, 100));
values.extend(std::iter::repeat_n(2_i64, 200));
values.extend(std::iter::repeat_n(3_i64, 100));
let encoded = RleEncoder::encode_i64(&values).unwrap();
let decoded = RleDecoder::decode_i64(&encoded).unwrap();
assert_eq!(values, decoded);
}
#[test]
fn i64_single_value() {
let values = vec![99_i64];
let encoded = RleEncoder::encode_i64(&values).unwrap();
let decoded = RleDecoder::decode_i64(&encoded).unwrap();
assert_eq!(values, decoded);
}
#[test]
fn i64_empty_error() {
assert!(RleEncoder::encode_i64(&[]).is_err());
}
#[test]
fn i64_negative_values() {
let values = vec![-1_i64; 50];
let encoded = RleEncoder::encode_i64(&values).unwrap();
let decoded = RleDecoder::decode_i64(&encoded).unwrap();
assert_eq!(values, decoded);
}
#[test]
fn u64_constant_roundtrip() {
let values = vec![100_u64; 500];
let encoded = RleEncoder::encode_u64(&values).unwrap();
let decoded = RleDecoder::decode_u64(&encoded).unwrap();
assert_eq!(values, decoded);
}
#[test]
fn u64_varied_roundtrip() {
let values: Vec<u64> = (0..200).collect();
let encoded = RleEncoder::encode_u64(&values).unwrap();
let decoded = RleDecoder::decode_u64(&encoded).unwrap();
assert_eq!(values, decoded);
}
#[test]
fn u64_empty_error() {
assert!(RleEncoder::encode_u64(&[]).is_err());
}
#[test]
fn f64_constant_roundtrip() {
let values = vec![2.75_f64; 1000];
let encoded = RleEncoder::encode_f64(&values).unwrap();
assert!(encoded.len() < values.len() * 8);
let decoded = RleDecoder::decode_f64(&encoded).unwrap();
assert_eq!(values.len(), decoded.len());
for (a, b) in values.iter().zip(decoded.iter()) {
assert_eq!(a.to_bits(), b.to_bits());
}
}
#[test]
fn f64_nan_roundtrip() {
let values = vec![f64::NAN; 100];
let encoded = RleEncoder::encode_f64(&values).unwrap();
let decoded = RleDecoder::decode_f64(&encoded).unwrap();
assert_eq!(values.len(), decoded.len());
for v in &decoded {
assert!(v.is_nan());
}
}
#[test]
fn f64_mixed_special_values() {
let values = vec![
f64::INFINITY,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NEG_INFINITY,
f64::NAN,
0.0,
-0.0, ];
let encoded = RleEncoder::encode_f64(&values).unwrap();
let decoded = RleDecoder::decode_f64(&encoded).unwrap();
assert_eq!(values.len(), decoded.len());
for (a, b) in values.iter().zip(decoded.iter()) {
assert_eq!(a.to_bits(), b.to_bits());
}
}
#[test]
fn f64_empty_error() {
assert!(RleEncoder::encode_f64(&[]).is_err());
}
#[test]
fn bool_constant_roundtrip() {
let values = vec![true; 500];
let encoded = RleEncoder::encode_bool(&values).unwrap();
assert!(encoded.len() < values.len());
let decoded = RleDecoder::decode_bool(&encoded).unwrap();
assert_eq!(values, decoded);
}
#[test]
fn bool_alternating_roundtrip() {
let values: Vec<bool> = (0..100).map(|i| i % 2 == 0).collect();
let encoded = RleEncoder::encode_bool(&values).unwrap();
let decoded = RleDecoder::decode_bool(&encoded).unwrap();
assert_eq!(values, decoded);
}
#[test]
fn bool_runs_roundtrip() {
let mut values = Vec::new();
values.extend(std::iter::repeat_n(true, 50));
values.extend(std::iter::repeat_n(false, 100));
values.extend(std::iter::repeat_n(true, 30));
let encoded = RleEncoder::encode_bool(&values).unwrap();
let decoded = RleDecoder::decode_bool(&encoded).unwrap();
assert_eq!(values, decoded);
}
#[test]
fn bool_empty_error() {
assert!(RleEncoder::encode_bool(&[]).is_err());
}
#[test]
fn string_constant_roundtrip() {
let values = vec!["us-east-1"; 200];
let encoded = RleEncoder::encode_string(&values).unwrap();
let decoded = RleDecoder::decode_string(&encoded).unwrap();
let expected: Vec<String> = values.iter().map(|s| (*s).to_owned()).collect();
assert_eq!(expected, decoded);
}
#[test]
fn string_runs_roundtrip() {
let values = vec!["a", "a", "a", "b", "b", "c", "c", "c", "c"];
let encoded = RleEncoder::encode_string(&values).unwrap();
let decoded = RleDecoder::decode_string(&encoded).unwrap();
let expected: Vec<String> = values.iter().map(|s| (*s).to_owned()).collect();
assert_eq!(expected, decoded);
}
#[test]
fn string_varied_roundtrip() {
let owned: Vec<String> = (0..50).map(|i| format!("val-{i}")).collect();
let refs: Vec<&str> = owned.iter().map(String::as_str).collect();
let encoded = RleEncoder::encode_string(&refs).unwrap();
let decoded = RleDecoder::decode_string(&encoded).unwrap();
assert_eq!(owned, decoded);
}
#[test]
fn string_empty_strings() {
let values = vec![""; 100];
let encoded = RleEncoder::encode_string(&values).unwrap();
let decoded = RleDecoder::decode_string(&encoded).unwrap();
let expected: Vec<String> = values.iter().map(|s| (*s).to_owned()).collect();
assert_eq!(expected, decoded);
}
#[test]
fn string_empty_error() {
let empty: &[&str] = &[];
assert!(RleEncoder::encode_string(empty).is_err());
}
#[test]
fn decode_truncated_header() {
assert!(RleDecoder::decode_i64(&[0, 1, 2, 3]).is_err());
}
#[test]
fn decode_truncated_run() {
let mut data = vec![VALUE_TYPE_I64];
data.extend_from_slice(&1_u32.to_le_bytes());
data.extend_from_slice(&1_u32.to_le_bytes());
assert!(RleDecoder::decode_i64(&data).is_err());
}
#[test]
fn decode_count_mismatch() {
let mut data = vec![VALUE_TYPE_I64];
data.extend_from_slice(&999_u32.to_le_bytes()); data.extend_from_slice(&1_u32.to_le_bytes()); data.extend_from_slice(&42_i64.to_le_bytes()); data.extend_from_slice(&1_u32.to_le_bytes()); assert!(RleDecoder::decode_i64(&data).is_err());
}
#[test]
fn constant_column_achieves_high_compression() {
let values = vec![42_i64; 10_000];
let encoded = RleEncoder::encode_i64(&values).unwrap();
let ratio = (values.len() * 8) as f64 / encoded.len() as f64;
assert!(ratio > 100.0, "Expected >100× compression, got {ratio:.1}×");
}
}
#[cfg(test)]
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn roundtrip_i64(values in proptest::collection::vec(any::<i64>(), 1..300)) {
let encoded = RleEncoder::encode_i64(&values).unwrap();
let decoded = RleDecoder::decode_i64(&encoded).unwrap();
prop_assert_eq!(&values, &decoded);
}
#[test]
fn roundtrip_u64(values in proptest::collection::vec(any::<u64>(), 1..300)) {
let encoded = RleEncoder::encode_u64(&values).unwrap();
let decoded = RleDecoder::decode_u64(&encoded).unwrap();
prop_assert_eq!(&values, &decoded);
}
#[test]
fn roundtrip_f64(values in proptest::collection::vec(any::<f64>(), 1..300)) {
let encoded = RleEncoder::encode_f64(&values).unwrap();
let decoded = RleDecoder::decode_f64(&encoded).unwrap();
prop_assert_eq!(values.len(), decoded.len());
for (a, b) in values.iter().zip(decoded.iter()) {
prop_assert_eq!(a.to_bits(), b.to_bits());
}
}
#[test]
fn roundtrip_bool(values in proptest::collection::vec(any::<bool>(), 1..300)) {
let encoded = RleEncoder::encode_bool(&values).unwrap();
let decoded = RleDecoder::decode_bool(&encoded).unwrap();
prop_assert_eq!(&values, &decoded);
}
#[test]
fn roundtrip_string(values in proptest::collection::vec("[a-z]{0,8}", 1..100)) {
let refs: Vec<&str> = values.iter().map(String::as_str).collect();
let encoded = RleEncoder::encode_string(&refs).unwrap();
let decoded = RleDecoder::decode_string(&encoded).unwrap();
prop_assert_eq!(&values, &decoded);
}
}
}
#[cfg(test)]
mod bomb_tests {
use super::*;
#[test]
fn a_declared_value_count_cannot_size_an_allocation() {
let mut payload = vec![VALUE_TYPE_I64];
payload.extend_from_slice(&u32::MAX.to_le_bytes()); payload.extend_from_slice(&0u32.to_le_bytes()); let err = RleDecoder::decode_i64(&payload).expect_err("must refuse the count");
assert!(
err.to_string().contains("ceiling"),
"expected the decode ceiling to reject it, got: {err}"
);
}
#[test]
fn a_run_length_cannot_expand_past_the_declared_count() {
let mut payload = vec![VALUE_TYPE_I64];
payload.extend_from_slice(&3u32.to_le_bytes()); payload.extend_from_slice(&1u32.to_le_bytes()); payload.extend_from_slice(&7i64.to_le_bytes()); payload.extend_from_slice(&u32::MAX.to_le_bytes()); let err = RleDecoder::decode_i64(&payload).expect_err("must refuse the run");
assert!(
err.to_string().contains("exceed the declared value count"),
"expected the run guard to reject it, got: {err}"
);
}
#[test]
fn a_string_run_cannot_expand_past_the_declared_count() {
let mut payload = vec![VALUE_TYPE_STRING];
payload.extend_from_slice(&1u32.to_le_bytes()); payload.extend_from_slice(&1u32.to_le_bytes()); payload.extend_from_slice(&1u32.to_le_bytes()); payload.push(b'x');
payload.extend_from_slice(&u32::MAX.to_le_bytes()); let err = RleDecoder::decode_string(&payload).expect_err("must refuse the run");
assert!(
err.to_string().contains("exceed the declared value count"),
"expected the run guard to reject it, got: {err}"
);
}
#[test]
fn a_run_count_beyond_the_payload_is_rejected() {
let mut payload = vec![VALUE_TYPE_BOOL];
payload.extend_from_slice(&10u32.to_le_bytes()); payload.extend_from_slice(&1_000_000u32.to_le_bytes()); let err = RleDecoder::decode_bool(&payload).expect_err("must refuse the run count");
assert!(
err.to_string().contains("more runs than the payload"),
"expected the run-count guard to reject it, got: {err}"
);
}
#[test]
fn legitimate_blocks_still_round_trip() {
let values: Vec<i64> = std::iter::repeat_n(4, 1000)
.chain(std::iter::repeat_n(9, 500))
.collect();
let encoded = RleEncoder::encode_i64(&values).expect("encode");
assert_eq!(RleDecoder::decode_i64(&encoded).expect("decode"), values);
}
}