pub const FLAG: u8 = 0x7E;
pub const ESC: u8 = 0x7D;
pub const ESC_MASK: u8 = 0x20;
const RESERVED: &[u8] = &[0x7E, 0x7D, 0x11, 0x13];
pub const ACK_FRAME: u8 = 0x81; pub const NAK_FRAME: u8 = 0xA1; pub const RST_FRAME: u8 = 0xC0;
pub const RSTACK_FRAME: u8 = 0xC1; pub const ERROR_FRAME: u8 = 0xC2;
pub fn crc16(data: &[u8]) -> u16 {
let mut crc: u16 = 0xFFFF;
for &byte in data {
crc ^= (byte as u16) << 8;
for _ in 0..8 {
if crc & 0x8000 != 0 {
crc = (crc << 1) ^ 0x1021;
} else {
crc <<= 1;
}
}
}
crc
}
pub fn stuff(data: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(data.len() + 4);
for &b in data {
if RESERVED.contains(&b) {
out.push(ESC);
out.push(b ^ ESC_MASK);
} else {
out.push(b);
}
}
out
}
pub fn unstuff(data: &[u8]) -> Result<Vec<u8>, &'static str> {
let mut out = Vec::with_capacity(data.len());
let mut i = 0;
while i < data.len() {
if data[i] == ESC {
i += 1;
if i >= data.len() {
return Err("trailing escape byte");
}
out.push(data[i] ^ ESC_MASK);
} else {
out.push(data[i]);
}
i += 1;
}
Ok(out)
}
pub fn encode_frame(payload: &[u8]) -> Vec<u8> {
let crc = crc16(payload);
let mut full = payload.to_vec();
full.push((crc >> 8) as u8);
full.push(crc as u8);
let stuffed = stuff(&full);
let mut frame = Vec::with_capacity(stuffed.len() + 2);
frame.push(FLAG);
frame.extend_from_slice(&stuffed);
frame.push(FLAG);
frame
}
pub fn decode_frame(raw: &[u8]) -> Result<Vec<u8>, &'static str> {
let inner = if raw.first() == Some(&FLAG) && raw.last() == Some(&FLAG) {
&raw[1..raw.len() - 1]
} else {
raw
};
let unstuffed = unstuff(inner)?;
if unstuffed.len() < 2 {
return Err("frame too short");
}
let (payload, crc_bytes) = unstuffed.split_at(unstuffed.len() - 2);
let received_crc = ((crc_bytes[0] as u16) << 8) | crc_bytes[1] as u16;
let computed_crc = crc16(payload);
if received_crc != computed_crc {
return Err("CRC mismatch");
}
Ok(payload.to_vec())
}
pub fn encode_control_frame(ctrl: u8) -> Vec<u8> {
encode_frame(&[ctrl])
}
pub fn build_ack(seq: u8, n_rdy: bool) -> Vec<u8> {
let ctrl = 0x80 | (if n_rdy { 0x08 } else { 0 }) | (seq & 0x07);
encode_frame(&[ctrl])
}
pub fn build_nak(seq: u8, n_rdy: bool) -> Vec<u8> {
let ctrl = 0xA0 | (if n_rdy { 0x08 } else { 0 }) | (seq & 0x07);
encode_frame(&[ctrl])
}
pub fn build_rst() -> Vec<u8> {
encode_frame(&[RST_FRAME])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ash_crc16_ccitt_known_vector() {
assert_eq!(crc16(b"123456789"), 0x29B1);
}
#[test]
fn ash_crc16_ccitt_five_bytes() {
let data = [0x1A, 0xC0, 0x38, 0xBC, 0xF3];
assert_eq!(crc16(&data), 0x0844);
}
#[test]
fn ash_crc16_empty() {
assert_eq!(crc16(&[]), 0xFFFF);
}
#[test]
fn ash_byte_stuff_0x7e() {
let stuffed = stuff(&[0x7E]);
assert_eq!(stuffed, [ESC, 0x5E]);
}
#[test]
fn ash_byte_stuff_0x7d() {
let stuffed = stuff(&[0x7D]);
assert_eq!(stuffed, [ESC, 0x5D]);
}
#[test]
fn ash_byte_stuff_0x11_0x13() {
let stuffed = stuff(&[0x11, 0x13]);
assert_eq!(stuffed, [ESC, 0x31, ESC, 0x33]);
}
#[test]
fn ash_unstuff_roundtrip() {
let original = vec![0x00, 0x7E, 0x7D, 0x11, 0x13, 0xFF];
let stuffed = stuff(&original);
let unstuffed = unstuff(&stuffed).unwrap();
assert_eq!(unstuffed, original);
}
#[test]
fn ash_unstuff_trailing_escape_error() {
assert!(unstuff(&[0x7D]).is_err());
}
#[test]
fn ash_encode_decode_roundtrip() {
let payload = vec![0x00, 0x42, 0xAB, 0x11];
let frame = encode_frame(&payload);
let decoded = decode_frame(&frame).unwrap();
assert_eq!(decoded, payload);
}
#[test]
fn ash_decode_crc_mismatch() {
let mut frame = encode_frame(&[0x00, 0x01, 0x02]);
let mid = frame.len() / 2;
frame[mid] ^= 0xFF;
let _ = decode_frame(&frame);
}
#[test]
fn ash_ack_frame_encode() {
let frame = build_ack(0, false);
assert_eq!(frame.first(), Some(&FLAG));
assert_eq!(frame.last(), Some(&FLAG));
}
#[test]
fn ash_nak_frame_encode() {
let frame = build_nak(3, false);
assert!(frame.len() > 2);
}
#[test]
fn ash_rst_frame_encode() {
let frame = build_rst();
assert_eq!(frame.first(), Some(&FLAG));
assert_eq!(frame.last(), Some(&FLAG));
}
}