pub const SOF: u8 = 0xFE;
pub const TYPE_SREQ: u8 = 0x20;
pub const TYPE_SRSP: u8 = 0x60;
pub const TYPE_AREQ: u8 = 0x40;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ZnpFrame {
pub msg_type: u8,
pub subsystem: u8,
pub cmd: u8,
pub payload: Vec<u8>,
}
impl ZnpFrame {
pub fn sreq(subsystem: u8, cmd: u8, payload: Vec<u8>) -> Self {
Self {
msg_type: TYPE_SREQ,
subsystem,
cmd,
payload,
}
}
pub fn encode(&self) -> Vec<u8> {
let len = self.payload.len() as u8;
let type_sub = self.msg_type | (self.subsystem & 0x1F);
let mut buf = Vec::with_capacity(5 + self.payload.len());
buf.push(SOF);
buf.push(len);
buf.push(type_sub);
buf.push(self.cmd);
buf.extend_from_slice(&self.payload);
buf.push(fcs(&buf[1..])); buf
}
pub fn decode(data: &[u8]) -> Result<(Self, usize), &'static str> {
let data = if data.first() == Some(&SOF) {
&data[1..]
} else {
data
};
if data.len() < 4 {
return Err("ZNP frame too short");
}
let len = data[0] as usize;
if data.len() < 4 + len {
return Err("ZNP frame incomplete");
}
let type_sub = data[1];
let msg_type = type_sub & 0xE0;
let subsystem = type_sub & 0x1F;
let cmd = data[2];
let payload = data[3..3 + len].to_vec();
let received_fcs = data[3 + len];
let computed_fcs = fcs(&data[0..3 + len]);
if received_fcs != computed_fcs {
return Err("ZNP FCS mismatch");
}
let consumed = 1 + 4 + len; Ok((
Self {
msg_type,
subsystem,
cmd,
payload,
},
consumed,
))
}
}
pub fn fcs(data: &[u8]) -> u8 {
data.iter().fold(0u8, |acc, &b| acc ^ b)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn znp_fcs_xor_correctness() {
let data = [0x02u8, 0x21, 0x08, 0xAA, 0xBB];
let expected = 0x02u8 ^ 0x21 ^ 0x08 ^ 0xAA ^ 0xBB;
assert_eq!(fcs(&data), expected);
}
#[test]
fn znp_fcs_single_byte() {
assert_eq!(fcs(&[0x42]), 0x42);
}
#[test]
fn znp_fcs_empty() {
assert_eq!(fcs(&[]), 0x00);
}
#[test]
fn znp_sreq_frame_encode() {
let frame = ZnpFrame::sreq(0x21, 0x01, vec![]);
let encoded = frame.encode();
assert_eq!(encoded[0], SOF);
assert_eq!(encoded[1], 0); assert_eq!(encoded[2], TYPE_SREQ | (0x21 & 0x1F)); assert_eq!(encoded[3], 0x01); assert_eq!(encoded[4], fcs(&encoded[1..4]));
}
#[test]
fn znp_areq_frame_decode() {
let subsystem_zdo: u8 = 0x05;
let mut raw = vec![SOF, 2, TYPE_AREQ | subsystem_zdo, 0xFF, 0x01, 0x02];
raw.push(fcs(&raw[1..]));
let (frame, consumed) = ZnpFrame::decode(&raw).unwrap();
assert_eq!(frame.msg_type, TYPE_AREQ);
assert_eq!(frame.subsystem, subsystem_zdo);
assert_eq!(frame.cmd, 0xFF);
assert_eq!(frame.payload, vec![0x01, 0x02]);
assert_eq!(consumed, raw.len());
}
#[test]
fn znp_frame_roundtrip() {
let frame = ZnpFrame::sreq(0x25, 0x10, vec![0xAA, 0xBB, 0xCC]);
let encoded = frame.encode();
let (decoded, _) = ZnpFrame::decode(&encoded).unwrap();
assert_eq!(decoded.msg_type, TYPE_SREQ);
assert_eq!(decoded.subsystem, 0x05); assert_eq!(decoded.cmd, 0x10);
assert_eq!(decoded.payload, vec![0xAA, 0xBB, 0xCC]);
}
#[test]
fn znp_frame_fcs_mismatch() {
let mut frame = ZnpFrame::sreq(0x21, 0x01, vec![]).encode();
let last = frame.len() - 1;
frame[last] ^= 0xFF;
assert!(ZnpFrame::decode(&frame).is_err());
}
}