use oxideav_core::bits::BitReader;
use crate::{Error, Result};
pub const RVLC_ESC_FLAG: i8 = 7;
pub const RVLC_CB_NUM_ENTRIES: usize = 15;
pub const RVLC_CB_MAX_LEN: u32 = 9;
const RVLC_CB: [(i8, u8, u32); RVLC_CB_NUM_ENTRIES] = [
(-7, 7, 65), (-6, 9, 257), (-5, 8, 129), (-4, 6, 33), (-3, 5, 17), (-2, 4, 9), (-1, 3, 5), (0, 1, 0), (1, 3, 7), (2, 5, 27), (3, 6, 51), (4, 7, 107), (5, 8, 195), (6, 9, 427), (7, 7, 99), ];
const RVLC_FORBIDDEN: [(u8, u32); 8] = [
(6, 50), (7, 96), (9, 256), (8, 194), (7, 98), (6, 52), (9, 426), (8, 212), ];
pub fn rvlc_encode(value: i8) -> Result<(u8, u32)> {
for &(v, len, cw) in &RVLC_CB {
if v == value {
return Ok((len, cw));
}
}
Err(Error::RvlcEncodeInvalid)
}
pub fn rvlc_decode(reader: &mut BitReader<'_>) -> Result<i8> {
let mut acc: u32 = 0;
for len in 1..=RVLC_CB_MAX_LEN {
let bit = reader.read_u32(1).map_err(|_| Error::UnexpectedEnd)?;
acc = (acc << 1) | bit;
for &(value, entry_len, entry_cw) in &RVLC_CB {
if u32::from(entry_len) == len && entry_cw == acc {
return Ok(value);
}
}
for &(f_len, f_cw) in &RVLC_FORBIDDEN {
if u32::from(f_len) == len && f_cw == acc {
return Err(Error::RvlcForbiddenCodeword);
}
}
}
Err(Error::RvlcForbiddenCodeword)
}
pub const RVLC_ESC_NUM_ENTRIES: usize = 54;
pub const RVLC_ESC_MAX_LEN: u32 = 20;
const RVLC_ESC_CB: [(u8, u32); RVLC_ESC_NUM_ENTRIES] = [
(2, 2), (2, 0), (3, 6), (3, 2), (4, 14), (5, 31), (5, 15), (5, 13), (6, 61), (6, 29), (6, 25), (6, 24), (7, 120), (7, 56), (8, 242), (8, 114), (9, 486), (9, 230), (10, 974), (10, 463), (11, 1950), (11, 1951), (11, 925), (12, 1848), (14, 7399), (13, 3698), (15, 14797), (20, 473482), (20, 473483), (20, 473484), (20, 473485), (20, 473486), (20, 473487), (20, 473488), (20, 473489), (20, 473490), (20, 473491), (20, 473492), (20, 473493), (20, 473494), (20, 473495), (20, 473496), (20, 473497), (20, 473498), (20, 473499), (20, 473500), (20, 473501), (20, 473502), (20, 473503), (19, 236736), (19, 236737), (19, 236738), (19, 236739), (19, 236740), ];
pub fn rvlc_esc_encode(magnitude: u8) -> Result<(u8, u32)> {
RVLC_ESC_CB
.get(magnitude as usize)
.copied()
.ok_or(Error::RvlcEncodeInvalid)
}
pub fn rvlc_esc_decode(reader: &mut BitReader<'_>) -> Result<u8> {
let mut acc: u32 = 0;
for len in 1..=RVLC_ESC_MAX_LEN {
let bit = reader.read_u32(1).map_err(|_| Error::UnexpectedEnd)?;
acc = (acc << 1) | bit;
for (idx, &(entry_len, entry_cw)) in RVLC_ESC_CB.iter().enumerate() {
if u32::from(entry_len) == len && entry_cw == acc {
return Ok(idx as u8);
}
}
}
Err(Error::RvlcEscInvalid)
}
#[cfg(test)]
mod tests {
use super::*;
use oxideav_core::bits::BitWriter;
fn encode_rvlc(value: i8) -> Vec<u8> {
let (len, cw) = rvlc_encode(value).unwrap();
let mut w = BitWriter::new();
w.write_u32(cw, u32::from(len));
w.align_to_byte_zero();
w.finish()
}
#[test]
fn rvlc_codebook_roundtrips_every_value() {
for value in -7..=7 {
let bytes = encode_rvlc(value);
let mut r = BitReader::new(&bytes);
assert_eq!(rvlc_decode(&mut r).unwrap(), value, "value {value}");
}
}
#[test]
fn rvlc_codewords_are_palindromes() {
for &(value, len, cw) in &RVLC_CB {
let mut forward = 0u32;
for i in 0..len {
let bit = (cw >> i) & 1;
forward = (forward << 1) | bit;
}
assert_eq!(forward, cw, "value {value} codeword not a palindrome");
}
}
#[test]
fn rvlc_codebook_is_prefix_free() {
for &(_, li, ci) in &RVLC_CB {
for &(_, lj, cj) in &RVLC_CB {
if (li, ci) == (lj, cj) {
continue;
}
if li <= lj {
let shifted = cj >> (lj - li);
assert_ne!(shifted, ci, "({li},{ci}) is a prefix of ({lj},{cj})");
}
}
}
}
#[test]
fn forbidden_codewords_are_detected() {
for &(len, cw) in &RVLC_FORBIDDEN {
let mut w = BitWriter::new();
w.write_u32(cw, u32::from(len));
w.align_to_byte_zero();
let bytes = w.finish();
let mut r = BitReader::new(&bytes);
assert!(
matches!(rvlc_decode(&mut r), Err(Error::RvlcForbiddenCodeword)),
"forbidden codeword ({len},{cw}) not detected"
);
}
}
#[test]
fn forbidden_codewords_disjoint_from_valid() {
for &(fl, fc) in &RVLC_FORBIDDEN {
for &(_, vl, vc) in &RVLC_CB {
assert!(
!(fl == vl && fc == vc),
"forbidden ({fl},{fc}) collides with a valid codeword"
);
}
}
}
#[test]
fn rvlc_esc_codebook_roundtrips_every_magnitude() {
for magnitude in 0u8..RVLC_ESC_NUM_ENTRIES as u8 {
let (len, cw) = rvlc_esc_encode(magnitude).unwrap();
let mut w = BitWriter::new();
w.write_u32(cw, u32::from(len));
w.align_to_byte_zero();
let bytes = w.finish();
let mut r = BitReader::new(&bytes);
assert_eq!(
rvlc_esc_decode(&mut r).unwrap(),
magnitude,
"mag {magnitude}"
);
}
}
#[test]
fn rvlc_esc_codebook_is_prefix_free() {
for &(li, ci) in &RVLC_ESC_CB {
for &(lj, cj) in &RVLC_ESC_CB {
if (li, ci) == (lj, cj) {
continue;
}
if li <= lj {
let shifted = cj >> (lj - li);
assert_ne!(shifted, ci, "esc ({li},{ci}) is a prefix of ({lj},{cj})");
}
}
}
}
#[test]
fn rvlc_encode_rejects_out_of_range() {
assert!(matches!(rvlc_encode(8), Err(Error::RvlcEncodeInvalid)));
assert!(matches!(rvlc_encode(-8), Err(Error::RvlcEncodeInvalid)));
assert!(matches!(
rvlc_esc_encode(RVLC_ESC_NUM_ENTRIES as u8),
Err(Error::RvlcEncodeInvalid)
));
}
#[test]
fn esc_flag_is_seven() {
assert_eq!(RVLC_ESC_FLAG, 7);
assert!(rvlc_encode(RVLC_ESC_FLAG).is_ok());
assert!(rvlc_encode(-RVLC_ESC_FLAG).is_ok());
}
}