#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::error::DecodeError;
use core::fmt;
pub const CHECKSUM_SIZE: usize = 4;
#[derive(Debug, Clone, PartialEq)]
pub struct RawData {
pub source_id: u32,
pub timestamp: u64,
pub value: f64,
}
impl RawData {
pub fn new(value: f64, timestamp: u64) -> Self {
Self {
source_id: 0,
timestamp,
value,
}
}
pub fn with_source(source_id: u32, value: f64, timestamp: u64) -> Self {
Self {
source_id,
timestamp,
value,
}
}
pub fn raw_size(&self) -> usize {
20
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ChannelInput {
pub name_id: u8,
pub source_id: u32,
pub value: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(u8)]
pub enum Priority {
P1Critical = 0,
P2Important = 1,
#[default]
P3Normal = 2,
P4Deferred = 3,
P5Disposable = 4,
}
impl Priority {
pub fn should_transmit(&self) -> bool {
matches!(
self,
Priority::P1Critical | Priority::P2Important | Priority::P3Normal
)
}
pub fn requires_ack(&self) -> bool {
matches!(self, Priority::P1Critical)
}
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(Priority::P1Critical),
1 => Some(Priority::P2Important),
2 => Some(Priority::P3Normal),
3 => Some(Priority::P4Deferred),
4 => Some(Priority::P5Disposable),
_ => None,
}
}
}
impl fmt::Display for Priority {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Priority::P1Critical => write!(f, "P1-CRITICAL"),
Priority::P2Important => write!(f, "P2-IMPORTANT"),
Priority::P3Normal => write!(f, "P3-NORMAL"),
Priority::P4Deferred => write!(f, "P4-DEFERRED"),
Priority::P5Disposable => write!(f, "P5-DISPOSABLE"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[repr(u8)]
pub enum MessageType {
#[default]
Data = 0,
Sync = 1,
Request = 2,
Response = 3,
Ack = 4,
Nack = 5,
Heartbeat = 6,
DataFixedChannel = 7,
}
impl MessageType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(MessageType::Data),
1 => Some(MessageType::Sync),
2 => Some(MessageType::Request),
3 => Some(MessageType::Response),
4 => Some(MessageType::Ack),
5 => Some(MessageType::Nack),
6 => Some(MessageType::Heartbeat),
7 => Some(MessageType::DataFixedChannel),
_ => None,
}
}
}
pub const COMPACT_MARKER_DATA: u8 = 0xA1;
pub const COMPACT_MARKER_KEYFRAME: u8 = 0xA2;
#[inline]
pub fn classify_compact_marker(byte: u8) -> Option<bool> {
match byte {
COMPACT_MARKER_DATA => Some(false),
COMPACT_MARKER_KEYFRAME => Some(true),
_ => None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompactHeader {
pub sequence: u16,
pub context_version: u16,
}
impl CompactHeader {
pub const SIZE: usize = 4;
pub fn new(sequence: u16, context_version: u16) -> Self {
Self {
sequence,
context_version,
}
}
pub fn write(&self, buf: &mut [u8]) -> Result<usize, DecodeError> {
if buf.len() < Self::SIZE {
return Err(DecodeError::BufferTooShort {
needed: Self::SIZE,
available: buf.len(),
});
}
buf[0..2].copy_from_slice(&self.sequence.to_be_bytes());
buf[2..4].copy_from_slice(&self.context_version.to_be_bytes());
Ok(Self::SIZE)
}
pub fn read(buf: &[u8]) -> Result<Self, DecodeError> {
if buf.len() < Self::SIZE {
return Err(DecodeError::BufferTooShort {
needed: Self::SIZE,
available: buf.len(),
});
}
let sequence = u16::from_be_bytes([buf[0], buf[1]]);
let context_version = u16::from_be_bytes([buf[2], buf[3]]);
Ok(Self {
sequence,
context_version,
})
}
}
#[inline]
pub fn ctx_version_compatible(incoming: u16, last: u16, max_forward_jump: u16) -> bool {
let forward = incoming.wrapping_sub(last);
forward <= max_forward_jump
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(u8)]
pub enum EncodingType {
#[default]
Raw64 = 0x00,
Raw32 = 0x01,
Delta8 = 0x10,
Delta16 = 0x11,
Delta32 = 0x12,
Pattern = 0x20,
PatternDelta = 0x21,
Repeated = 0x30,
Interpolated = 0x31,
Multi = 0x40,
}
impl EncodingType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0x00 => Some(EncodingType::Raw64),
0x01 => Some(EncodingType::Raw32),
0x10 => Some(EncodingType::Delta8),
0x11 => Some(EncodingType::Delta16),
0x12 => Some(EncodingType::Delta32),
0x20 => Some(EncodingType::Pattern),
0x21 => Some(EncodingType::PatternDelta),
0x30 => Some(EncodingType::Repeated),
0x31 => Some(EncodingType::Interpolated),
0x40 => Some(EncodingType::Multi),
_ => None,
}
}
pub fn typical_size(&self) -> usize {
match self {
EncodingType::Raw64 => 8,
EncodingType::Raw32 => 4,
EncodingType::Delta8 => 1,
EncodingType::Delta16 => 2,
EncodingType::Delta32 => 4,
EncodingType::Pattern => 2, EncodingType::PatternDelta => 3, EncodingType::Repeated => 0,
EncodingType::Interpolated => 0,
EncodingType::Multi => 0, }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MessageHeader {
pub version: u8,
pub message_type: MessageType,
pub priority: Priority,
pub sequence: u16,
pub timestamp: u32,
pub context_version: u32,
}
impl MessageHeader {
pub fn new(message_type: MessageType, priority: Priority) -> Self {
Self {
version: crate::PROTOCOL_VERSION,
message_type,
priority,
sequence: 0,
timestamp: 0,
context_version: 0,
}
}
pub const SIZE: usize = 10;
pub fn encode_header_byte(&self) -> u8 {
let version_bits = (self.version & 0x03) << 6;
let type_bits = (self.message_type as u8 & 0x07) << 3;
let priority_bits = self.priority as u8 & 0x07;
version_bits | type_bits | priority_bits
}
pub fn decode_header_byte(byte: u8) -> (u8, Option<MessageType>, Option<Priority>) {
let version = (byte >> 6) & 0x03;
let msg_type = MessageType::from_u8((byte >> 3) & 0x07);
let priority = Priority::from_u8(byte & 0x07);
(version, msg_type, priority)
}
pub fn to_bytes(&self) -> [u8; Self::SIZE] {
let mut bytes = [0u8; Self::SIZE];
bytes[0] = self.encode_header_byte();
bytes[1..3].copy_from_slice(&self.sequence.to_be_bytes());
bytes[3..7].copy_from_slice(&self.timestamp.to_be_bytes());
let cv = self.context_version & 0x00FFFFFF;
bytes[7..10].copy_from_slice(&cv.to_be_bytes()[1..]);
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < Self::SIZE {
return None;
}
let (version, msg_type, priority) = Self::decode_header_byte(bytes[0]);
let msg_type = msg_type?;
let priority = priority?;
let sequence = u16::from_be_bytes([bytes[1], bytes[2]]);
let timestamp = u32::from_be_bytes([bytes[3], bytes[4], bytes[5], bytes[6]]);
let context_version = u32::from_be_bytes([0, bytes[7], bytes[8], bytes[9]]);
Some(Self {
version,
message_type: msg_type,
priority,
sequence,
timestamp,
context_version,
})
}
}
impl Default for MessageHeader {
fn default() -> Self {
Self::new(MessageType::Data, Priority::P3Normal)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EncodedMessage {
pub header: MessageHeader,
pub payload: Vec<u8>,
}
impl EncodedMessage {
pub fn new(header: MessageHeader, payload: Vec<u8>) -> Self {
Self { header, payload }
}
pub fn len(&self) -> usize {
MessageHeader::SIZE + self.payload.len()
}
pub fn is_empty(&self) -> bool {
self.payload.is_empty()
}
pub fn encoding_type(&self) -> Option<EncodingType> {
let mut pos = 0;
while pos < self.payload.len() {
let byte = self.payload[pos];
pos += 1;
if byte & 0x80 == 0 {
return self
.payload
.get(pos)
.and_then(|&b| EncodingType::from_u8(b));
}
}
None
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(self.len());
bytes.extend_from_slice(&self.header.to_bytes());
bytes.extend_from_slice(&self.payload);
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < MessageHeader::SIZE {
return None;
}
let header = MessageHeader::from_bytes(&bytes[..MessageHeader::SIZE])?;
let payload = bytes[MessageHeader::SIZE..].to_vec();
Some(Self { header, payload })
}
pub fn compute_checksum(&self) -> u32 {
use xxhash_rust::xxh32::xxh32;
let mut data = Vec::with_capacity(MessageHeader::SIZE + self.payload.len());
data.extend_from_slice(&self.header.to_bytes());
data.extend_from_slice(&self.payload);
xxh32(&data, 0) }
pub fn to_bytes_with_checksum(&self) -> Vec<u8> {
let mut bytes = self.to_bytes();
let checksum = self.compute_checksum();
bytes.extend_from_slice(&checksum.to_be_bytes());
bytes
}
pub fn from_bytes_with_checksum(bytes: &[u8]) -> Result<Self, DecodeError> {
if bytes.len() < MessageHeader::SIZE + CHECKSUM_SIZE {
return Err(DecodeError::BufferTooShort {
needed: MessageHeader::SIZE + CHECKSUM_SIZE,
available: bytes.len(),
});
}
let checksum_offset = bytes.len() - CHECKSUM_SIZE;
let expected = u32::from_be_bytes(bytes[checksum_offset..].try_into().unwrap());
let message =
Self::from_bytes(&bytes[..checksum_offset]).ok_or(DecodeError::InvalidHeader)?;
let actual = message.compute_checksum();
if actual != expected {
return Err(DecodeError::InvalidChecksum { expected, actual });
}
Ok(message)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DecodedData {
pub source_id: u32,
pub timestamp: u64,
pub value: f64,
pub priority: Priority,
pub deferred_available: bool,
}
impl DecodedData {
pub fn new(source_id: u32, timestamp: u64, value: f64, priority: Priority) -> Self {
Self {
source_id,
timestamp,
value,
priority,
deferred_available: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_priority_ordering() {
assert!(Priority::P1Critical < Priority::P2Important);
assert!(Priority::P2Important < Priority::P3Normal);
assert!(Priority::P3Normal < Priority::P4Deferred);
assert!(Priority::P4Deferred < Priority::P5Disposable);
}
#[test]
fn test_priority_should_transmit() {
assert!(Priority::P1Critical.should_transmit());
assert!(Priority::P2Important.should_transmit());
assert!(Priority::P3Normal.should_transmit());
assert!(!Priority::P4Deferred.should_transmit());
assert!(!Priority::P5Disposable.should_transmit());
}
#[test]
fn test_header_byte_roundtrip() {
let header = MessageHeader {
version: 1,
message_type: MessageType::Data,
priority: Priority::P2Important,
sequence: 0,
timestamp: 0,
context_version: 0,
};
let byte = header.encode_header_byte();
let (version, msg_type, priority) = MessageHeader::decode_header_byte(byte);
assert_eq!(version, 1);
assert_eq!(msg_type, Some(MessageType::Data));
assert_eq!(priority, Some(Priority::P2Important));
}
#[test]
fn test_header_serialization() {
let header = MessageHeader {
version: 1,
message_type: MessageType::Sync,
priority: Priority::P1Critical,
sequence: 12345,
timestamp: 67890,
context_version: 42,
};
let bytes = header.to_bytes();
let restored = MessageHeader::from_bytes(&bytes).unwrap();
assert_eq!(header.version, restored.version);
assert_eq!(header.message_type, restored.message_type);
assert_eq!(header.priority, restored.priority);
assert_eq!(header.sequence, restored.sequence);
assert_eq!(header.timestamp, restored.timestamp);
assert_eq!(header.context_version, restored.context_version);
}
#[test]
fn test_message_serialization() {
let message = EncodedMessage {
header: MessageHeader::default(),
payload: vec![0x00, 0x10, 0x42],
};
let bytes = message.to_bytes();
let restored = EncodedMessage::from_bytes(&bytes).unwrap();
assert_eq!(message.header.message_type, restored.header.message_type);
assert_eq!(message.payload, restored.payload);
}
#[test]
fn test_raw_data() {
let data = RawData::new(42.5, 12345);
assert_eq!(data.source_id, 0);
assert_eq!(data.value, 42.5);
assert_eq!(data.timestamp, 12345);
assert_eq!(data.raw_size(), 20);
}
#[test]
fn test_checksum_computation() {
let message = EncodedMessage {
header: MessageHeader::default(),
payload: vec![0x00, 0x10, 0x42],
};
let checksum1 = message.compute_checksum();
let checksum2 = message.compute_checksum();
assert_eq!(checksum1, checksum2);
let message2 = EncodedMessage {
header: MessageHeader::default(),
payload: vec![0x00, 0x10, 0x43],
};
let checksum3 = message2.compute_checksum();
assert_ne!(checksum1, checksum3);
}
#[test]
fn test_checksum_roundtrip() {
let message = EncodedMessage {
header: MessageHeader {
version: 1,
message_type: MessageType::Data,
priority: Priority::P2Important,
sequence: 42,
timestamp: 12345,
context_version: 7,
},
payload: vec![0x00, 0x10, 0x42, 0x55, 0xAA],
};
let bytes = message.to_bytes_with_checksum();
let restored = EncodedMessage::from_bytes_with_checksum(&bytes).unwrap();
assert_eq!(message.header.sequence, restored.header.sequence);
assert_eq!(message.header.timestamp, restored.header.timestamp);
assert_eq!(message.payload, restored.payload);
}
#[test]
fn test_checksum_corruption_detected() {
let message = EncodedMessage {
header: MessageHeader::default(),
payload: vec![0x00, 0x10, 0x42],
};
let mut bytes = message.to_bytes_with_checksum();
bytes[MessageHeader::SIZE] ^= 0xFF;
let result = EncodedMessage::from_bytes_with_checksum(&bytes);
assert!(matches!(result, Err(DecodeError::InvalidChecksum { .. })));
}
#[test]
fn test_checksum_buffer_too_short() {
let short_bytes = vec![0u8; MessageHeader::SIZE];
let result = EncodedMessage::from_bytes_with_checksum(&short_bytes);
assert!(matches!(result, Err(DecodeError::BufferTooShort { .. })));
}
#[test]
fn test_compact_header_roundtrip() {
let h = CompactHeader::new(12345, 6789);
let mut buf = [0u8; CompactHeader::SIZE];
assert_eq!(h.write(&mut buf).unwrap(), CompactHeader::SIZE);
assert_eq!(buf, [0x30, 0x39, 0x1A, 0x85]);
let back = CompactHeader::read(&buf).unwrap();
assert_eq!(back, h);
}
#[test]
fn test_compact_header_buffer_too_short() {
let h = CompactHeader::new(1, 2);
let mut buf = [0u8; 3];
assert!(matches!(
h.write(&mut buf),
Err(DecodeError::BufferTooShort { .. })
));
assert!(matches!(
CompactHeader::read(&buf),
Err(DecodeError::BufferTooShort { .. })
));
}
#[test]
fn test_classify_compact_marker() {
assert_eq!(classify_compact_marker(0xA1), Some(false));
assert_eq!(classify_compact_marker(0xA2), Some(true));
assert_eq!(classify_compact_marker(0x00), None);
assert_eq!(classify_compact_marker(0xFF), None);
assert_eq!(classify_compact_marker(0x7A), None);
}
#[test]
fn test_message_type_data_fixed_channel() {
assert_eq!(MessageType::DataFixedChannel as u8, 7);
assert_eq!(MessageType::from_u8(7), Some(MessageType::DataFixedChannel));
}
#[test]
fn test_ctx_version_compatible_forward_and_wraparound() {
assert!(ctx_version_compatible(100, 100, 32));
assert!(ctx_version_compatible(101, 100, 32));
assert!(ctx_version_compatible(131, 100, 32));
assert!(ctx_version_compatible(132, 100, 32));
assert!(!ctx_version_compatible(133, 100, 32));
assert!(ctx_version_compatible(0, 65535, 32));
assert!(ctx_version_compatible(5, 65530, 32));
assert!(ctx_version_compatible(26, 65530, 32));
assert!(!ctx_version_compatible(31, 65530, 32));
assert!(!ctx_version_compatible(1000, 65530, 32));
assert!(!ctx_version_compatible(10, 100, 32));
}
}