#[cfg(not(feature = "std"))]
use alloc::{string::ToString, vec::Vec};
use crate::context::Context;
use crate::encoder::{fixed_bitmap_bytes, FixedEncoding};
use crate::error::{DecodeError, Result};
use crate::protocol::{
classify_compact_marker, ctx_version_compatible, CompactHeader, DecodedData, EncodedMessage,
EncodingType,
};
const FIXED_CTX_MAX_JUMP: u16 = 256;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FixedFrameInfo {
pub keyframe: bool,
pub sequence: u16,
pub context_version: u16,
pub gap_size: u8,
pub context_mismatch: bool,
}
#[derive(Debug, Clone)]
pub struct Decoder {
verify_checksum: bool,
last_sequence: Option<u16>,
last_fixed_sequence: Option<u16>,
last_fixed_ctx_version: Option<u16>,
}
impl Decoder {
pub fn new() -> Self {
Self {
verify_checksum: false,
last_sequence: None,
last_fixed_sequence: None,
last_fixed_ctx_version: None,
}
}
pub fn with_checksum_verification() -> Self {
Self {
verify_checksum: true,
last_sequence: None,
last_fixed_sequence: None,
last_fixed_ctx_version: None,
}
}
pub fn checksum_verification_enabled(&self) -> bool {
self.verify_checksum
}
pub fn decode(&mut self, message: &EncodedMessage, context: &Context) -> Result<DecodedData> {
if let Some(last_seq) = self.last_sequence {
let expected = last_seq.wrapping_add(1);
if message.header.sequence != expected {
}
}
self.last_sequence = Some(message.header.sequence);
let payload = &message.payload;
if payload.is_empty() {
return Err(DecodeError::BufferTooShort {
needed: 1,
available: 0,
}
.into());
}
let (source_id, offset) = self.decode_varint(payload)?;
if offset >= payload.len() {
return Err(DecodeError::BufferTooShort {
needed: offset + 1,
available: payload.len(),
}
.into());
}
let encoding_byte = payload[offset];
let encoding_type = EncodingType::from_u8(encoding_byte)
.ok_or(DecodeError::UnknownEncodingType(encoding_byte))?;
let value = self.decode_value(encoding_type, &payload[offset + 1..], source_id, context)?;
Ok(DecodedData::new(
source_id,
message.header.timestamp as u64,
value,
message.header.priority,
))
}
pub fn decode_bytes(&mut self, bytes: &[u8], context: &Context) -> Result<DecodedData> {
let message = if self.verify_checksum {
EncodedMessage::from_bytes_with_checksum(bytes)?
} else {
EncodedMessage::from_bytes(bytes).ok_or(DecodeError::InvalidHeader)?
};
self.decode(&message, context)
}
fn decode_varint(&self, buffer: &[u8]) -> Result<(u32, usize)> {
let mut result: u32 = 0;
let mut shift = 0;
let mut offset = 0;
loop {
if offset >= buffer.len() {
return Err(DecodeError::BufferTooShort {
needed: offset + 1,
available: buffer.len(),
}
.into());
}
let byte = buffer[offset];
result |= ((byte & 0x7F) as u32) << shift;
offset += 1;
if byte & 0x80 == 0 {
break;
}
shift += 7;
if shift >= 32 {
return Err(DecodeError::MalformedMessage {
offset,
reason: "Varint too long".to_string(),
}
.into());
}
}
Ok((result, offset))
}
fn decode_value(
&self,
encoding_type: EncodingType,
data: &[u8],
source_id: u32,
context: &Context,
) -> Result<f64> {
match encoding_type {
EncodingType::Raw64 => self.decode_raw64(data),
EncodingType::Raw32 => self.decode_raw32(data),
EncodingType::Delta8 => self.decode_delta8(data, source_id, context),
EncodingType::Delta16 => self.decode_delta16(data, source_id, context),
EncodingType::Delta32 => self.decode_delta32(data, source_id, context),
EncodingType::Repeated => self.decode_repeated(source_id, context),
EncodingType::Interpolated => self.decode_interpolated(source_id, context),
EncodingType::Pattern => self.decode_pattern(data, context),
EncodingType::PatternDelta => self.decode_pattern_delta(data, context),
EncodingType::Multi => Err(DecodeError::MalformedMessage {
offset: 0,
reason: "Multi encoding should use decode_multi".to_string(),
}
.into()),
}
}
fn decode_raw64(&self, data: &[u8]) -> Result<f64> {
if data.len() < 8 {
return Err(DecodeError::BufferTooShort {
needed: 8,
available: data.len(),
}
.into());
}
let bytes: [u8; 8] = data[..8].try_into().unwrap();
Ok(f64::from_be_bytes(bytes))
}
fn decode_raw32(&self, data: &[u8]) -> Result<f64> {
if data.len() < 4 {
return Err(DecodeError::BufferTooShort {
needed: 4,
available: data.len(),
}
.into());
}
let bytes: [u8; 4] = data[..4].try_into().unwrap();
Ok(f32::from_be_bytes(bytes) as f64)
}
fn decode_delta8(&self, data: &[u8], source_id: u32, context: &Context) -> Result<f64> {
if data.is_empty() {
return Err(DecodeError::BufferTooShort {
needed: 1,
available: 0,
}
.into());
}
let prediction =
context
.predict(source_id)
.ok_or_else(|| DecodeError::MalformedMessage {
offset: 0,
reason: "No prediction available for delta decoding".to_string(),
})?;
let delta = data[0] as i8;
let scale = context.scale_factor() as f64;
let decoded = prediction.value + (delta as f64 / scale);
Ok(decoded)
}
fn decode_delta16(&self, data: &[u8], source_id: u32, context: &Context) -> Result<f64> {
if data.len() < 2 {
return Err(DecodeError::BufferTooShort {
needed: 2,
available: data.len(),
}
.into());
}
let prediction =
context
.predict(source_id)
.ok_or_else(|| DecodeError::MalformedMessage {
offset: 0,
reason: "No prediction available for delta decoding".to_string(),
})?;
let delta = i16::from_be_bytes([data[0], data[1]]);
let scale = context.scale_factor() as f64;
let decoded = prediction.value + (delta as f64 / scale);
Ok(decoded)
}
fn decode_delta32(&self, data: &[u8], source_id: u32, context: &Context) -> Result<f64> {
if data.len() < 4 {
return Err(DecodeError::BufferTooShort {
needed: 4,
available: data.len(),
}
.into());
}
let prediction =
context
.predict(source_id)
.ok_or_else(|| DecodeError::MalformedMessage {
offset: 0,
reason: "No prediction available for delta decoding".to_string(),
})?;
let delta = i32::from_be_bytes([data[0], data[1], data[2], data[3]]);
let scale = context.scale_factor() as f64;
let decoded = prediction.value + (delta as f64 / scale);
Ok(decoded)
}
fn decode_repeated(&self, source_id: u32, context: &Context) -> Result<f64> {
context.last_value(source_id).ok_or_else(|| {
DecodeError::MalformedMessage {
offset: 0,
reason: "No previous value for repeated decoding".to_string(),
}
.into()
})
}
fn decode_interpolated(&self, source_id: u32, context: &Context) -> Result<f64> {
let prediction =
context
.predict(source_id)
.ok_or_else(|| DecodeError::MalformedMessage {
offset: 0,
reason: "No prediction available for interpolated decoding".to_string(),
})?;
Ok(prediction.value)
}
fn decode_pattern(&self, data: &[u8], context: &Context) -> Result<f64> {
let (pattern_id, _) = self.decode_varint(data)?;
let pattern = context
.get_pattern(pattern_id)
.ok_or(DecodeError::UnknownPattern { pattern_id })?;
pattern.value.ok_or_else(|| {
DecodeError::MalformedMessage {
offset: 0,
reason: "Pattern has no numeric value".to_string(),
}
.into()
})
}
fn decode_pattern_delta(&self, data: &[u8], context: &Context) -> Result<f64> {
let (pattern_id, offset) = self.decode_varint(data)?;
if offset >= data.len() {
return Err(DecodeError::BufferTooShort {
needed: offset + 1,
available: data.len(),
}
.into());
}
let pattern = context
.get_pattern(pattern_id)
.ok_or(DecodeError::UnknownPattern { pattern_id })?;
let base_value = pattern.value.ok_or_else(|| DecodeError::MalformedMessage {
offset: 0,
reason: "Pattern has no numeric value".to_string(),
})?;
let delta = data[offset] as i8;
let scale = context.scale_factor() as f64;
Ok(base_value + (delta as f64 / scale))
}
pub fn decode_multi(
&mut self,
message: &EncodedMessage,
context: &Context,
) -> Result<Vec<(u8, f64)>> {
let payload = &message.payload;
let (_source_id, mut offset) = self.decode_varint(payload)?;
if offset >= payload.len() {
return Err(DecodeError::BufferTooShort {
needed: offset + 1,
available: payload.len(),
}
.into());
}
let encoding = payload[offset];
offset += 1;
if encoding != EncodingType::Multi as u8 {
return Err(DecodeError::MalformedMessage {
offset: offset - 1,
reason: "Expected Multi encoding".to_string(),
}
.into());
}
if offset >= payload.len() {
return Err(DecodeError::BufferTooShort {
needed: offset + 1,
available: payload.len(),
}
.into());
}
let count = payload[offset] as usize;
offset += 1;
let mut values = Vec::with_capacity(count);
for _ in 0..count {
if offset >= payload.len() {
return Err(DecodeError::BufferTooShort {
needed: offset + 1,
available: payload.len(),
}
.into());
}
let name_id = payload[offset];
offset += 1;
if offset >= payload.len() {
return Err(DecodeError::BufferTooShort {
needed: offset + 1,
available: payload.len(),
}
.into());
}
let value_encoding_byte = payload[offset];
offset += 1;
let enc_type = EncodingType::from_u8(value_encoding_byte)
.ok_or(DecodeError::UnknownEncodingType(value_encoding_byte))?;
let ch_source_id = name_id as u32;
let value = match enc_type {
EncodingType::Raw64 => {
let v = self.decode_raw64(&payload[offset..])?;
offset += 8;
v
}
EncodingType::Raw32 => {
let v = self.decode_raw32(&payload[offset..])?;
offset += 4;
v
}
EncodingType::Delta8 => {
let v = self.decode_delta8(&payload[offset..], ch_source_id, context)?;
offset += 1;
v
}
EncodingType::Delta16 => {
let v = self.decode_delta16(&payload[offset..], ch_source_id, context)?;
offset += 2;
v
}
EncodingType::Delta32 => {
let v = self.decode_delta32(&payload[offset..], ch_source_id, context)?;
offset += 4;
v
}
EncodingType::Repeated => {
self.decode_repeated(ch_source_id, context)?
}
EncodingType::Interpolated => {
self.decode_interpolated(ch_source_id, context)?
}
_ => {
return Err(DecodeError::MalformedMessage {
offset,
reason: "Unsupported encoding in multi frame".to_string(),
}
.into());
}
};
values.push((name_id, value));
}
Ok(values)
}
pub fn reset(&mut self) {
self.last_sequence = None;
self.last_fixed_sequence = None;
self.last_fixed_ctx_version = None;
}
pub fn last_sequence(&self) -> Option<u16> {
self.last_sequence
}
pub fn last_fixed_sequence(&self) -> Option<u16> {
self.last_fixed_sequence
}
pub fn decode_multi_fixed(
&mut self,
input: &[u8],
channel_count: usize,
context: &Context,
output: &mut [f64],
) -> Result<FixedFrameInfo> {
if channel_count == 0 {
return Err(DecodeError::MalformedMessage {
offset: 0,
reason: "channel_count must be > 0".to_string(),
}
.into());
}
if output.len() < channel_count {
return Err(DecodeError::BufferTooShort {
needed: channel_count,
available: output.len(),
}
.into());
}
let bitmap_bytes = fixed_bitmap_bytes(channel_count);
let min_size = 1 + CompactHeader::SIZE + bitmap_bytes;
if input.len() < min_size {
return Err(DecodeError::BufferTooShort {
needed: min_size,
available: input.len(),
}
.into());
}
let marker = input[0];
let keyframe = match classify_compact_marker(marker) {
Some(kf) => kf,
None => {
return Err(DecodeError::MalformedMessage {
offset: 0,
reason: "not a fixed-channel ALEC frame".to_string(),
}
.into());
}
};
let header = CompactHeader::read(&input[1..1 + CompactHeader::SIZE])?;
let gap_size = match self.last_fixed_sequence {
Some(prev) => {
let diff = header.sequence.wrapping_sub(prev);
if diff == 0 {
0
} else {
diff.saturating_sub(1).min(u8::MAX as u16) as u8
}
}
None => 0,
};
let context_mismatch = if keyframe {
false
} else {
match self.last_fixed_ctx_version {
Some(last) => {
!ctx_version_compatible(header.context_version, last, FIXED_CTX_MAX_JUMP)
}
None => false,
}
};
let bitmap_start = 1 + CompactHeader::SIZE;
let data_start = bitmap_start + bitmap_bytes;
if channel_count > 64 {
return Err(DecodeError::MalformedMessage {
offset: 0,
reason: "channel_count > 64 not supported by fixed wire format".to_string(),
}
.into());
}
let mut encodings: [FixedEncoding; 64] = [FixedEncoding::Repeated; 64];
for i in 0..channel_count {
let bits = (input[bitmap_start + i / 4] >> ((i % 4) * 2)) & 0b11;
encodings[i] = FixedEncoding::from_bits(bits);
}
let data_bytes: usize = (0..channel_count).map(|i| encodings[i].byte_size()).sum();
let expected_total = data_start + data_bytes;
if input.len() < expected_total {
return Err(DecodeError::BufferTooShort {
needed: expected_total,
available: input.len(),
}
.into());
}
let mut cursor = data_start;
for i in 0..channel_count {
let source_id = crate::encoder::Encoder::fixed_channel_source_id(i);
let enc = encodings[i];
let value = match enc {
FixedEncoding::Repeated => match context.last_value(source_id) {
Some(v) => v,
None => {
return Err(DecodeError::MalformedMessage {
offset: cursor,
reason: "Repeated encoding without prior value".to_string(),
}
.into());
}
},
FixedEncoding::Delta8 => {
let delta_byte = input[cursor] as i8;
cursor += 1;
self.apply_delta(delta_byte as f64, source_id, context)?
}
FixedEncoding::Delta16 => {
let b0 = input[cursor];
let b1 = input[cursor + 1];
cursor += 2;
let d = i16::from_be_bytes([b0, b1]);
self.apply_delta(d as f64, source_id, context)?
}
FixedEncoding::Raw32 => {
let bytes = [
input[cursor],
input[cursor + 1],
input[cursor + 2],
input[cursor + 3],
];
cursor += 4;
f32::from_be_bytes(bytes) as f64
}
};
output[i] = value;
}
debug_assert_eq!(cursor, expected_total);
self.last_fixed_sequence = Some(header.sequence);
self.last_fixed_ctx_version = Some(header.context_version);
Ok(FixedFrameInfo {
keyframe,
sequence: header.sequence,
context_version: header.context_version,
gap_size,
context_mismatch,
})
}
fn apply_delta(&self, scaled_delta: f64, source_id: u32, context: &Context) -> Result<f64> {
let prediction = match context.predict(source_id) {
Some(p) => p,
None => {
return Err(DecodeError::MalformedMessage {
offset: 0,
reason: "Delta encoding without prediction".to_string(),
}
.into());
}
};
let scale = context.scale_factor() as f64;
let delta = scaled_delta / scale;
Ok(prediction.value + delta)
}
}
impl Default for Decoder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::classifier::Classifier;
use crate::encoder::Encoder;
use crate::protocol::{MessageHeader, RawData};
#[test]
fn test_roundtrip_raw() {
let mut encoder = Encoder::new();
let mut decoder = Decoder::new();
let classifier = Classifier::default();
let context = Context::new();
let original = RawData::new(42.5, 12345);
let classification = classifier.classify(&original, &context);
let message = encoder.encode(&original, &classification, &context);
let decoded = decoder.decode(&message, &context).unwrap();
assert!((decoded.value - original.value).abs() < 0.001);
assert_eq!(decoded.source_id, original.source_id);
}
#[test]
fn test_roundtrip_delta() {
let mut encoder = Encoder::new();
let mut decoder = Decoder::new();
let classifier = Classifier::default();
let mut ctx_encoder = Context::new();
let mut ctx_decoder = Context::new();
for i in 0..10 {
let data = RawData::new(20.0 + i as f64 * 0.1, i as u64);
ctx_encoder.observe(&data);
ctx_decoder.observe(&data);
}
let original = RawData::new(21.05, 100);
let classification = classifier.classify(&original, &ctx_encoder);
let message = encoder.encode(&original, &classification, &ctx_encoder);
assert!(matches!(
message.encoding_type(),
Some(EncodingType::Delta8) | Some(EncodingType::Delta16)
));
let decoded = decoder.decode(&message, &ctx_decoder).unwrap();
assert!((decoded.value - original.value).abs() < 0.01);
}
#[test]
fn test_roundtrip_repeated() {
let mut encoder = Encoder::new();
let mut decoder = Decoder::new();
let classifier = Classifier::default();
let mut ctx_encoder = Context::new();
let mut ctx_decoder = Context::new();
let data = RawData::new(42.0, 0);
ctx_encoder.observe(&data);
ctx_decoder.observe(&data);
let original = RawData::new(42.0, 1);
let classification = classifier.classify(&original, &ctx_encoder);
let message = encoder.encode(&original, &classification, &ctx_encoder);
assert_eq!(message.encoding_type(), Some(EncodingType::Repeated));
let decoded = decoder.decode(&message, &ctx_decoder).unwrap();
assert!((decoded.value - original.value).abs() < 0.001);
}
#[test]
fn test_roundtrip_multi() {
let mut encoder = Encoder::new();
let mut decoder = Decoder::new();
let context = Context::new();
let values: Vec<(u8, f64)> = vec![(1, 22.5), (2, 65.0), (3, 1013.25)];
let message = encoder.encode_multi(
&values,
42,
12345,
crate::protocol::Priority::P3Normal,
&context,
);
let decoded = decoder.decode_multi(&message, &context).unwrap();
assert_eq!(decoded.len(), values.len());
for ((orig_id, orig_val), (dec_id, dec_val)) in values.iter().zip(decoded.iter()) {
assert_eq!(orig_id, dec_id);
assert!((orig_val - dec_val).abs() < 0.01);
}
}
#[test]
fn test_varint_roundtrip() {
let decoder = Decoder::new();
let test_values = vec![0, 1, 127, 128, 255, 256, 16383, 16384, 100000];
for value in test_values {
let mut buffer = Vec::new();
let mut v = value;
while v >= 0x80 {
buffer.push((v as u8 & 0x7F) | 0x80);
v >>= 7;
}
buffer.push(v as u8);
let (decoded, _) = decoder.decode_varint(&buffer).unwrap();
assert_eq!(decoded, value);
}
}
#[test]
fn test_decode_invalid_encoding() {
let mut decoder = Decoder::new();
let context = Context::new();
let message = EncodedMessage::new(
MessageHeader::default(),
vec![0x00, 0xFF], );
let result = decoder.decode(&message, &context);
assert!(result.is_err());
}
#[test]
fn test_sequence_tracking() {
let mut decoder = Decoder::new();
let context = Context::new();
let mut encoder = Encoder::new();
let classifier = Classifier::default();
let data = RawData::new(42.0, 0);
let classification = classifier.classify(&data, &context);
let msg1 = encoder.encode(&data, &classification, &context);
decoder.decode(&msg1, &context).unwrap();
assert_eq!(decoder.last_sequence(), Some(0));
let msg2 = encoder.encode(&data, &classification, &context);
decoder.decode(&msg2, &context).unwrap();
assert_eq!(decoder.last_sequence(), Some(1));
}
#[test]
fn test_checksum_encode_decode_roundtrip() {
let mut encoder = Encoder::with_checksum();
let mut decoder = Decoder::with_checksum_verification();
let classifier = Classifier::default();
let context = Context::new();
let data = RawData::new(42.5, 12345);
let classification = classifier.classify(&data, &context);
let bytes = encoder.encode_to_bytes(&data, &classification, &context);
let decoded = decoder.decode_bytes(&bytes, &context).unwrap();
assert!((decoded.value - data.value).abs() < 0.001);
assert_eq!(decoded.source_id, data.source_id);
}
#[test]
fn test_checksum_corruption_decode_fails() {
use crate::error::AlecError;
let mut encoder = Encoder::with_checksum();
let mut decoder = Decoder::with_checksum_verification();
let classifier = Classifier::default();
let context = Context::new();
let data = RawData::new(42.5, 12345);
let classification = classifier.classify(&data, &context);
let mut bytes = encoder.encode_to_bytes(&data, &classification, &context);
bytes[5] ^= 0xFF;
let result = decoder.decode_bytes(&bytes, &context);
assert!(matches!(
result,
Err(AlecError::Decode(
crate::error::DecodeError::InvalidChecksum { .. }
))
));
}
#[test]
fn test_no_checksum_still_works() {
let mut encoder = Encoder::new();
let mut decoder = Decoder::new();
let classifier = Classifier::default();
let context = Context::new();
let data = RawData::new(123.456, 999);
let classification = classifier.classify(&data, &context);
let bytes = encoder.encode_to_bytes(&data, &classification, &context);
let decoded = decoder.decode_bytes(&bytes, &context).unwrap();
assert!((decoded.value - data.value).abs() < 0.001);
}
}