#![expect(clippy::redundant_pub_crate)]
use crate::error::Error;
use crate::error::Result;
const OUT_BUF_LEN: usize = 4096;
const BYTE_OUT_MAX: usize = 32;
const MIXER_INIT: [u16; 17] = {
let mut row = [0_u16; 17];
let mut step = 0_usize;
while step < 17 {
#[expect(clippy::cast_possible_truncation)]
let value = ((step << 12) as u32).wrapping_sub((step == 16) as u32) as u16;
row[step] = value;
step += 1;
}
row
};
#[derive(Clone)]
pub(crate) struct CmCoder {
c0: [u16; 256],
c1: [[u16; 256]; 256],
c2: [[u16; 17]; 512],
}
#[inline]
fn next_byte(input: &[u8], pos: usize, filler: usize) -> Result<u8> {
match input.get(pos) {
Some(&byte) => Ok(byte),
None => fill_byte(pos - input.len(), filler),
}
}
#[cold]
const fn fill_byte(offset: usize, filler: usize) -> Result<u8> {
if offset < filler {
Ok(0xFF)
} else {
Err(Error::TruncatedData)
}
}
#[derive(Debug)]
struct BitCursor {
low: u32,
high: u32,
prev_first: u8,
prev_second: u8,
run: u32,
}
impl BitCursor {
#[inline]
const fn fresh() -> Self {
Self {
low: 0,
high: 0xFFFF_FFFF,
prev_first: 0,
prev_second: 0,
run: 0,
}
}
#[inline]
fn flag(&mut self) -> usize {
if self.prev_first == self.prev_second {
self.run += 1;
} else {
self.run = 0;
}
usize::from(self.run > 2)
}
#[inline]
const fn advance(&mut self, byte: u8) {
self.prev_second = self.prev_first;
self.prev_first = byte;
}
}
impl CmCoder {
#[expect(clippy::large_stack_arrays)]
pub(crate) fn new() -> Self {
let mut state = Self {
c0: [0; 256],
c1: [[0; 256]; 256],
c2: [[0; 17]; 512],
};
state.reset();
state
}
pub(crate) fn reset(&mut self) {
self.c0.fill(1 << 15);
for row in &mut self.c1 {
row.fill(1 << 15);
}
self.c2.fill(MIXER_INIT);
}
pub(crate) fn encode(&mut self, input: &[u8], output: &mut Vec<u8>) {
self.reset();
let mut cursor = BitCursor::fresh();
let mut out_buf = [0_u8; OUT_BUF_LEN];
let mut used = 0_usize;
for &byte in input {
flush_full(output, &out_buf, &mut used);
let flag = cursor.flag();
self.encode_byte(byte, flag, &mut cursor, &mut out_buf, &mut used);
cursor.advance(byte);
}
output.extend_from_slice(&out_buf[..used]);
output.extend_from_slice(&cursor.low.to_be_bytes());
}
pub(crate) fn decode(&mut self, input: &[u8], filler: usize, output: &mut [u8]) -> Result<()> {
if output.is_empty() {
return Ok(());
}
self.reset();
let mut pos = 0_usize;
let mut cursor = BitCursor::fresh();
let mut code = 0_u32;
for _ in 0..4 {
let byte = next_byte(input, pos, filler)?;
pos += 1;
code = (code << 8) | u32::from(byte);
}
for slot in output.iter_mut() {
let flag = cursor.flag();
let byte = self.decode_byte(input, filler, flag, &mut cursor, &mut code, &mut pos)?;
cursor.advance(byte);
*slot = byte;
}
Ok(())
}
#[inline]
fn predict(
&self,
context: usize,
first: u8,
second: u8,
flag: usize,
form: fn(u16, u16, u32) -> u32,
) -> (u32, usize) {
let direct = u32::from(self.c0[context]) + u32::from(self.c1[usize::from(first)][context]);
let mixed = direct * 7 + u32::from(self.c1[usize::from(second)][context]) * 2;
let estimate = mixed >> 4;
let step = (estimate >> 12) as usize;
let row = &self.c2[2 * context + flag];
let point = form(row[step], row[step + 1], estimate & 4095);
let mix = point * 3 + estimate;
(mix, step)
}
#[inline]
fn encode_byte(
&mut self,
byte: u8,
flag: usize,
cursor: &mut BitCursor,
out_buf: &mut [u8; OUT_BUF_LEN],
used: &mut usize,
) {
let mut context = 1_usize;
let mut bits = byte;
while context < 256 {
let (split, step) = self.predict(
context,
cursor.prev_first,
cursor.prev_second,
flag,
interpolate,
);
let mid = cursor
.low
.wrapping_add(scaled_width(cursor.low, cursor.high, split));
let bit = bits & 128 != 0;
let keep = u32::from(bit).wrapping_neg();
cursor.low = (cursor.low & keep) | (mid.wrapping_add(1) & !keep);
cursor.high = (mid & keep) | (cursor.high & !keep);
flush_shared_bytes(&mut cursor.low, &mut cursor.high, out_buf, used);
self.adapt(context, cursor.prev_first, flag, step, bit);
context = 2 * context + usize::from(bit);
bits <<= 1;
}
}
#[inline]
fn decode_byte(
&mut self,
input: &[u8],
filler: usize,
flag: usize,
cursor: &mut BitCursor,
code: &mut u32,
pos: &mut usize,
) -> Result<u8> {
let (first, second) = (cursor.prev_first, cursor.prev_second);
let mut context = 1_usize;
let mut current = self.predict(1, first, second, flag, interpolate);
while context < 256 {
let (split, step) = current;
let (zero, one) = if context < 128 {
(
self.predict(2 * context, first, second, flag, interpolate),
self.predict(2 * context + 1, first, second, flag, interpolate_weighted),
)
} else {
((0, 0), (0, 0))
};
let mid = cursor
.low
.wrapping_add(scaled_width(cursor.low, cursor.high, split));
let bit = *code <= mid;
let keep = u32::from(bit).wrapping_neg();
let over = mid.wrapping_add(1);
cursor.low = (cursor.low & keep) | (over & !keep);
cursor.high = (mid & keep) | (cursor.high & !keep);
renormalize_after_bit(input, filler, cursor, code, pos)?;
self.adapt(context, cursor.prev_first, flag, step, bit);
context = 2 * context + usize::from(bit);
let pick = usize::from(bit).wrapping_neg();
current = (
(one.0 & keep) | (zero.0 & !keep),
(one.1 & pick) | (zero.1 & !pick),
);
}
#[expect(clippy::cast_possible_truncation)]
let byte = (context & 255) as u8;
Ok(byte)
}
#[inline]
fn adapt(&mut self, context: usize, first: u8, flag: usize, step: usize, bit: bool) {
let mask = u16::from(bit).wrapping_neg();
Self::adapt_counter(&mut self.c0[context], 2, mask);
Self::adapt_counter(&mut self.c1[usize::from(first)][context], 4, mask);
let row = &mut self.c2[2 * context + flag];
let (head, tail) = row.split_at_mut(step + 1);
Self::adapt_counter(&mut head[step], 6, mask);
Self::adapt_counter(&mut tail[0], 6, mask);
}
#[inline]
const fn adapt_counter(slot: &mut u16, rate: u32, mask: u16) {
let value = *slot;
let target = (u16::MAX >> rate) & mask;
*slot = value.wrapping_sub(value >> rate).wrapping_add(target);
}
}
#[inline]
const fn scaled_width(low: u32, high: u32, mix: u32) -> u32 {
let spread = high.wrapping_sub(low) as u64;
#[expect(clippy::cast_possible_truncation)]
let width = (spread.wrapping_mul(mix as u64) >> 18) as u32;
width
}
#[inline]
const fn interpolate(low: u16, high: u16, frac: u32) -> u32 {
let low = low as i32;
let high = high as i32;
#[expect(clippy::cast_possible_wrap)]
let frac = frac as i32;
let point = low + (((high - low) * frac) >> 12);
#[expect(clippy::cast_sign_loss)]
let point = point as u32;
point
}
#[inline]
const fn interpolate_weighted(low: u16, high: u16, frac: u32) -> u32 {
(low as u32 * (4096 - frac) + high as u32 * frac) >> 12
}
#[inline]
const fn shared_bytes(low: u32, high: u32) -> u32 {
(low ^ high).leading_zeros() / 8
}
#[inline]
const fn shift_range(low: u32, high: u32, shift: u32) -> (u32, u32) {
let wide_low = (low as u64) << shift;
let wide_high = ((high as u64) << shift) | ((1_u64 << shift) - 1);
#[expect(clippy::cast_possible_truncation)]
(wide_low as u32, wide_high as u32)
}
#[inline]
fn flush_full(output: &mut Vec<u8>, out_buf: &[u8; OUT_BUF_LEN], used: &mut usize) {
if *used > OUT_BUF_LEN - BYTE_OUT_MAX {
output.extend_from_slice(&out_buf[..*used]);
*used = 0;
}
}
#[inline]
fn flush_shared_bytes(
low: &mut u32,
high: &mut u32,
out_buf: &mut [u8; OUT_BUF_LEN],
used: &mut usize,
) {
if (*low ^ *high) < (1 << 24) {
let count = shared_bytes(*low, *high);
out_buf[*used..*used + 4].copy_from_slice(&low.to_be_bytes());
*used += count as usize;
(*low, *high) = shift_range(*low, *high, 8 * count);
}
}
#[inline]
fn renormalize_after_bit(
input: &[u8],
filler: usize,
cursor: &mut BitCursor,
code: &mut u32,
pos: &mut usize,
) -> Result<()> {
if (cursor.low ^ cursor.high) < (1 << 24) {
if let Some(window) = input.get(*pos..).and_then(<[u8]>::first_chunk::<4>) {
let count = shared_bytes(cursor.low, cursor.high);
let shift = 8 * count;
let next = u32::from_be_bytes(*window);
(cursor.low, cursor.high) = shift_range(cursor.low, cursor.high, shift);
#[expect(clippy::cast_possible_truncation)]
let merged = (u64::from(*code) << shift | u64::from(next) >> (32 - shift)) as u32;
*code = merged;
*pos += count as usize;
} else {
(cursor.low, cursor.high, *code, *pos) =
renormalize_tail(cursor.low, cursor.high, *code, *pos, input, filler)?;
}
}
Ok(())
}
#[cold]
fn renormalize_tail(
mut low: u32,
mut high: u32,
mut code: u32,
mut pos: usize,
input: &[u8],
filler: usize,
) -> Result<(u32, u32, u32, usize)> {
while (low ^ high) < (1 << 24) {
low <<= 8;
high = (high << 8) + 255;
code = (code << 8) | u32::from(next_byte(input, pos, filler)?);
pos += 1;
}
Ok((low, high, code, pos))
}
#[cfg(test)]
mod tests {
use super::CmCoder;
use super::interpolate;
use super::interpolate_weighted;
use crate::error::Error;
macro_rules! vector {
($name:literal) => {
(
$name,
include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/vectors/cm/",
$name,
".bin"
)) as &[u8],
include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/vectors/cm/",
$name,
".cm"
)) as &[u8],
)
};
}
const VECTORS: &[(&str, &[u8], &[u8])] = &[
vector!("empty"),
vector!("zero1"),
vector!("ff1"),
vector!("hello"),
vector!("inc16"),
vector!("all256"),
vector!("zeros64"),
vector!("zeros65"),
vector!("zeros300"),
vector!("rand63"),
vector!("rand64"),
vector!("rand65"),
vector!("rand1k"),
vector!("text2k"),
vector!("runs"),
vector!("abalt"),
];
fn encode_all(input: &[u8]) -> Vec<u8> {
let mut coder = CmCoder::new();
let mut out = Vec::new();
coder.encode(input, &mut out);
out
}
fn decode_all(coded: &[u8], len: usize) -> crate::error::Result<Vec<u8>> {
let mut state = CmCoder::new();
let mut out = vec![0; len];
state.decode(coded, 0, &mut out).map(|()| out)
}
fn pseudo_random(seed: u32, len: usize) -> Vec<u8> {
let mut state = seed;
(0..len)
.map(|_| {
state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
u8::try_from((state >> 16) & 0xFF).expect("masked byte fits")
})
.collect()
}
#[test]
fn encode_matches_oracle_byte_for_byte() {
for (name, raw, coded) in VECTORS {
assert_eq!(encode_all(raw), coded.to_vec(), "encode differs for {name}");
}
}
#[test]
fn decode_restores_oracle_originals() {
for (name, raw, coded) in VECTORS {
let got = decode_all(coded, raw.len())
.unwrap_or_else(|_| panic!("oracle stream decodes for {name}"));
assert_eq!(got, raw.to_vec(), "decode differs for {name}");
}
}
#[test]
fn roundtrip_on_synthetic_inputs() {
let mut inputs: Vec<Vec<u8>> = vec![
vec![],
vec![0],
vec![255],
vec![0, 255],
vec![0; 1000],
vec![255; 1000],
vec![b'A'; 300],
b"AB".repeat(200),
(0..=255u8).cycle().take(1024).collect(),
];
for len in [1usize, 2, 3, 7, 8, 63, 64, 65, 100, 1000] {
inputs.push(pseudo_random(0x00C0_FFEE, len));
}
for raw in &inputs {
let coded = encode_all(raw);
let got = decode_all(&coded, raw.len()).expect("roundtrip decodes");
assert_eq!(&got, raw, "roundtrip differs for len {}", raw.len());
}
}
#[test]
fn every_truncation_fails_with_truncated_data() {
for (name, raw, coded) in VECTORS {
if raw.is_empty() {
continue;
}
for end in 0..coded.len() {
let mut state = CmCoder::new();
let mut out = vec![0; raw.len()];
let err = state
.decode(&coded[..end], 0, &mut out)
.expect_err(&format!("short stream of vector {name} must fail"));
assert!(
matches!(err, Error::TruncatedData),
"wrong error for short stream of {name}"
);
}
}
}
#[test]
fn empty_output_needs_no_input() {
let mut state = CmCoder::new();
state.decode(&[], 0, &mut []).expect("empty decode is free");
let (_, _, coded) = VECTORS[4];
state
.decode(&coded[..3.min(coded.len())], 0, &mut [])
.expect("empty output ignores input");
}
#[test]
fn trailing_bytes_are_ignored() {
for (name, raw, coded) in VECTORS {
let mut padded = coded.to_vec();
padded.extend_from_slice(&[0xAA, 0x55, 0x00, 0xFF]);
let got = decode_all(&padded, raw.len())
.unwrap_or_else(|_| panic!("padded stream decodes for {name}"));
assert_eq!(got, raw.to_vec(), "padded decode differs for {name}");
}
}
#[test]
fn interpolation_forms_are_equal() {
const EDGES: [u16; 7] = [0, 1, 2, 4095, 4096, 65534, 65535];
for low in EDGES {
for high in EDGES {
for frac in 0..4096 {
assert_eq!(
interpolate(low, high, frac),
interpolate_weighted(low, high, frac),
"low {low} high {high} frac {frac}"
);
}
}
}
}
#[test]
fn state_resets_between_calls() {
let first = encode_all(b"hello world, hello world");
let mut coder = CmCoder::new();
let mut again = Vec::new();
coder.encode(b"something else entirely", &mut again);
let mut second = Vec::new();
coder.encode(b"hello world, hello world", &mut second);
assert_eq!(second, first, "coder state leaked between calls");
let got = decode_all(&second, 24).expect("decodes");
assert_eq!(got, b"hello world, hello world");
}
}