use crate::protocol::crc::crc16_xmodem;
use byteorder::{LittleEndian, WriteBytesExt};
pub const FRAME_MAGIC: u32 = 0xDEADBEEF;
pub const FWPKG_MAGIC: u32 = 0xEFBEADDF;
pub const ACK_SUCCESS: u8 = 0x5A;
pub const ACK_FAILURE: u8 = 0x00;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum CommandType {
Handshake = 0xF0,
Ack = 0xE1,
DownloadFlashImage = 0xD2,
DownloadOtpEfuse = 0xC3,
UploadData = 0xB4,
ReadOtpEfuse = 0xA5,
FlashLock = 0x96,
Reset = 0x87,
DownloadFactoryBin = 0x78,
DownloadVersion = 0x69,
SetBaudRate = 0x5A,
DownloadNv = 0x4B,
SwitchDfu = 0x1E,
}
impl CommandType {
pub fn reversed(self) -> u8 {
!(self as u8)
}
pub fn swapped(self) -> u8 {
let cmd = self as u8;
cmd.rotate_right(4)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum ImageType {
Loader = 0,
Normal = 1,
KvNv = 2,
Efuse = 3,
Otp = 4,
FlashBoot = 5,
Factory = 6,
Version = 7,
SecurityA = 8,
SecurityB = 9,
SecurityC = 10,
ProtocolA = 11,
AppsA = 12,
RadioConfig = 13,
Rom = 14,
Emmc = 15,
Database = 16,
FlashBoot3892 = 17,
}
impl From<u32> for ImageType {
fn from(value: u32) -> Self {
match value {
0 => Self::Loader,
2 => Self::KvNv,
3 => Self::Efuse,
4 => Self::Otp,
5 => Self::FlashBoot,
6 => Self::Factory,
7 => Self::Version,
_ => Self::Normal,
}
}
}
#[derive(Debug)]
pub struct SebootFrame {
frame_type: CommandType,
data: Vec<u8>,
}
impl SebootFrame {
pub fn new(frame_type: CommandType) -> Self {
Self {
frame_type,
data: Vec::new(),
}
}
#[allow(clippy::unwrap_used)] pub fn handshake(baud_rate: u32) -> Self {
let mut frame = Self::new(CommandType::Handshake);
frame.data.write_u32::<LittleEndian>(baud_rate).unwrap();
frame.data.push(8); frame.data.push(1); frame.data.push(0); frame.data.push(0); frame
}
#[allow(clippy::unwrap_used)] pub fn set_baud_rate(baud_rate: u32) -> Self {
let mut frame = Self::new(CommandType::SetBaudRate);
frame.data.write_u32::<LittleEndian>(baud_rate).unwrap();
frame.data.write_u32::<LittleEndian>(0x0108).unwrap(); frame
}
#[allow(clippy::unwrap_used)] pub fn download_flash_image(addr: u32, len: u32, erase_size: u32, is_rom: bool) -> Self {
let mut frame = Self::new(CommandType::DownloadFlashImage);
frame.data.write_u32::<LittleEndian>(addr).unwrap();
frame.data.write_u32::<LittleEndian>(len).unwrap();
frame.data.write_u32::<LittleEndian>(erase_size).unwrap();
let formal = u8::from(is_rom);
frame.data.push(formal);
frame.data.push(!formal);
frame
}
#[allow(clippy::unwrap_used)] pub fn download_factory_bin(addr: u32, len: u32, erase_size: u32) -> Self {
let mut frame = Self::new(CommandType::DownloadFactoryBin);
frame.data.write_u32::<LittleEndian>(addr).unwrap();
frame.data.write_u32::<LittleEndian>(len).unwrap();
frame.data.write_u32::<LittleEndian>(erase_size).unwrap();
frame.data.push(0x00); frame.data.push(0xFF); frame
}
#[allow(clippy::unwrap_used)] pub fn download_nv(addr: u32, len: u32, erase_size: u32, erase_all: bool) -> Self {
let mut frame = Self::new(CommandType::DownloadNv);
frame.data.write_u32::<LittleEndian>(addr).unwrap();
frame.data.write_u32::<LittleEndian>(len).unwrap();
frame.data.write_u32::<LittleEndian>(erase_size).unwrap();
frame.data.write_u16::<LittleEndian>(0).unwrap(); frame
.data
.write_u16::<LittleEndian>(u16::from(erase_all))
.unwrap(); frame
}
#[allow(clippy::unwrap_used)] pub fn download_otp_efuse(len: u32) -> Self {
let mut frame = Self::new(CommandType::DownloadOtpEfuse);
frame.data.write_u32::<LittleEndian>(len).unwrap();
frame
}
#[allow(clippy::unwrap_used)] pub fn download_version(len: u32) -> Self {
let mut frame = Self::new(CommandType::DownloadVersion);
frame.data.write_u32::<LittleEndian>(len).unwrap();
frame
}
#[allow(clippy::unwrap_used)] pub fn upload_data(addr: u32, len: u32) -> Self {
let mut frame = Self::new(CommandType::UploadData);
frame.data.write_u32::<LittleEndian>(len).unwrap();
frame.data.write_u32::<LittleEndian>(addr).unwrap();
frame
}
#[allow(clippy::unwrap_used)] pub fn read_otp_efuse(start_bit: u16, bit_width: u16) -> Self {
let mut frame = Self::new(CommandType::ReadOtpEfuse);
frame.data.write_u16::<LittleEndian>(start_bit).unwrap();
frame.data.write_u16::<LittleEndian>(bit_width).unwrap();
frame
}
#[allow(clippy::unwrap_used)] pub fn flash_lock(param: u16) -> Self {
let mut frame = Self::new(CommandType::FlashLock);
frame.data.write_u16::<LittleEndian>(param).unwrap();
frame
}
#[allow(clippy::unwrap_used)] pub fn reset() -> Self {
let mut frame = Self::new(CommandType::Reset);
frame.data.write_u16::<LittleEndian>(0).unwrap();
frame
}
#[allow(clippy::unwrap_used)] pub fn switch_dfu() -> Self {
let mut frame = Self::new(CommandType::SwitchDfu);
frame.data.write_u16::<LittleEndian>(0).unwrap();
frame
}
pub fn erase_all() -> Self {
Self::download_flash_image(0, 0, 0xFFFFFFFF, false)
}
#[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.frame_type as u8);
buf.push(self.frame_type.reversed());
buf.extend_from_slice(&self.data);
let crc = crc16_xmodem(&buf);
buf.write_u16::<LittleEndian>(crc).unwrap();
buf
}
pub fn command_type(&self) -> CommandType {
self.frame_type
}
}
#[derive(Debug)]
pub struct SebootAck {
pub frame_type: u8,
pub result: u8,
pub error_code: u8,
}
impl SebootAck {
pub const MIN_LEN: usize = 12;
pub const HANDSHAKE_ACK: [u8; 12] = [
0xEF, 0xBE, 0xAD, 0xDE, 0x0C, 0x00, 0xE1, 0x1E, 0x5A, 0x00, 0x00, 0x00, ];
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < Self::MIN_LEN {
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() < Self::MIN_LEN {
return None;
}
let frame_type = frame[6];
let result = frame[8];
let error_code = frame[9];
Some(Self {
frame_type,
result,
error_code,
})
}
pub fn is_success(&self) -> bool {
self.result == ACK_SUCCESS
}
pub fn is_handshake_ack(&self) -> bool {
self.frame_type == CommandType::Ack as u8 && self.is_success()
}
}
pub fn contains_handshake_ack(data: &[u8]) -> bool {
data.windows(10).any(|w| {
w[0] == 0xEF
&& w[1] == 0xBE
&& w[2] == 0xAD
&& w[3] == 0xDE
&& w[4] == 0x0C
&& w[5] == 0x00
&& w[6] == 0xE1
&& w[7] == 0x1E
&& w[8] == 0x5A
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_command_type_reversed() {
assert_eq!(CommandType::Handshake.reversed(), 0x0F);
assert_eq!(CommandType::DownloadFlashImage.reversed(), 0x2D);
assert_eq!(CommandType::Reset.reversed(), 0x78);
}
#[test]
fn test_handshake_frame_length() {
let frame = SebootFrame::handshake(115200);
let data = frame.build();
assert_eq!(data.len(), 18);
assert_eq!(&data[0..4], &[0xEF, 0xBE, 0xAD, 0xDE]);
assert_eq!(&data[4..6], &[0x12, 0x00]); assert_eq!(data[6], 0xF0);
assert_eq!(data[7], 0x0F);
}
#[test]
fn test_download_flash_image_frame() {
let frame = SebootFrame::download_flash_image(0x00800000, 0x1000, 0x1000, false);
let data = frame.build();
assert_eq!(data.len(), 24);
assert_eq!(data[6], 0xD2);
assert_eq!(data[7], 0x2D);
}
#[test]
fn test_erase_all_frame() {
let frame = SebootFrame::erase_all();
let data = frame.build();
assert_eq!(&data[16..20], &[0xFF, 0xFF, 0xFF, 0xFF]);
}
#[test]
fn test_reset_frame() {
let frame = SebootFrame::reset();
let data = frame.build();
assert_eq!(data.len(), 12);
assert_eq!(data[6], 0x87);
assert_eq!(data[7], 0x78);
}
#[test]
fn test_contains_handshake_ack() {
let mut data = vec![0x00, 0x00];
data.extend_from_slice(&SebootAck::HANDSHAKE_ACK[..10]);
data.extend_from_slice(&[0x00, 0x00]);
assert!(contains_handshake_ack(&data));
}
}