use crate::core::{DecodeContext, MessageCodec, MessageFields};
pub const MAX_PAYLOAD_BYTES: usize = 10;
const HEAD_BITS: usize = 84;
const CRC7_POLY: u8 = 0x09;
fn crc7(bits: &[u8]) -> u8 {
let mut crc: u8 = 0;
for &bit in bits {
let in_bit = bit & 1;
let top = (crc >> 6) & 1;
crc = ((crc << 1) | in_bit) & 0x7F;
if top ^ in_bit != 0 {
crc ^= CRC7_POLY;
}
}
crc & 0x7F
}
#[derive(Copy, Clone, Debug, Default)]
pub struct PacketBytesMessage;
impl MessageCodec for PacketBytesMessage {
type Unpacked = Vec<u8>;
const PAYLOAD_BITS: u32 = 91;
const CRC_BITS: u32 = 7;
fn pack(&self, fields: &MessageFields) -> Option<Vec<u8>> {
let bytes = fields.free_text.as_ref()?.as_bytes();
if bytes.is_empty() || bytes.len() > MAX_PAYLOAD_BYTES {
return None;
}
let mut out = vec![0u8; PacketBytesMessage::PAYLOAD_BITS as usize];
let len_code = (bytes.len() - 1) as u8;
for i in 0..4 {
out[i] = (len_code >> (3 - i)) & 1;
}
for byte_idx in 0..MAX_PAYLOAD_BYTES {
let b = if byte_idx < bytes.len() {
bytes[byte_idx]
} else {
0
};
for bit in 0..8 {
out[4 + byte_idx * 8 + bit] = (b >> (7 - bit)) & 1;
}
}
let crc = crc7(&out[..HEAD_BITS]);
for i in 0..7 {
out[HEAD_BITS + i] = (crc >> (6 - i)) & 1;
}
Some(out)
}
fn unpack(&self, payload: &[u8], _ctx: &DecodeContext) -> Option<Self::Unpacked> {
if payload.len() != Self::PAYLOAD_BITS as usize {
return None;
}
if !Self::verify_info(payload) {
return None;
}
let mut len_code: u8 = 0;
for i in 0..4 {
len_code = (len_code << 1) | (payload[i] & 1);
}
let len = len_code as usize + 1;
if len > MAX_PAYLOAD_BYTES {
return None;
}
let mut out = Vec::with_capacity(len);
for byte_idx in 0..len {
let mut b: u8 = 0;
for bit in 0..8 {
b = (b << 1) | (payload[4 + byte_idx * 8 + bit] & 1);
}
out.push(b);
}
Some(out)
}
fn verify_info(info: &[u8]) -> bool {
if info.len() != Self::PAYLOAD_BITS as usize {
return false;
}
let computed = crc7(&info[..HEAD_BITS]);
let mut received: u8 = 0;
for &b in &info[HEAD_BITS..(HEAD_BITS + 7)] {
received = (received << 1) | (b & 1);
}
computed == received
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pack(bytes: &[u8]) -> Option<Vec<u8>> {
let fields = MessageFields {
free_text: Some(unsafe { std::str::from_utf8_unchecked(bytes) }.to_string()),
..Default::default()
};
PacketBytesMessage.pack(&fields)
}
fn unpack(bits: &[u8]) -> Option<Vec<u8>> {
PacketBytesMessage.unpack(bits, &DecodeContext::default())
}
#[test]
fn pack_then_unpack_roundtrips_short_payload() {
let payload = b"hello";
let bits = pack(payload).expect("pack short");
let out = unpack(&bits).expect("unpack short");
assert_eq!(out, payload);
}
#[test]
fn pack_then_unpack_roundtrips_max_length() {
let payload = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"; assert_eq!(payload.len(), MAX_PAYLOAD_BYTES);
let bits = pack(payload).expect("pack 10");
let out = unpack(&bits).expect("unpack 10");
assert_eq!(out, payload);
}
#[test]
fn pack_then_unpack_roundtrips_single_byte() {
let payload = b"\x42";
let bits = pack(payload).expect("pack 1");
let out = unpack(&bits).expect("unpack 1");
assert_eq!(out, payload);
}
#[test]
fn pack_rejects_empty_payload() {
assert!(pack(b"").is_none(), "empty payload must be rejected");
}
#[test]
fn pack_rejects_oversize_payload() {
let bytes = vec![0x55_u8; 11]; let fields = MessageFields {
free_text: Some(unsafe { String::from_utf8_unchecked(bytes) }),
..Default::default()
};
assert!(
PacketBytesMessage.pack(&fields).is_none(),
"11-byte payload must be rejected"
);
}
#[test]
fn unpack_rejects_wrong_length_buffer() {
let bits = vec![0u8; 90]; assert!(unpack(&bits).is_none(), "bit buffer of length 90 rejected");
let bits = vec![0u8; 92];
assert!(unpack(&bits).is_none(), "bit buffer of length 92 rejected");
}
#[test]
fn unpack_rejects_invalid_length_code() {
let mut bits = vec![0u8; 91];
bits[0] = 1;
bits[1] = 0;
bits[2] = 1;
bits[3] = 0; assert!(
unpack(&bits).is_none(),
"length code 10 (→ 11 bytes) must reject"
);
}
#[test]
fn pack_payload_bits_in_correct_positions() {
let bits = pack(b"\xAA").expect("pack 0xAA");
assert_eq!(bits.len(), 91);
assert_eq!(&bits[0..4], &[0, 0, 0, 0], "length code");
assert_eq!(&bits[4..12], &[1, 0, 1, 0, 1, 0, 1, 0], "byte 0 bits");
for &b in &bits[12..84] {
assert_eq!(b, 0, "payload tail must be zero");
}
let computed = crc7(&bits[..84]);
let mut stored: u8 = 0;
for &b in &bits[84..91] {
stored = (stored << 1) | (b & 1);
}
assert_eq!(stored, computed, "trailer must hold the CRC-7 of the head");
}
#[test]
fn unpack_rejects_bit_flip_in_payload() {
let mut bits = pack(b"hello").expect("pack");
bits[20] ^= 1;
assert!(
unpack(&bits).is_none(),
"single bit flip must fail CRC-7 verification"
);
}
#[test]
fn unpack_rejects_bit_flip_in_crc() {
let mut bits = pack(b"hi").expect("pack");
bits[88] ^= 1;
assert!(
unpack(&bits).is_none(),
"bit flip in CRC trailer must fail verification"
);
}
#[test]
fn verify_info_accepts_valid_pack_output() {
for payload in [b"x".as_slice(), b"hello", b"\x00\x01\x02\x03\x04"] {
let bits = pack(payload).expect("pack");
assert!(
PacketBytesMessage::verify_info(&bits),
"verify_info must accept a fresh pack() output for {:?}",
payload
);
}
}
#[test]
fn verify_info_rejects_wrong_length() {
assert!(!PacketBytesMessage::verify_info(&[0u8; 90]));
assert!(!PacketBytesMessage::verify_info(&[0u8; 92]));
assert!(!PacketBytesMessage::verify_info(&[0u8; 0]));
}
}