pub const RS_N: usize = 255;
pub const RS_DATA_PRIM_POLY: u16 = 0x11D;
pub const RS_DATA_K: usize = 251;
pub const RS_DATA_PARITY: usize = RS_N - RS_DATA_K;
pub const RS_SYSTEM_PRIM_POLY: u16 = 0x169;
pub const RS_SYSTEM_K: usize = 239;
pub const RS_SYSTEM_PARITY: usize = RS_N - RS_SYSTEM_K;
const RS_FCR: usize = 1;
struct GfTables {
exp: [u8; 510],
log: [u8; 256],
}
impl GfTables {
fn new(prim_poly: u16) -> Self {
let mut exp = [0u8; 510];
let mut log = [0u8; 256];
let mut x: u16 = 1; for i in 0..255usize {
exp[i] = x as u8;
log[x as usize] = i as u8;
x <<= 1; if x & 0x100 != 0 {
x ^= prim_poly; }
}
for i in 255..510usize {
exp[i] = exp[i - 255];
}
Self { exp, log }
}
#[inline]
fn mul(&self, a: u8, b: u8) -> u8 {
if a == 0 || b == 0 {
return 0;
}
self.exp[self.log[a as usize] as usize + self.log[b as usize] as usize]
}
}
fn rs_generator_poly(nsym: usize, gf: &GfTables) -> Vec<u8> {
let mut gen = vec![0u8; nsym + 1];
gen[0] = 1;
for i in 0..nsym {
let alpha_i = gf.exp[RS_FCR + i];
for j in (1..=i + 1).rev() {
gen[j] = gen[j] ^ gf.mul(gen[j - 1], alpha_i);
}
}
gen
}
fn rs_encode_block(data: &[u8], nsym: usize, gen: &[u8], gf: &GfTables) -> [u8; RS_N] {
let k = RS_N - nsym;
debug_assert!(data.len() <= k, "data block too large for RS({}, {})", RS_N, k);
debug_assert_eq!(gen.len(), nsym + 1);
let mut cw = [0u8; RS_N];
cw[..data.len()].copy_from_slice(data);
for i in 0..k {
let feedback = cw[i] ^ cw[k]; if feedback != 0 {
for j in 0..nsym - 1 {
cw[k + j] = cw[k + j + 1] ^ gf.mul(feedback, gen[j + 1]);
}
cw[k + nsym - 1] = gf.mul(feedback, gen[nsym]);
} else {
for j in 0..nsym - 1 {
cw[k + j] = cw[k + j + 1];
}
cw[k + nsym - 1] = 0;
}
}
cw[..data.len()].copy_from_slice(data);
cw
}
pub fn reed_solomon_encode(
data: &[u8],
buffer: &mut [u8],
factor: usize,
block_size: usize,
prim_poly: u16,
) {
let nsym = RS_N - block_size;
assert_eq!(
buffer.len(),
factor * RS_N,
"buffer must be factor({}) × 255 = {} bytes, got {}",
factor,
factor * RS_N,
buffer.len()
);
assert!(
data.len() <= factor * block_size,
"data length {} exceeds factor({}) × block_size({}) = {}",
data.len(),
factor,
block_size,
factor * block_size
);
let gf = GfTables::new(prim_poly);
let gen = rs_generator_poly(nsym, &gf);
buffer.fill(0);
for s in 0..factor {
let src_start = s * block_size;
let src_end = (src_start + block_size).min(data.len());
let chunk = if src_start < data.len() {
&data[src_start..src_end]
} else {
&[] as &[u8]
};
let codeword = rs_encode_block(chunk, nsym, &gen, &gf);
for (i, &b) in codeword.iter().enumerate() {
buffer[i * factor + s] = b;
}
}
}
pub fn reed_solomon_decode(encoded: &[u8], buffer: &mut [u8], factor: usize, block_size: usize) {
let mut index = 0;
let mut n = 0;
let mut length = buffer.len();
for _i in 0..factor {
let mut cindex = n;
if n < encoded.len() {
let size = length.min(block_size);
length -= size;
let offset = index + size;
while index < offset {
buffer[index] = encoded[cindex];
index += 1;
cindex += factor;
}
}
n += 1;
}
}
pub fn reed_solomon_encode_compact(data: &[u8], block_size: usize) -> Vec<u8> {
let nsym = 255 - block_size;
let gf = GfTables::new(RS_DATA_PRIM_POLY);
let gen = rs_generator_poly(nsym, &gf);
let n_blocks = if data.is_empty() { 0 } else { (data.len() + block_size - 1) / block_size };
let mut result = Vec::with_capacity(data.len() + n_blocks * nsym);
result.extend_from_slice(data);
for i in 0..n_blocks {
let start = i * block_size;
let end = std::cmp::min(start + block_size, data.len());
let chunk = &data[start..end];
let mut padded = vec![0u8; block_size];
padded[..chunk.len()].copy_from_slice(chunk);
let codeword = rs_encode_block(&padded, nsym, &gen, &gf);
let k = RS_N - nsym;
result.extend_from_slice(&codeword[k..]);
}
result
}
pub fn reed_solomon_decode_compact(encoded: &[u8], data_size: usize) -> Vec<u8> {
let mut data = vec![0u8; data_size];
let copy_len = std::cmp::min(data_size, encoded.len());
data[..copy_len].copy_from_slice(&encoded[..copy_len]);
data
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reed_solomon_basic_deinterleave() {
let encoded = [1, 4, 2, 5, 3, 6, 99, 99];
let mut buffer = [0u8; 6];
reed_solomon_decode(&encoded, &mut buffer, 2, 3);
assert_eq!(buffer, [1, 2, 3, 4, 5, 6]);
}
#[test]
fn test_reed_solomon_factor3() {
let encoded = [10, 20, 30, 11, 21, 31, 0, 0, 0];
let mut buffer = [0u8; 6];
reed_solomon_decode(&encoded, &mut buffer, 3, 2);
assert_eq!(buffer, [10, 11, 20, 21, 30, 31]);
}
#[test]
fn test_reed_solomon_real_world_params() {
let encoded = vec![0xAA; 1024];
let mut buffer = vec![0u8; 3 * 239];
reed_solomon_decode(&encoded, &mut buffer, 3, 239);
assert!(buffer.iter().all(|&b| b == 0xAA));
}
#[test]
fn test_gf_tables_0x11d() {
let gf = GfTables::new(RS_DATA_PRIM_POLY);
assert_eq!(gf.exp[0], 1);
assert_eq!(gf.exp[1], 2);
assert_eq!(gf.exp[8], 0x1D);
assert_eq!(gf.exp[255], gf.exp[0]);
assert_eq!(gf.log[1], 0);
assert_eq!(gf.log[2], 1);
}
#[test]
fn test_gf_tables_0x169() {
let gf = GfTables::new(RS_SYSTEM_PRIM_POLY);
assert_eq!(gf.exp[0], 1);
assert_eq!(gf.exp[1], 2);
assert_eq!(gf.exp[8], 0x69);
assert_eq!(gf.exp[255], gf.exp[0]);
}
#[test]
fn test_gf_mul_identity() {
let gf = GfTables::new(RS_DATA_PRIM_POLY);
for a in 0..=255u8 {
assert_eq!(gf.mul(a, 1), a);
}
for a in 0..=255u8 {
assert_eq!(gf.mul(a, 0), 0);
}
}
#[test]
fn test_gf_mul_commutative() {
let gf = GfTables::new(RS_DATA_PRIM_POLY);
for a in 1..=255u8 {
for b in 1..=255u8 {
assert_eq!(gf.mul(a, b), gf.mul(b, a), "a={}, b={}", a, b);
}
}
}
#[test]
fn test_generator_poly_degree() {
let gf = GfTables::new(RS_DATA_PRIM_POLY);
let gen = rs_generator_poly(RS_DATA_PARITY, &gf); assert_eq!(gen.len(), RS_DATA_PARITY + 1); assert_eq!(gen[0], 1); }
#[test]
fn test_generator_poly_roots() {
let gf = GfTables::new(RS_DATA_PRIM_POLY);
let nsym = RS_DATA_PARITY; let gen = rs_generator_poly(nsym, &gf);
for i in 0..nsym {
let alpha_i = gf.exp[RS_FCR + i]; let mut val: u8 = 0;
for &coeff in &gen {
val = gf.mul(val, alpha_i) ^ coeff;
}
assert_eq!(val, 0, "α^{} should be a root of g(x)", RS_FCR + i);
}
}
#[test]
fn test_generator_poly_roots_system() {
let gf = GfTables::new(RS_SYSTEM_PRIM_POLY);
let nsym = RS_SYSTEM_PARITY; let gen = rs_generator_poly(nsym, &gf);
assert_eq!(gen.len(), nsym + 1);
assert_eq!(gen[0], 1);
for i in 0..nsym {
let alpha_i = gf.exp[RS_FCR + i];
let mut val: u8 = 0;
for &coeff in &gen {
val = gf.mul(val, alpha_i) ^ coeff;
}
assert_eq!(val, 0, "α^{} should be a root of g(x)", RS_FCR + i);
}
}
#[test]
fn test_encode_block_codeword_is_valid() {
let gf = GfTables::new(RS_DATA_PRIM_POLY);
let nsym = RS_DATA_PARITY;
let gen = rs_generator_poly(nsym, &gf);
let data: Vec<u8> = (1..=RS_DATA_K as u8).collect();
let cw = rs_encode_block(&data, nsym, &gen, &gf);
for r in 0..nsym {
let alpha_r = gf.exp[RS_FCR + r];
let mut syndrome: u8 = 0;
for &byte in cw.iter() {
syndrome = gf.mul(syndrome, alpha_r) ^ byte;
}
assert_eq!(syndrome, 0, "syndrome at root α^{} must be 0", RS_FCR + r);
}
}
#[test]
fn test_encode_block_data_preserved() {
let gf = GfTables::new(RS_SYSTEM_PRIM_POLY);
let nsym = RS_SYSTEM_PARITY;
let gen = rs_generator_poly(nsym, &gf);
let data = vec![0x42u8; RS_SYSTEM_K];
let cw = rs_encode_block(&data, nsym, &gen, &gf);
assert_eq!(&cw[..RS_SYSTEM_K], &data[..]);
}
#[test]
fn test_encode_block_zeros() {
let gf = GfTables::new(RS_DATA_PRIM_POLY);
let nsym = RS_DATA_PARITY;
let gen = rs_generator_poly(nsym, &gf);
let data = vec![0u8; RS_DATA_K];
let cw = rs_encode_block(&data, nsym, &gen, &gf);
assert!(cw.iter().all(|&b| b == 0));
}
#[test]
fn test_roundtrip_factor1_data_pages() {
let data = vec![0xAB; RS_DATA_K];
let mut encoded = vec![0u8; RS_N]; reed_solomon_encode(&data, &mut encoded, 1, RS_DATA_K, RS_DATA_PRIM_POLY);
let mut decoded = vec![0u8; RS_DATA_K];
reed_solomon_decode(&encoded, &mut decoded, 1, RS_DATA_K);
assert_eq!(decoded, data);
}
#[test]
fn test_roundtrip_factor3_system_pages() {
let data: Vec<u8> = (0..3 * RS_SYSTEM_K)
.map(|i| (i & 0xFF) as u8)
.collect();
let mut encoded = vec![0u8; 3 * RS_N]; reed_solomon_encode(&data, &mut encoded, 3, RS_SYSTEM_K, RS_SYSTEM_PRIM_POLY);
let mut decoded = vec![0u8; 3 * RS_SYSTEM_K];
reed_solomon_decode(&encoded, &mut decoded, 3, RS_SYSTEM_K);
assert_eq!(decoded, data);
}
#[test]
fn test_roundtrip_factor2_data_pages() {
let data: Vec<u8> = (0..2 * RS_DATA_K)
.map(|i| ((i * 7 + 13) & 0xFF) as u8)
.collect();
let mut encoded = vec![0u8; 2 * RS_N];
reed_solomon_encode(&data, &mut encoded, 2, RS_DATA_K, RS_DATA_PRIM_POLY);
let mut decoded = vec![0u8; 2 * RS_DATA_K];
reed_solomon_decode(&encoded, &mut decoded, 2, RS_DATA_K);
assert_eq!(decoded, data);
}
#[test]
fn test_roundtrip_short_last_block() {
let data: Vec<u8> = (0..500).map(|i| (i & 0xFF) as u8).collect();
let factor = (data.len() + RS_SYSTEM_K - 1) / RS_SYSTEM_K; assert_eq!(factor, 3);
let mut encoded = vec![0u8; factor * RS_N];
reed_solomon_encode(&data, &mut encoded, factor, RS_SYSTEM_K, RS_SYSTEM_PRIM_POLY);
let mut decoded = vec![0u8; factor * RS_SYSTEM_K];
reed_solomon_decode(&encoded, &mut decoded, factor, RS_SYSTEM_K);
assert_eq!(&decoded[..data.len()], &data[..]);
assert!(decoded[data.len()..].iter().all(|&b| b == 0));
}
#[test]
fn test_roundtrip_file_header_realistic() {
let data: Vec<u8> = (0u16..717).map(|i| (i & 0xFF) as u8).collect();
let mut page = vec![0u8; 0x400];
let encoded_len = 3 * RS_N; reed_solomon_encode(&data, &mut page[..encoded_len], 3, RS_SYSTEM_K, RS_SYSTEM_PRIM_POLY);
let mut decoded = vec![0u8; 3 * RS_SYSTEM_K]; reed_solomon_decode(&page, &mut decoded, 3, RS_SYSTEM_K);
assert_eq!(decoded, data);
}
#[test]
fn test_interleave_data_byte_positions() {
let stream0: Vec<u8> = (1..=RS_DATA_K as u8).collect();
let stream1: Vec<u8> = (0..RS_DATA_K).map(|i| (200 + i) as u8).collect();
let mut data = Vec::new();
data.extend_from_slice(&stream0);
data.extend_from_slice(&stream1);
let mut encoded = vec![0u8; 2 * RS_N];
reed_solomon_encode(&data, &mut encoded, 2, RS_DATA_K, RS_DATA_PRIM_POLY);
assert_eq!(encoded[0], stream0[0]);
assert_eq!(encoded[1], stream1[0]);
assert_eq!(encoded[2], stream0[1]);
assert_eq!(encoded[3], stream1[1]);
}
#[test]
fn test_encode_block_system_poly_syndromes() {
let gf = GfTables::new(RS_SYSTEM_PRIM_POLY);
let nsym = RS_SYSTEM_PARITY;
let gen = rs_generator_poly(nsym, &gf);
let data: Vec<u8> = (0..RS_SYSTEM_K).map(|i| ((i * 37 + 5) & 0xFF) as u8).collect();
let cw = rs_encode_block(&data, nsym, &gen, &gf);
for r in 0..nsym {
let alpha_r = gf.exp[RS_FCR + r];
let mut syndrome: u8 = 0;
for &byte in cw.iter() {
syndrome = gf.mul(syndrome, alpha_r) ^ byte;
}
assert_eq!(syndrome, 0, "syndrome at root α^{} must be 0", RS_FCR + r);
}
}
#[test]
fn test_gf_exp_table_matches_libredwg_f256_power() {
let libredwg_power: [u8; 32] = [
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
0x69, 0xd2, 0xcd, 0xf3, 0x8f, 0x77, 0xee, 0xb5,
0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0xe9,
0xbb, 0x1f, 0x3e, 0x7c, 0xf8, 0x99, 0x5b, 0xb6,
];
let gf = GfTables::new(RS_SYSTEM_PRIM_POLY);
for i in 0..32 {
assert_eq!(
gf.exp[i], libredwg_power[i],
"exp[{}]: ours={:#04x} vs LibreDWG={:#04x}",
i, gf.exp[i], libredwg_power[i]
);
}
}
#[test]
fn test_generator_poly_matches_libredwg_rsgen() {
let libredwg_rsgen: [u8; 17] = [
0x6a, 0xe3, 0x63, 0x1f, 0xa1, 0x24, 0x9e, 0x44, 0x13,
0x1e, 0x2f, 0xfc, 0xfd, 0xce, 0xa9, 0xdb, 0x01,
];
let gf = GfTables::new(RS_SYSTEM_PRIM_POLY);
let gen = rs_generator_poly(RS_SYSTEM_PARITY, &gf);
assert_eq!(gen.len(), 17);
for i in 0..17 {
assert_eq!(
gen[i], libredwg_rsgen[16 - i],
"gen[{}] (x^{} coeff): ours={:#04x} vs LibreDWG rsgen[{}]={:#04x}",
i, 16 - i, gen[i], 16 - i, libredwg_rsgen[16 - i]
);
}
}
#[test]
fn test_encode_parity_matches_libredwg_convention() {
let gf = GfTables::new(RS_SYSTEM_PRIM_POLY);
let nsym = RS_SYSTEM_PARITY;
let gen = rs_generator_poly(nsym, &gf);
let data: Vec<u8> = (1..=RS_SYSTEM_K as u8).collect();
let cw = rs_encode_block(&data, nsym, &gen, &gf);
for j in 0..16usize {
let root = gf.exp[j + 1]; let mut val: u8 = 0;
for &byte in cw.iter() {
val = gf.mul(val, root) ^ byte;
}
assert_eq!(val, 0, "LibreDWG-compatible syndrome at α^{} must be 0", j + 1);
}
}
}