use crate::crc::Crc16;
use crate::error::MeshError;
pub const BROADCAST: u32 = 0xFFFF_FFFF;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Frame {
bytes: [u8; Frame::MAX_LEN],
len: usize,
}
impl Frame {
pub const MAX_LEN: usize = 250;
pub const HEADER_LEN: usize = 12;
pub const OVERHEAD: usize = Self::HEADER_LEN + 2;
pub const MAX_PAYLOAD: usize = Self::MAX_LEN - Self::OVERHEAD;
pub const VERSION: u8 = 1;
pub const DEFAULT_HOP_LIMIT: u8 = 3;
const HOP_LIMIT: usize = 11;
pub fn new(src: u32, dst: u32, id: u16, payload: &[u8]) -> Result<Frame, MeshError> {
if payload.len() > Self::MAX_PAYLOAD {
return Err(MeshError::PayloadTooLong);
}
let len = Self::OVERHEAD + payload.len();
let mut bytes = [0u8; Self::MAX_LEN];
bytes[0] = Self::VERSION;
bytes[1..5].copy_from_slice(&src.to_be_bytes());
bytes[5..9].copy_from_slice(&dst.to_be_bytes());
bytes[9..11].copy_from_slice(&id.to_be_bytes());
bytes[Self::HOP_LIMIT] = Self::DEFAULT_HOP_LIMIT;
bytes[12..12 + payload.len()].copy_from_slice(payload);
let crc = Self::checksum(&bytes, len);
bytes[len - 2..len].copy_from_slice(&crc.to_be_bytes());
Ok(Frame { bytes, len })
}
pub fn broadcast(src: u32, id: u16, payload: &[u8]) -> Result<Frame, MeshError> {
Self::new(src, BROADCAST, id, payload)
}
pub fn with_hop_limit(mut self, hop_limit: u8) -> Frame {
self.bytes[Self::HOP_LIMIT] = hop_limit;
self
}
pub fn parse(bytes: &[u8]) -> Result<Frame, MeshError> {
if bytes.len() < Self::OVERHEAD {
return Err(MeshError::FrameTooShort);
}
if bytes.len() > Self::MAX_LEN {
return Err(MeshError::FrameTooLong);
}
if bytes[0] != Self::VERSION {
return Err(MeshError::UnsupportedVersion(bytes[0]));
}
let len = bytes.len();
let expected = Self::checksum(bytes, len);
let found = u16::from_be_bytes([bytes[len - 2], bytes[len - 1]]);
if expected != found {
return Err(MeshError::CrcMismatch { expected, found });
}
let mut buffer = [0u8; Self::MAX_LEN];
buffer[..len].copy_from_slice(bytes);
Ok(Frame { bytes: buffer, len })
}
fn checksum(bytes: &[u8], len: usize) -> u16 {
let mut crc = Crc16::new();
crc.update(&bytes[..Self::HOP_LIMIT]);
crc.update(&bytes[12..len - 2]);
crc.finish()
}
pub fn version(&self) -> u8 {
self.bytes[0]
}
pub fn src(&self) -> u32 {
u32::from_be_bytes([self.bytes[1], self.bytes[2], self.bytes[3], self.bytes[4]])
}
pub fn dst(&self) -> u32 {
u32::from_be_bytes([self.bytes[5], self.bytes[6], self.bytes[7], self.bytes[8]])
}
pub fn id(&self) -> u16 {
u16::from_be_bytes([self.bytes[9], self.bytes[10]])
}
pub fn hop_limit(&self) -> u8 {
self.bytes[Self::HOP_LIMIT]
}
pub fn payload(&self) -> &[u8] {
&self.bytes[12..self.len - 2]
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len]
}
pub fn is_broadcast(&self) -> bool {
self.dst() == BROADCAST
}
pub fn dedup_key(&self) -> (u32, u16) {
(self.src(), self.id())
}
pub fn relayed(&self) -> Option<Frame> {
let hop_limit = self.hop_limit();
if hop_limit == 0 {
return None;
}
let mut forwarded = *self;
forwarded.bytes[Self::HOP_LIMIT] = hop_limit - 1;
Some(forwarded)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_then_parse_round_trips() {
let frame = Frame::new(0x0102_0304, 0x0506_0708, 0x090A, b"payload").unwrap();
let parsed = Frame::parse(frame.as_bytes()).unwrap();
assert_eq!(parsed.version(), Frame::VERSION);
assert_eq!(parsed.src(), 0x0102_0304);
assert_eq!(parsed.dst(), 0x0506_0708);
assert_eq!(parsed.id(), 0x090A);
assert_eq!(parsed.hop_limit(), Frame::DEFAULT_HOP_LIMIT);
assert_eq!(parsed.payload(), b"payload");
}
#[test]
fn an_empty_payload_round_trips() {
let frame = Frame::new(1, 2, 3, b"").unwrap();
assert_eq!(frame.as_bytes().len(), Frame::OVERHEAD);
let parsed = Frame::parse(frame.as_bytes()).unwrap();
assert_eq!(parsed.payload(), b"");
}
#[test]
fn broadcast_is_addressed_to_every_node() {
let frame = Frame::broadcast(0x42, 1, b"hi").unwrap();
assert_eq!(frame.dst(), BROADCAST);
assert!(frame.is_broadcast());
assert!(!Frame::new(0x42, 0x43, 1, b"hi").unwrap().is_broadcast());
}
#[test]
fn the_largest_payload_fits_and_a_larger_one_does_not() {
let big = [0u8; Frame::MAX_PAYLOAD];
let frame = Frame::new(1, 2, 3, &big).unwrap();
assert_eq!(frame.as_bytes().len(), Frame::MAX_LEN);
let too_big = [0u8; Frame::MAX_PAYLOAD + 1];
assert_eq!(
Frame::new(1, 2, 3, &too_big),
Err(MeshError::PayloadTooLong)
);
}
#[test]
fn parse_rejects_a_short_frame() {
let short = [0u8; Frame::OVERHEAD - 1];
assert_eq!(Frame::parse(&short), Err(MeshError::FrameTooShort));
}
#[test]
fn parse_rejects_an_oversized_frame() {
let big = [0u8; Frame::MAX_LEN + 1];
assert_eq!(Frame::parse(&big), Err(MeshError::FrameTooLong));
}
#[test]
fn parse_rejects_an_unknown_version() {
let mut bytes = Frame::new(1, 2, 3, b"x").unwrap().as_bytes().to_vec();
bytes[0] = 0xFF;
assert_eq!(
Frame::parse(&bytes),
Err(MeshError::UnsupportedVersion(0xFF))
);
}
#[test]
fn parse_rejects_a_corrupt_payload() {
let mut bytes = Frame::new(1, 2, 3, b"data").unwrap().as_bytes().to_vec();
bytes[12] ^= 0xFF; assert!(matches!(
Frame::parse(&bytes),
Err(MeshError::CrcMismatch { .. })
));
}
#[test]
fn the_checksum_ignores_the_hop_limit() {
let frame = Frame::new(1, 2, 3, b"data").unwrap();
let mut bytes = frame.as_bytes().to_vec();
bytes[11] = 99;
let parsed = Frame::parse(&bytes).unwrap();
assert_eq!(parsed.hop_limit(), 99);
}
#[test]
fn with_hop_limit_leaves_the_frame_valid() {
let frame = Frame::new(1, 2, 3, b"data").unwrap().with_hop_limit(7);
assert_eq!(frame.hop_limit(), 7);
assert_eq!(Frame::parse(frame.as_bytes()).unwrap().hop_limit(), 7);
}
#[test]
fn relaying_spends_a_hop_and_keeps_everything_else() {
let frame = Frame::new(0xAA, 0xBB, 5, b"flood")
.unwrap()
.with_hop_limit(2);
let forwarded = frame.relayed().unwrap();
assert_eq!(forwarded.hop_limit(), 1);
assert_eq!(forwarded.src(), frame.src());
assert_eq!(forwarded.dst(), frame.dst());
assert_eq!(forwarded.id(), frame.id());
assert_eq!(forwarded.payload(), frame.payload());
assert!(Frame::parse(forwarded.as_bytes()).is_ok());
}
#[test]
fn a_frame_out_of_hops_is_not_relayed() {
let frame = Frame::new(1, 2, 3, b"x").unwrap().with_hop_limit(0);
assert_eq!(frame.relayed(), None);
}
#[test]
fn dedup_key_is_source_and_id() {
let frame = Frame::new(0xDEAD_BEEF, 2, 0x1234, b"x").unwrap();
assert_eq!(frame.dedup_key(), (0xDEAD_BEEF, 0x1234));
}
#[test]
fn the_largest_payload_round_trips() {
let payload = [0xCD; Frame::MAX_PAYLOAD];
let frame = Frame::new(1, 2, 3, &payload).unwrap();
assert_eq!(frame.as_bytes().len(), Frame::MAX_LEN);
let parsed = Frame::parse(frame.as_bytes()).unwrap();
assert_eq!(parsed.payload(), &payload[..]);
}
}