Initial commit
This commit is contained in:
@@ -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"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user