use crate::packing::CodedBlock;
use crate::params::{CodeParams, ParamError};
use crate::trellis::{next_state, output_symbol};
use crate::{K, M, MAX_SUPPORTED_INFO_BITS, N};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EncodeError {
NbitsExceedsData {
nbits: usize,
available: usize,
},
PayloadTooLarge {
cap: usize,
got: usize,
},
AllocationFailed,
}
impl core::fmt::Display for EncodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for EncodeError {}
pub struct ViterbiEncoder {
params: CodeParams,
}
impl ViterbiEncoder {
pub fn new(params: CodeParams) -> Result<Self, ParamError> {
params.validate()?; if params.k != K {
return Err(ParamError::KUnsupported { k: params.k });
}
if params.generators.len() != N {
return Err(ParamError::RateUnsupported {
n: params.generators.len(),
});
}
Ok(Self { params })
}
pub fn encode(&self, data: &[u8]) -> Result<CodedBlock, EncodeError> {
self.encode_bits(data, data.len().saturating_mul(8))
}
pub fn encode_bits(&self, data: &[u8], nbits: usize) -> Result<CodedBlock, EncodeError> {
let available = data.len().saturating_mul(8); if nbits > available {
return Err(EncodeError::NbitsExceedsData { nbits, available });
}
if nbits > MAX_SUPPORTED_INFO_BITS {
return Err(EncodeError::PayloadTooLarge {
cap: MAX_SUPPORTED_INFO_BITS,
got: nbits,
});
}
let ncoded = (nbits + M as usize) * N;
let mut bytes: Vec<u8> = Vec::new();
bytes
.try_reserve_exact(ncoded.div_ceil(8))
.map_err(|_| EncodeError::AllocationFailed)?;
bytes.resize(ncoded.div_ceil(8), 0);
let mut state = 0usize;
let mut oi = 0usize; let emit = |bytes: &mut [u8], oi: &mut usize, sym: u32| {
for j in 0..N {
if (sym >> (N - 1 - j)) & 1 == 1 {
crate::packing::set_bit(bytes, *oi);
}
*oi += 1;
}
};
for i in 0..nbits {
let bit = crate::packing::get_bit(data, i); emit(&mut bytes, &mut oi, output_symbol(state, bit, &self.params));
state = next_state(state, bit, self.params.k);
}
for _ in 0..M {
emit(&mut bytes, &mut oi, output_symbol(state, 0, &self.params));
state = next_state(state, 0, self.params.k);
}
Ok(CodedBlock {
bytes,
nbits: ncoded,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::params::CodeParams;
#[test]
fn impulse_response_matches_traced_ccsds() {
let enc = ViterbiEncoder::new(CodeParams::ccsds_r1_2()).unwrap();
let cb = enc.encode_bits(&[0x80], 1).unwrap();
assert_eq!(cb.nbits, 14);
assert_eq!(cb.bytes, vec![0xBA, 0x48]);
}
#[test]
fn encode_appends_zero_tail_length() {
let enc = ViterbiEncoder::new(CodeParams::ccsds_r1_2()).unwrap();
let cb = enc.encode(&[0xFF]).unwrap(); assert_eq!(cb.nbits, (8 + 6) * 2);
}
#[test]
fn encode_bits_rejects_overlong_nbits() {
let enc = ViterbiEncoder::new(CodeParams::ccsds_r1_2()).unwrap();
assert_eq!(
enc.encode_bits(&[0x00], 9),
Err(EncodeError::NbitsExceedsData {
nbits: 9,
available: 8
})
);
}
#[test]
fn encode_rejects_oversized_payload() {
let enc = ViterbiEncoder::new(CodeParams::ccsds_r1_2()).unwrap();
let big = vec![0u8; crate::MAX_SUPPORTED_INFO_BITS / 8 + 1]; assert!(matches!(
enc.encode(&big),
Err(EncodeError::PayloadTooLarge { .. })
));
}
}