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 { 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, } pub fn extract_frame(data: &[u8]) -> Result { 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 { 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()); } }