179 lines
4.1 KiB
Rust
179 lines
4.1 KiB
Rust
use crate::common::errors::FrameError;
|
|
|
|
pub fn checksum(payload: &[u8]) -> u8 {
|
|
let mut cs: u8 = 0x01;
|
|
for &b in payload {
|
|
cs ^= b;
|
|
}
|
|
cs
|
|
}
|
|
|
|
pub fn build_frame(cmd: u8, args: &[u8]) -> Vec<u8> {
|
|
let mut payload_body = Vec::with_capacity(1 + args.len());
|
|
payload_body.push(cmd);
|
|
payload_body.extend_from_slice(args);
|
|
|
|
let length = payload_body.len() as u8;
|
|
|
|
let mut inner = Vec::with_capacity(3 + payload_body.len());
|
|
inner.push(0x00);
|
|
inner.push(0x01);
|
|
inner.push(length);
|
|
inner.extend_from_slice(&payload_body);
|
|
|
|
let cs = checksum(&inner);
|
|
|
|
let mut frame = Vec::with_capacity(2 + inner.len() + 3);
|
|
frame.push(0x10);
|
|
frame.push(0x02);
|
|
frame.extend_from_slice(&inner);
|
|
frame.push(0x10);
|
|
frame.push(0x03);
|
|
frame.push(cs);
|
|
frame
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct Frame {
|
|
pub command: u8,
|
|
pub payload: Vec<u8>,
|
|
}
|
|
|
|
pub fn extract_frame(data: &[u8]) -> Result<Frame, FrameError> {
|
|
if data.len() < 8 {
|
|
return Err(FrameError::new(format!(
|
|
"Frame too short: expected at least 8 bytes, got {}",
|
|
data.len()
|
|
)));
|
|
}
|
|
|
|
if data[0] != 0x10 || data[1] != 0x02 {
|
|
return Err(FrameError::new(format!(
|
|
"Invalid frame header: {:#04x} {:#04x}",
|
|
data[0], data[1]
|
|
)));
|
|
}
|
|
|
|
if data[2] == 0x00 {
|
|
return Err(FrameError::new("Not a response frame"));
|
|
}
|
|
|
|
if data[3] != 0x00 {
|
|
return Err(FrameError::new(format!(
|
|
"Invalid device id byte: {:#04x}",
|
|
data[3]
|
|
)));
|
|
}
|
|
|
|
let footer = find_footer(data)
|
|
.ok_or_else(|| FrameError::new("Missing frame footer"))?;
|
|
|
|
let expected = checksum(&data[2..footer]);
|
|
let received = data[data.len() - 1];
|
|
|
|
if expected != received {
|
|
return Err(FrameError::new(format!(
|
|
"Invalid checksum: expected 0x{:02X}, received 0x{:02X}",
|
|
expected, received
|
|
)));
|
|
}
|
|
|
|
Ok(Frame {
|
|
command: data[5],
|
|
payload: data[6..footer].to_vec(),
|
|
})
|
|
}
|
|
|
|
fn find_footer(data: &[u8]) -> Option<usize> {
|
|
if data.len() < 2 {
|
|
return None;
|
|
}
|
|
|
|
(2..data.len() - 1)
|
|
.find(|&i| data[i] == 0x10 && data[i + 1] == 0x03)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_checksum() {
|
|
let payload = [0x00, 0x01, 0x03, 0x10, 0x20, 0x30];
|
|
|
|
// 0x01 ^ 0x00 ^ 0x01 ^ 0x03 ^ 0x10 ^ 0x20 ^ 0x30
|
|
assert_eq!(checksum(&payload), 0x03);
|
|
}
|
|
|
|
#[test]
|
|
fn test_build_frame() {
|
|
let frame = build_frame(0x05, &[0xAA, 0xBB]);
|
|
|
|
let expected_without_checksum = vec![
|
|
0x10, 0x02, // header
|
|
0x00, 0x01, 0x03, // device + version + length
|
|
0x05, 0xAA, 0xBB, // command + args
|
|
0x10, 0x03, // footer
|
|
];
|
|
|
|
assert_eq!(&frame[..frame.len() - 1], expected_without_checksum);
|
|
|
|
let expected_checksum = checksum(&frame[2..frame.len() - 3]);
|
|
assert_eq!(frame[frame.len() - 1], expected_checksum);
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_frame_invalid_header() {
|
|
let mut frame = build_frame(0x01, &[]);
|
|
frame[0] = 0x11;
|
|
|
|
let result = extract_frame(&frame);
|
|
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_frame_invalid_checksum() {
|
|
let mut frame = build_frame(0x01, &[]);
|
|
let last = frame.len() - 1;
|
|
|
|
frame[last] ^= 0xFF;
|
|
|
|
let result = extract_frame(&frame);
|
|
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_frame_not_response() {
|
|
let mut frame = build_frame(0x01, &[]);
|
|
|
|
// data[2] == 0 means request frame according to your parser
|
|
frame[2] = 0x00;
|
|
|
|
let result = extract_frame(&frame);
|
|
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_frame_invalid_device_id() {
|
|
let mut frame = build_frame(0x01, &[]);
|
|
|
|
frame[3] = 0xFF;
|
|
|
|
let result = extract_frame(&frame);
|
|
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_extract_frame_too_short() {
|
|
let data = vec![0x10, 0x02, 0x01];
|
|
|
|
let result = extract_frame(&data);
|
|
|
|
assert!(result.is_err());
|
|
}
|
|
}
|