pub const PROTOCOL_VERSION: u8 = 1;
pub const DEFAULT_NODE_ID: u8 = 1;
pub const FUNCTIONAL_NODE: u8 = 0;
pub const UID_LEN: usize = 12;
pub const UID_PREFIX_LEN: usize = 7;
pub const FRAME_DATA_MAX: usize = 8;
pub const PAGE_BYTES: usize = 256;
pub const SESSION_TIMEOUT_MS: u32 = 5_000;
const DIR_REQUEST: u16 = 0b10;
const DIR_RESPONSE: u16 = 0b11;
pub const SUB_CONTROL: u16 = 0;
pub const SUB_DATA: u16 = 1;
pub const fn req_id(node: u8, sub: u16) -> u16 {
(DIR_REQUEST << 9) | ((node as u16 & 0x7F) << 2) | (sub & 0b11)
}
pub const fn resp_id(node: u8, sub: u16) -> u16 {
(DIR_RESPONSE << 9) | ((node as u16 & 0x7F) << 2) | (sub & 0b11)
}
pub const fn is_request(id: u16) -> bool {
(id >> 9) == DIR_REQUEST
}
pub const fn is_response(id: u16) -> bool {
(id >> 9) == DIR_RESPONSE
}
pub const fn node_of(id: u16) -> u8 {
((id >> 2) & 0x7F) as u8
}
pub const fn sub_of(id: u16) -> u16 {
id & 0b11
}
pub mod cmd {
pub const PING: u8 = 0x01;
pub const ENTER_UPDATE: u8 = 0x02;
pub const SESSION_OPEN: u8 = 0x10;
pub const SESSION_CLOSE: u8 = 0x11;
pub const GET_INFO: u8 = 0x12;
pub const BEGIN: u8 = 0x20;
pub const ABORT: u8 = 0x21;
pub const FINISH: u8 = 0x30;
pub const QUERY: u8 = 0x31;
}
pub const STATUS_OK: u8 = 0x00;
pub const STATUS_ERROR: u8 = 0x01;
pub mod err {
pub const NO_SESSION: u8 = 1;
pub const BUSY: u8 = 2;
pub const OFFSET: u8 = 3;
pub const TOO_LARGE: u8 = 4;
pub const FLASH: u8 = 5;
pub const CRC: u8 = 6;
pub const TIMEOUT: u8 = 8;
}
pub mod state {
pub const APP: u8 = 0;
pub const BOOTLOADER_IDLE: u8 = 1;
pub const BOOTLOADER_RECEIVING: u8 = 2;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Request<'a> {
Ping,
EnterUpdate {
uid_prefix: &'a [u8],
},
SessionOpen {
nonce: u32,
},
SessionClose,
GetInfo,
Begin {
len: u32,
crc32: u32,
},
Abort,
Finish {
len: u32,
crc32: u32,
},
Query,
Unknown {
cmd: u8,
},
}
pub fn parse_control(data: &[u8]) -> Option<Request<'_>> {
let (&opcode, rest) = data.split_first()?;
let le4 = |b: &[u8]| u32::from_le_bytes([b[0], b[1], b[2], b[3]]);
Some(match opcode {
cmd::PING => Request::Ping,
cmd::ENTER_UPDATE if rest.len() == UID_PREFIX_LEN => {
Request::EnterUpdate { uid_prefix: rest }
}
cmd::SESSION_OPEN if rest.len() == 4 => Request::SessionOpen { nonce: le4(rest) },
cmd::SESSION_CLOSE if rest.is_empty() => Request::SessionClose,
cmd::GET_INFO if rest.is_empty() => Request::GetInfo,
cmd::BEGIN if rest.len() == 8 => Request::Begin {
len: le4(rest),
crc32: le4(&rest[4..]),
},
cmd::ABORT if rest.is_empty() => Request::Abort,
cmd::FINISH if rest.len() == 8 => Request::Finish {
len: le4(rest),
crc32: le4(&rest[4..]),
},
cmd::QUERY if rest.is_empty() => Request::Query,
cmd => Request::Unknown { cmd },
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Response {
pub cmd: u8,
pub status: u8,
pub err: u8,
pub next_offset: u32,
}
impl Response {
pub const LEN: usize = 8;
pub const fn ok(cmd: u8, next_offset: u32) -> Self {
Self {
cmd,
status: STATUS_OK,
err: 0,
next_offset,
}
}
pub const fn failure(cmd: u8, err: u8, next_offset: u32) -> Self {
Self {
cmd,
status: STATUS_ERROR,
err,
next_offset,
}
}
pub fn encode(&self) -> [u8; Self::LEN] {
let offset = self.next_offset.to_le_bytes();
[
0x80 | self.cmd,
self.cmd,
self.status,
self.err,
offset[0],
offset[1],
offset[2],
offset[3],
]
}
pub fn decode(data: &[u8]) -> Option<Self> {
let bytes: &[u8; Self::LEN] = data.try_into().ok()?;
if bytes[0] & 0x80 == 0 {
return None;
}
Some(Self {
cmd: bytes[1],
status: bytes[2],
err: bytes[3],
next_offset: u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
})
}
}
pub const INFO_LEN: usize = 18;
pub const fn info_frame_count() -> usize {
INFO_LEN.div_ceil(FRAME_DATA_MAX)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Info {
pub protocol_version: u8,
pub state: u8,
pub uid: [u8; UID_LEN],
pub chip_id: u32,
}
impl Info {
pub fn encode(&self) -> [u8; INFO_LEN] {
let mut out = [0u8; INFO_LEN];
out[0] = self.protocol_version;
out[1] = self.state;
out[2..2 + UID_LEN].copy_from_slice(&self.uid);
out[2 + UID_LEN..].copy_from_slice(&self.chip_id.to_le_bytes());
out
}
pub fn decode(data: &[u8]) -> Option<Self> {
let data: &[u8; INFO_LEN] = data.try_into().ok()?;
let mut uid = [0u8; UID_LEN];
uid.copy_from_slice(&data[2..2 + UID_LEN]);
Some(Self {
protocol_version: data[0],
state: data[1],
uid,
chip_id: u32::from_le_bytes(data[2 + UID_LEN..].try_into().unwrap()),
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct Crc32(u32);
impl Crc32 {
pub const fn new() -> Self {
Self(0xFFFF_FFFF)
}
pub fn update(&mut self, mut data: &[u8]) {
while let Some((&byte, rest)) = data.split_first() {
data = rest;
self.0 ^= byte as u32;
for _ in 0..8 {
let mask = (self.0 & 1).wrapping_neg();
self.0 = (self.0 >> 1) ^ (0xEDB8_8320 & mask);
}
}
}
pub const fn finalize(self) -> u32 {
!self.0
}
}
impl Default for Crc32 {
fn default() -> Self {
Self::new()
}
}
pub fn crc32(data: &[u8]) -> u32 {
let mut crc = Crc32::new();
crc.update(data);
crc.finalize()
}
pub const fn filter_words(code: u16, mask: u16) -> (u32, u32) {
let shift = 21;
(
(code as u32 & 0x7FF) << shift,
((mask as u32 & 0x7FF) << shift) | 0b110,
)
}
pub const fn node_filters(node: u8) -> ((u16, u16), (u16, u16)) {
(
(req_id(node, SUB_CONTROL), 0x7FC),
(req_id(FUNCTIONAL_NODE, SUB_CONTROL), 0x7FF),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crc32_reference_vectors() {
assert_eq!(crc32(b"123456789"), 0xCBF4_3926);
assert_eq!(crc32(b""), 0x0000_0000);
assert_eq!(crc32(&[0xFFu8; 4]), 0xFFFF_FFFF);
let image = b"the quick brown fox jumps over the lazy dog";
let mut crc = Crc32::new();
for chunk in image.chunks(7) {
crc.update(chunk);
}
assert_eq!(crc.finalize(), crc32(image));
}
#[test]
fn id_layout() {
assert_eq!(req_id(FUNCTIONAL_NODE, SUB_CONTROL), 0x400);
assert_eq!(req_id(1, SUB_CONTROL), 0x404);
assert_eq!(req_id(1, SUB_DATA), 0x405);
assert_eq!(req_id(0x7F, SUB_DATA), 0x5FD);
assert_eq!(resp_id(1, SUB_CONTROL), 0x604);
assert_eq!(resp_id(0x7F, SUB_DATA), 0x7FD);
for id in [0x400u16, 0x404, 0x405, 0x5FD] {
assert!(is_request(id), "{id:#x} is not a request");
assert!(!is_response(id));
}
for id in [0x600u16, 0x604, 0x7FD] {
assert!(is_response(id), "{id:#x} is not a response");
assert!(!is_request(id));
}
for node in [FUNCTIONAL_NODE, 1, 42, 0x7F] {
for sub in [SUB_CONTROL, SUB_DATA] {
let id = req_id(node, sub);
assert_eq!(node_of(id), node);
assert_eq!(sub_of(id), sub);
}
}
}
#[test]
fn response_round_trip() {
let ok = Response::ok(cmd::QUERY, 256);
let bytes = ok.encode();
assert_eq!(bytes[0], 0x80 | cmd::QUERY);
assert_eq!(Response::decode(&bytes), Some(ok));
let bad = Response::failure(cmd::BEGIN, err::TOO_LARGE, 0);
assert_eq!(Response::decode(&bad.encode()), Some(bad));
assert_eq!(Response::decode(&[cmd::PING, 0, 0, 0, 0, 0, 0, 0]), None);
assert_eq!(Response::decode(&[0x81, 0x01]), None);
}
#[test]
fn control_frames() {
assert_eq!(parse_control(&[cmd::PING]), Some(Request::Ping));
assert_eq!(
parse_control(&[cmd::SESSION_OPEN, 0x78, 0x56, 0x34, 0x12]),
Some(Request::SessionOpen { nonce: 0x1234_5678 })
);
assert_eq!(parse_control(&[cmd::GET_INFO]), Some(Request::GetInfo));
assert_eq!(
parse_control(&[cmd::SESSION_CLOSE]),
Some(Request::SessionClose)
);
assert_eq!(parse_control(&[cmd::ABORT]), Some(Request::Abort));
assert_eq!(parse_control(&[cmd::QUERY]), Some(Request::Query));
assert_eq!(
parse_control(&[cmd::BEGIN, 0x00, 0xC0, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44]),
Some(Request::Begin {
len: 0xC000,
crc32: 0x4433_2211
})
);
let mut enter = [0u8; 1 + UID_PREFIX_LEN];
enter[0] = cmd::ENTER_UPDATE;
enter[1..].copy_from_slice(&[1, 2, 3, 4, 5, 6, 7]);
assert_eq!(
parse_control(&enter),
Some(Request::EnterUpdate {
uid_prefix: &[1, 2, 3, 4, 5, 6, 7]
})
);
assert_eq!(
parse_control(&[cmd::BEGIN, 0, 0]),
Some(Request::Unknown { cmd: cmd::BEGIN })
);
assert_eq!(parse_control(&[]), None);
assert_eq!(parse_control(&[0x77]), Some(Request::Unknown { cmd: 0x77 }));
}
#[test]
fn info_payload() {
let info = Info {
protocol_version: PROTOCOL_VERSION,
state: state::BOOTLOADER_RECEIVING,
uid: [0xA0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
chip_id: 0x3054_0001,
};
let bytes = info.encode();
assert_eq!(bytes.len(), INFO_LEN);
assert_eq!(Info::decode(&bytes), Some(info));
assert_eq!(Info::decode(&bytes[..17]), None);
assert_eq!(info_frame_count(), 3);
assert_eq!(2 * FRAME_DATA_MAX + 2, INFO_LEN);
}
#[test]
fn filter_encoding() {
let ((own, own_mask), (functional, functional_mask)) = node_filters(5);
assert_eq!(own, req_id(5, SUB_CONTROL));
assert_eq!(own_mask, 0x7FC);
assert_eq!(functional, 0x400);
assert_eq!(functional_mask, 0x7FF);
let (value, mask) = filter_words(own, own_mask);
assert_eq!(value, (0x414u32) << 21);
assert_eq!(mask & 0b110, 0b110);
assert_eq!(mask & (0x7FF << 21), (0x7FCu32) << 21);
let matches = |rx: u16| filter_words(rx, 0x7FF).0 & mask == value;
assert!(matches(req_id(5, SUB_CONTROL)));
assert!(matches(req_id(5, SUB_DATA)));
assert!(!matches(req_id(6, SUB_CONTROL)));
assert!(!matches(req_id(4, SUB_DATA)));
assert!(!matches(req_id(FUNCTIONAL_NODE, SUB_CONTROL)));
}
}