use crate::lrc::LrcMode;
pub const STX: u8 = 0x02;
pub const ETX: u8 = 0x03;
pub const SOH: u8 = 0x01;
pub const EOT: u8 = 0x04;
pub const ACK: u8 = 0x06;
pub const NAK: u8 = 0x15;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PacketType {
Application,
Progress,
Ack,
Nak,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodedPacket {
pub packet_type: PacketType,
pub payload: Vec<u8>,
pub valid_lrc: bool,
}
impl DecodedPacket {
fn unknown() -> Self {
Self {
packet_type: PacketType::Unknown,
payload: Vec::new(),
valid_lrc: false,
}
}
fn control(packet_type: PacketType) -> Self {
Self {
packet_type,
payload: Vec::new(),
valid_lrc: true,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PacketCodec {
lrc_mode: LrcMode,
}
impl PacketCodec {
#[must_use]
pub fn new(mode: LrcMode) -> Self {
Self { lrc_mode: mode }
}
#[must_use]
pub fn lrc_mode(&self) -> LrcMode {
self.lrc_mode
}
#[must_use]
pub fn encode_application(&self, payload: &[u8]) -> Vec<u8> {
let mut frame = Vec::with_capacity(payload.len() + 3);
frame.push(STX);
frame.extend_from_slice(payload);
frame.push(ETX);
frame.push(self.lrc_mode.compute(payload));
frame
}
#[must_use]
pub fn encode_control(&self, ctrl: u8) -> Vec<u8> {
vec![ctrl, ETX, self.lrc_mode.compute(&[ctrl])]
}
#[must_use]
pub fn decode(&self, data: &[u8]) -> DecodedPacket {
let Some(&first) = data.first() else {
return DecodedPacket::unknown();
};
match first {
ACK => return DecodedPacket::control(PacketType::Ack),
NAK => return DecodedPacket::control(PacketType::Nak),
_ => {}
}
if first == SOH {
if data.len() < 2 || *data.last().unwrap() != EOT {
return DecodedPacket::unknown();
}
return DecodedPacket {
packet_type: PacketType::Progress,
payload: data[1..data.len() - 1].to_vec(),
valid_lrc: true,
};
}
if first == STX {
let Some(etx_index) = data.iter().position(|&b| b == ETX) else {
return DecodedPacket::unknown();
};
if etx_index + 2 != data.len() {
return DecodedPacket::unknown();
}
let payload = &data[1..etx_index];
let rx_lrc = data[etx_index + 1];
let calc_lrc = self.lrc_mode.compute(payload);
return DecodedPacket {
packet_type: PacketType::Application,
payload: payload.to_vec(),
valid_lrc: rx_lrc == calc_lrc,
};
}
DecodedPacket::unknown()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_application_frames_stx_payload_etx_lrc() {
let codec = PacketCodec::new(LrcMode::Std);
let frame = codec.encode_application(b"AB");
assert_eq!(frame, vec![STX, b'A', b'B', ETX, 0x7C]); }
#[test]
fn application_round_trip_all_modes() {
for mode in [
LrcMode::Stx,
LrcMode::Std,
LrcMode::Noext,
LrcMode::StxNoext,
] {
let codec = PacketCodec::new(mode);
let payload = b"123456780P0000065000";
let decoded = codec.decode(&codec.encode_application(payload));
assert_eq!(decoded.packet_type, PacketType::Application);
assert_eq!(decoded.payload, payload);
assert!(decoded.valid_lrc);
}
}
#[test]
fn application_detects_corrupted_lrc() {
let codec = PacketCodec::new(LrcMode::Std);
let mut frame = codec.encode_application(b"HELLO");
*frame.last_mut().unwrap() ^= 0xFF; let decoded = codec.decode(&frame);
assert_eq!(decoded.packet_type, PacketType::Application);
assert_eq!(decoded.payload, b"HELLO");
assert!(!decoded.valid_lrc);
}
#[test]
fn encode_control_frames_ctrl_etx_lrc() {
let codec = PacketCodec::new(LrcMode::Std);
let frame = codec.encode_control(ACK);
assert_eq!(frame.len(), 3);
assert_eq!(frame[0], ACK);
assert_eq!(frame[1], ETX);
}
#[test]
fn decode_ack() {
let decoded = PacketCodec::new(LrcMode::Std).decode(&[ACK]);
assert_eq!(decoded.packet_type, PacketType::Ack);
assert!(decoded.valid_lrc);
}
#[test]
fn decode_nak() {
let decoded = PacketCodec::new(LrcMode::Std).decode(&[NAK]);
assert_eq!(decoded.packet_type, PacketType::Nak);
assert!(decoded.valid_lrc);
}
#[test]
fn decode_full_control_frame_from_encode_control() {
let codec = PacketCodec::new(LrcMode::Std);
assert_eq!(
codec.decode(&codec.encode_control(ACK)).packet_type,
PacketType::Ack
);
assert_eq!(
codec.decode(&codec.encode_control(NAK)).packet_type,
PacketType::Nak
);
}
#[test]
fn decode_control_is_lead_byte_only_ignoring_trailing_and_lrc() {
let codec = PacketCodec::new(LrcMode::Std);
assert_eq!(codec.decode(&[ACK, 0x00]).packet_type, PacketType::Ack);
assert_eq!(codec.decode(&[ACK, ETX, 0xFF]).packet_type, PacketType::Ack);
assert_eq!(
codec.decode(&[NAK, 0x99, 0x42]).packet_type,
PacketType::Nak
);
}
#[test]
fn decode_empty_is_unknown() {
let decoded = PacketCodec::new(LrcMode::Std).decode(&[]);
assert_eq!(decoded.packet_type, PacketType::Unknown);
assert!(!decoded.valid_lrc);
}
#[test]
fn decode_lone_soh_is_unknown_not_panic() {
let decoded = PacketCodec::new(LrcMode::Std).decode(&[SOH]);
assert_eq!(decoded.packet_type, PacketType::Unknown);
assert!(!decoded.valid_lrc);
}
#[test]
fn decode_progress_update() {
let codec = PacketCodec::new(LrcMode::Std);
let msg = b"ELABORAZIONE... "; let mut frame = vec![SOH];
frame.extend_from_slice(msg);
frame.push(EOT);
let decoded = codec.decode(&frame);
assert_eq!(decoded.packet_type, PacketType::Progress);
assert_eq!(decoded.payload, msg);
}
#[test]
fn decode_stx_without_etx_is_unknown() {
let decoded = PacketCodec::new(LrcMode::Std).decode(&[STX, b'A', b'B']);
assert_eq!(decoded.packet_type, PacketType::Unknown);
assert!(!decoded.valid_lrc);
}
#[test]
fn decode_stx_with_etx_but_no_lrc_is_unknown() {
let decoded = PacketCodec::new(LrcMode::Std).decode(&[STX, b'A', ETX]);
assert_eq!(decoded.packet_type, PacketType::Unknown);
assert!(!decoded.valid_lrc);
}
#[test]
fn decode_unknown_lead_byte() {
let decoded = PacketCodec::new(LrcMode::Std).decode(&[0x99, 0x00]);
assert_eq!(decoded.packet_type, PacketType::Unknown);
assert!(!decoded.valid_lrc);
}
#[test]
fn decode_stx_with_trailing_bytes_after_lrc_is_unknown() {
let codec = PacketCodec::new(LrcMode::Std);
let mut frame = codec.encode_application(b"AB"); frame.push(0x00); let decoded = codec.decode(&frame);
assert_eq!(decoded.packet_type, PacketType::Unknown);
assert!(!decoded.valid_lrc);
}
#[test]
fn decode_coalesced_frames_is_unknown() {
let codec = PacketCodec::new(LrcMode::Std);
let mut first = codec.encode_application(b"AB");
first.extend_from_slice(&codec.encode_application(b"CD"));
let decoded = codec.decode(&first);
assert_eq!(decoded.packet_type, PacketType::Unknown);
assert!(!decoded.valid_lrc);
}
#[test]
fn decode_soh_without_eot_is_unknown() {
let codec = PacketCodec::new(LrcMode::Std);
let mut frame = vec![SOH];
frame.extend_from_slice(b"ELABORAZIONE... ");
frame.push(0xFF); let decoded = codec.decode(&frame);
assert_eq!(decoded.packet_type, PacketType::Unknown);
assert!(!decoded.valid_lrc);
}
}