Files
dsp-thomann/src/dsp408/protocol/frame.rs
T

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());
}
}