use {
crate::protocol::crc::crc16_xmodem,
byteorder::{LittleEndian, WriteBytesExt},
};
pub const FRAME_MAGIC: u32 = 0xDEADBEEF;
pub const DEFAULT_BAUD: u32 = 115200;
pub const HIGH_BAUD: u32 = 921600;
pub const HANDSHAKE_ACK: [u8; 10] = [
0xEF, 0xBE, 0xAD, 0xDE, 0x0C, 0x00, 0xE1, 0x1E, 0x5A, 0x00, ];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Command {
Handshake = 0xF0,
SetBaudRate = 0x5A,
Download = 0xD2,
Reset = 0x87,
}
impl Command {
pub fn swapped(self) -> u8 {
let cmd = self as u8;
cmd.rotate_right(4)
}
}
#[derive(Debug)]
pub struct CommandFrame {
cmd: Command,
data: Vec<u8>,
}
impl CommandFrame {
pub fn new(cmd: Command) -> Self {
Self {
cmd,
data: Vec::new(),
}
}
#[allow(clippy::unwrap_used)] pub fn handshake(baud: u32) -> Self {
let mut frame = Self::new(Command::Handshake);
frame
.data
.write_u32::<LittleEndian>(baud)
.unwrap();
frame
.data
.write_u32::<LittleEndian>(0x0108)
.unwrap(); frame
}
#[allow(clippy::unwrap_used)] pub fn set_baud_rate(baud: u32) -> Self {
let mut frame = Self::new(Command::SetBaudRate);
frame
.data
.write_u32::<LittleEndian>(baud)
.unwrap();
frame
.data
.write_u32::<LittleEndian>(0x0108)
.unwrap();
frame
}
#[allow(clippy::unwrap_used)] pub fn download(addr: u32, len: u32, erase_size: u32) -> Self {
let mut frame = Self::new(Command::Download);
frame
.data
.write_u32::<LittleEndian>(addr)
.unwrap();
frame
.data
.write_u32::<LittleEndian>(len)
.unwrap();
frame
.data
.write_u32::<LittleEndian>(erase_size)
.unwrap();
frame
.data
.extend_from_slice(&[0x00, 0xFF]); frame
}
pub fn erase_all() -> Self {
Self::download(0, 0, 0xFFFFFFFF)
}
pub fn reset() -> Self {
let mut frame = Self::new(Command::Reset);
frame
.data
.extend_from_slice(&[0x00, 0x00]);
frame
}
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::unwrap_used)] pub fn build(&self) -> Vec<u8> {
let total_len = 10
+ self
.data
.len();
let mut buf = Vec::with_capacity(total_len);
buf.write_u32::<LittleEndian>(FRAME_MAGIC)
.unwrap();
buf.write_u16::<LittleEndian>(total_len as u16)
.unwrap();
buf.push(self.cmd as u8);
buf.push(
self.cmd
.swapped(),
);
buf.extend_from_slice(&self.data);
let crc = crc16_xmodem(&buf);
buf.write_u16::<LittleEndian>(crc)
.unwrap();
buf
}
pub fn command(&self) -> Command {
self.cmd
}
}
#[derive(Debug)]
pub struct ResponseFrame {
pub cmd: u8,
pub scmd: u8,
pub data: Vec<u8>,
}
impl ResponseFrame {
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 10 {
return None;
}
let magic_pos = data
.windows(4)
.position(|w| u32::from_le_bytes([w[0], w[1], w[2], w[3]]) == FRAME_MAGIC)?;
let frame = &data[magic_pos..];
if frame.len() < 10 {
return None;
}
let len = u16::from_le_bytes([frame[4], frame[5]]) as usize;
if frame.len() < len {
return None;
}
let cmd = frame[6];
let scmd = frame[7];
let data = frame[8..len - 2].to_vec();
Some(Self { cmd, scmd, data })
}
pub fn is_handshake_ack(&self) -> bool {
self.cmd == 0xE1
&& !self
.data
.is_empty()
&& self.data[0] == 0x5A
}
pub fn is_ack(&self) -> bool {
!self
.data
.is_empty()
&& self.data[0] == 0x5A
}
}
pub fn contains_handshake_ack(data: &[u8]) -> bool {
data.windows(HANDSHAKE_ACK.len())
.any(|w| w == HANDSHAKE_ACK)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_command_swapped() {
assert_eq!(Command::Handshake.swapped(), 0x0F);
assert_eq!(Command::SetBaudRate.swapped(), 0xA5);
assert_eq!(Command::Download.swapped(), 0x2D);
assert_eq!(Command::Reset.swapped(), 0x78);
}
#[test]
fn test_handshake_frame() {
let frame = CommandFrame::handshake(115200);
let data = frame.build();
assert_eq!(&data[0..4], &[0xEF, 0xBE, 0xAD, 0xDE]);
assert_eq!(data[6], 0xF0);
assert_eq!(data[7], 0x0F);
}
#[test]
fn test_download_frame() {
let frame = CommandFrame::download(0x00800000, 0x1000, 0x1000);
let data = frame.build();
assert_eq!(&data[0..4], &[0xEF, 0xBE, 0xAD, 0xDE]);
assert_eq!(data[6], 0xD2);
assert_eq!(data[7], 0x2D);
}
#[test]
fn test_erase_all_frame() {
let frame = CommandFrame::erase_all();
let data = frame.build();
assert_eq!(&data[16..20], &[0xFF, 0xFF, 0xFF, 0xFF]);
}
#[test]
fn test_contains_handshake_ack() {
assert!(contains_handshake_ack(&HANDSHAKE_ACK));
let mut data = vec![0x00, 0x00];
data.extend_from_slice(&HANDSHAKE_ACK);
data.extend_from_slice(&[0x00, 0x00]);
assert!(contains_handshake_ack(&data));
assert!(!contains_handshake_ack(&[0x00; 20]));
}
#[test]
fn test_response_frame_parse_handshake_ack() {
let mut buf = Vec::new();
buf.extend_from_slice(&FRAME_MAGIC.to_le_bytes());
buf.extend_from_slice(&12u16.to_le_bytes()); buf.push(0xE1); buf.push(0x1E); buf.push(0x5A); buf.push(0x00); let crc = crate::protocol::crc::crc16_xmodem(&buf);
buf.extend_from_slice(&crc.to_le_bytes());
let resp = ResponseFrame::parse(&buf);
assert!(resp.is_some());
let resp = resp.unwrap();
assert!(resp.is_handshake_ack());
assert!(resp.is_ack());
assert_eq!(resp.cmd, 0xE1);
assert_eq!(resp.scmd, 0x1E);
}
#[test]
fn test_response_frame_parse_failure() {
let mut buf = Vec::new();
buf.extend_from_slice(&FRAME_MAGIC.to_le_bytes());
buf.extend_from_slice(&12u16.to_le_bytes());
buf.push(0xE1);
buf.push(0x1E);
buf.push(0x00); buf.push(0x01); let crc = crate::protocol::crc::crc16_xmodem(&buf);
buf.extend_from_slice(&crc.to_le_bytes());
let resp = ResponseFrame::parse(&buf).unwrap();
assert!(!resp.is_ack());
assert!(!resp.is_handshake_ack());
}
#[test]
fn test_response_frame_parse_too_short() {
assert!(ResponseFrame::parse(&[0; 5]).is_none());
}
#[test]
fn test_response_frame_parse_no_magic() {
let data = vec![0x00; 20];
assert!(ResponseFrame::parse(&data).is_none());
}
#[test]
fn test_response_frame_parse_with_prefix() {
let mut buf = vec![0xFF; 3];
buf.extend_from_slice(&FRAME_MAGIC.to_le_bytes());
buf.extend_from_slice(&12u16.to_le_bytes());
buf.push(0xE1);
buf.push(0x1E);
buf.push(0x5A);
buf.push(0x00);
let crc = crate::protocol::crc::crc16_xmodem(&buf[3..]);
buf.extend_from_slice(&crc.to_le_bytes());
let resp = ResponseFrame::parse(&buf);
assert!(resp.is_some());
}
#[test]
fn test_command_frame_command_getter() {
let frame = CommandFrame::handshake(115200);
assert_eq!(frame.command(), Command::Handshake);
let frame = CommandFrame::reset();
assert_eq!(frame.command(), Command::Reset);
let frame = CommandFrame::set_baud_rate(921600);
assert_eq!(frame.command(), Command::SetBaudRate);
let frame = CommandFrame::download(0, 0, 0);
assert_eq!(frame.command(), Command::Download);
}
#[test]
fn test_reset_frame_structure() {
let frame = CommandFrame::reset();
let data = frame.build();
assert_eq!(data[6], Command::Reset as u8);
assert_eq!(data[7], Command::Reset.swapped());
assert_eq!(data.len(), 12);
}
#[test]
fn test_frame_magic_bytes() {
let frame = CommandFrame::handshake(115200);
let data = frame.build();
assert_eq!(&data[0..4], &[0xEF, 0xBE, 0xAD, 0xDE]);
}
#[test]
fn test_frame_length_field_matches_actual() {
let frame = CommandFrame::handshake(115200);
let data = frame.build();
let len_field = u16::from_le_bytes([data[4], data[5]]) as usize;
assert_eq!(len_field, data.len());
}
#[test]
fn test_constants() {
assert_eq!(FRAME_MAGIC, 0xDEADBEEF);
assert_eq!(DEFAULT_BAUD, 115200);
assert_eq!(HIGH_BAUD, 921600);
assert_eq!(HANDSHAKE_ACK.len(), 10);
}
}