Initial commit

This commit is contained in:
2026-07-20 19:29:57 +02:00
commit ad7da5f7ee
23 changed files with 5271 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
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;
}
for i in 2..data.len() - 1 {
if data[i] == 0x10 && data[i + 1] == 0x03 {
return Some(i);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_then_extract_round_trips() {
let cmd = 0x34;
let args = [0x01, 0x02, 0x03];
let frame = build_frame(cmd, &args);
let decoded = extract_frame(&frame).unwrap();
assert_eq!(decoded.command, cmd);
assert_eq!(decoded.payload, vec![0x01, 0x02, 0x03]);
}
#[test]
fn too_short_is_an_error() {
let err = extract_frame(&[0x10, 0x02, 0x01]).unwrap_err();
assert!(err.0.contains("too short"));
}
}