use core::convert::TryFrom;
use serde::{Deserialize, Serialize};
use super::property::Property;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(tag = "cmd")]
pub enum Command {
EraseFlashAll,
EraseFlash {
address: usize,
length: usize,
},
ReadMemory {
address: usize,
length: usize,
},
WriteMemory {
address: usize,
data: Vec<u8>,
},
WriteMemoryWords {
address: usize,
words: Vec<u32>,
},
FillMemory,
ConfigureMemory {
address: usize,
},
FlashSecurityDisable,
GetProperty(Property),
ReceiveSbFile {
data: Vec<u8>,
},
Call,
Reset,
FlashReadResource,
Keystore(KeystoreOperation),
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, enum_iterator::Sequence, Ord, PartialEq, PartialOrd)]
pub enum CommandTag {
EraseFlashAll = 0x01,
EraseFlash = 0x02,
ReadMemory = 0x03,
WriteMemory = 0x04,
FillMemory = 0x05,
FlashSecurityDisable = 0x06,
GetProperty = 0x07,
ReceiveSbFile = 0x08,
Execute = 0x09,
Call = 0x0A,
Reset = 0x0B,
SetProperty = 0x0C,
EraseFlashAllUnlock = 0x0D,
FlashProgramOnce = 0x0E,
FlashReadOnce = 0x0F,
FlashReadResource = 0x10,
ConfigureMemory = 0x11,
ReliableUpdate = 0x12,
GenerateKeyBlob = 0x13,
Keystore = 0x15,
ConfigureI2c = 0xC1,
ConfigureSpi = 0xC2,
ConfigureCan = 0xC3,
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum DataPhase {
None,
CommandData(Vec<u8>),
ResponseData,
}
impl DataPhase {
pub fn has_command_data(&self) -> bool {
matches!(self, DataPhase::CommandData(_))
}
}
impl Command {
pub fn data_phase(&self) -> DataPhase {
use CommandTag as Tag;
match (self, self.tag()) {
(_, Tag::ReadMemory) => DataPhase::ResponseData,
(_, Tag::EraseFlash) => DataPhase::None,
(_, Tag::EraseFlashAll) => DataPhase::None,
(_, Tag::GetProperty) => DataPhase::None,
(_, Tag::Reset) => DataPhase::None,
(_, Tag::ConfigureMemory) => DataPhase::None,
(Command::WriteMemory { address: _, data }, _) => DataPhase::CommandData(data.clone()),
(Command::WriteMemoryWords { address: _, words }, _) => {
use std::io::Write;
let mut bytes = Vec::with_capacity(words.len() * 4);
let cursor = &mut bytes;
for word in words.iter() {
cursor.write_all(&word.to_le_bytes()).unwrap();
}
DataPhase::CommandData(bytes)
}
(Command::ReceiveSbFile { data }, _) => DataPhase::CommandData(data.clone()),
(Command::Keystore(KeystoreOperation::Enroll), _) => DataPhase::None,
(Command::Keystore(KeystoreOperation::ReadKeystore), _) => DataPhase::ResponseData,
(Command::Keystore(KeystoreOperation::SetKey { key: _, data }), _) => {
DataPhase::CommandData(data.clone())
}
(Command::Keystore(KeystoreOperation::GenerateKey { key: _, len: _ }), _) => {
DataPhase::None
}
(Command::Keystore(KeystoreOperation::WriteNonVolatile), _) => DataPhase::None,
(Command::Keystore(KeystoreOperation::ReadNonVolatile), _) => DataPhase::None,
_ => todo!(),
}
}
pub fn parameters(&self) -> Vec<u32> {
use Command::*;
match self.clone() {
GetProperty(property) => {
vec![property as u8 as u32, 0]
}
ReadMemory { address, length } => {
vec![address as u32, length as u32]
}
EraseFlash { address, length } => {
vec![address as u32, length as u32]
}
EraseFlashAll => {
vec![]
}
WriteMemory { address, data } => {
vec![address as u32, data.len() as u32, 0]
}
WriteMemoryWords { address, words } => {
vec![address as u32, (words.len() * 4) as u32, 0]
}
ConfigureMemory { address } => {
vec![0, address as u32]
}
ReceiveSbFile { data } => {
vec![data.len() as _]
}
Reset => {
vec![]
}
Keystore(operation) => {
use KeystoreOperation::*;
match operation.clone() {
Enroll => {
vec![u32::from(&operation)]
}
ReadKeystore => {
vec![u32::from(&operation)]
}
SetKey { key, data } => {
vec![u32::from(&operation), key as u32, data.len() as u32]
}
GenerateKey { key, len } => {
vec![u32::from(&operation), key as u32, len]
}
WriteNonVolatile => {
vec![u32::from(&operation), 0]
}
ReadNonVolatile => {
vec![u32::from(&operation), 0]
}
_ => todo!(),
}
}
_ => todo!(),
}
}
pub fn tag(&self) -> CommandTag {
use Command::*;
use CommandTag as Tag;
match *self {
EraseFlashAll => Tag::EraseFlashAll,
EraseFlash {
address: _,
length: _,
} => Tag::EraseFlash,
ReadMemory {
address: _,
length: _,
} => Tag::ReadMemory,
WriteMemory {
address: _,
data: _,
} => Tag::WriteMemory,
WriteMemoryWords {
address: _,
words: _,
} => Tag::WriteMemory,
FillMemory => Tag::FillMemory,
FlashSecurityDisable => Tag::FlashSecurityDisable,
GetProperty(_) => Tag::GetProperty,
ReceiveSbFile { .. } => Tag::ReceiveSbFile,
Call => Tag::Call,
Reset => Tag::Reset,
FlashReadResource => Tag::FlashReadResource,
ConfigureMemory { address: _ } => Tag::ConfigureMemory,
Keystore(_) => Tag::Keystore,
}
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct HidHeader {
report_id: ReportId,
packet_length: usize,
}
impl Command {
pub fn hid_packet(&self) -> Vec<u8> {
let command_packet = self.command_packet();
let mut header = [0u8; 4];
header[0] = ReportId::Command as u8;
header[2..].copy_from_slice(&(command_packet.len() as u16).to_le_bytes());
let mut hid_packet = Vec::new();
hid_packet.extend_from_slice(&header);
hid_packet.extend_from_slice(&command_packet);
hid_packet
}
pub fn header(&self) -> [u8; 4] {
[
self.tag() as u8,
self.data_phase().has_command_data() as u8,
0,
self.parameters().len() as u8,
]
}
fn command_packet(&self) -> Vec<u8> {
let params = self.parameters();
assert!(params.len() <= 7);
let mut packet = Vec::new();
packet.extend_from_slice(&self.header());
params
.iter()
.for_each(|param| packet.extend_from_slice(param.to_le_bytes().as_ref()));
packet.resize(32, 0);
packet
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, enum_iterator::Sequence, Ord, PartialEq, PartialOrd)]
pub enum ResponseTag {
Generic = 0xA0,
ReadMemory = 0xA3,
GetProperty = 0xA7,
FlashReadOnce = 0xAF,
FlashReadResource = 0xB0,
Keystore = 0xB5,
}
impl TryFrom<u8> for ResponseTag {
type Error = u8;
fn try_from(byte: u8) -> Result<ResponseTag, u8> {
use ResponseTag::*;
Ok(match byte {
0xA0 => Generic,
0xA3 => ReadMemory,
0xA7 => GetProperty,
0xAF => FlashReadOnce,
0xB0 => FlashReadResource,
0xB5 => Keystore,
_ => return Err(byte),
})
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Response {
Generic,
Data(Vec<u8>),
GetProperty(Vec<u32>),
ReadMemory(Vec<u8>),
}
impl Response {
pub fn tag(&self) -> ResponseTag {
use Response::*;
use ResponseTag as Tag;
match *self {
Generic => Tag::Generic,
Data(_) => Tag::Generic,
GetProperty(_) => Tag::GetProperty,
ReadMemory(_) => Tag::ReadMemory,
}
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, enum_iterator::Sequence, Ord, PartialEq, PartialOrd)]
pub enum ReportId {
Command = 1,
Response = 3,
CommandData = 2,
ResponseData = 4,
}
impl TryFrom<u8> for ReportId {
type Error = u8;
fn try_from(byte: u8) -> Result<Self, Self::Error> {
use ReportId::*;
Ok(match byte {
1 => Command,
2 => CommandData,
3 => Response,
4 => ResponseData,
_ => return Err(byte),
})
}
}
#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Version {
pub mark: Option<char>,
pub major: u8,
pub minor: u8,
pub fixation: u8,
}
impl core::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
if let Some(mark) = self.mark {
write!(f, "{}", mark)?;
}
write!(f, "{}.{}.{}", self.major, self.minor, self.fixation)
}
}
impl From<u32> for Version {
fn from(value: u32) -> Self {
Self {
mark: {
let candidate = (value >> 24) as u8 as char;
if candidate.is_ascii_uppercase() {
Some(candidate)
} else {
None
}
},
major: (value >> 16) as u8,
minor: (value >> 8) as u8,
fixation: value as u8,
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum FlashReadMargin {
Normal,
User,
Factory,
}
impl From<FlashReadMargin> for u8 {
fn from(frm: FlashReadMargin) -> u8 {
use FlashReadMargin::*;
match frm {
Normal => 0,
User => 1,
Factory => 2,
}
}
}
impl core::convert::TryFrom<u8> for FlashReadMargin {
type Error = u8;
fn try_from(byte: u8) -> core::result::Result<Self, u8> {
use FlashReadMargin::*;
Ok(match byte {
0 => Normal,
1 => User,
2 => Factory,
_ => return Err(byte),
})
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub enum Key {
PrinceRegion0 = 7,
PrinceRegion1 = 8,
PrinceRegion2 = 9,
SecureBootKek = 3,
UniqueDeviceSecret = 12,
UserPsk = 11,
}
pub const KEYSTORE_KEY_NAMES: [&str; 6] = [
"secure-boot-kek",
"user-key",
"unique-device-secret",
"prince-region-0",
"prince-region-1",
"prince-region-2",
];
impl TryFrom<&str> for Key {
type Error = String;
fn try_from(name: &str) -> Result<Self, Self::Error> {
use Key::*;
Ok(match name {
"prince-region-0" => PrinceRegion0,
"prince-region-1" => PrinceRegion1,
"prince-region-2" => PrinceRegion2,
"secure-boot-kek" => SecureBootKek,
"unique-device-secret" => UniqueDeviceSecret,
"user-key" => UserPsk,
_ => return Err(name.to_string()),
})
}
}
#[repr(u8)]
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(tag = "sub-cmd")]
pub enum KeystoreOperation {
Enroll,
SetKey { key: Key, data: Vec<u8> },
GenerateKey { key: Key, len: u32 },
WriteNonVolatile,
ReadNonVolatile,
WriteKeystore,
ReadKeystore,
}
impl From<&KeystoreOperation> for u32 {
fn from(operation: &KeystoreOperation) -> Self {
use KeystoreOperation::*;
match operation {
Enroll => 0,
SetKey { key: _, data: _ } => 1,
GenerateKey { key: _, len: _ } => 2,
WriteNonVolatile => 3,
ReadNonVolatile => 4,
WriteKeystore => 5,
ReadKeystore => 6,
}
}
}
#[cfg(test)]
mod test {
#[cfg(all(feature = "with-device", test))]
use super::*;
#[cfg(all(feature = "with-device", test))]
fn command_packet() {
insta::assert_debug_snapshot!(
Command::GetProperty(Property::CurrentVersion).command_packet()
);
}
#[cfg(all(feature = "with-device", test))]
fn hid_packet() {
insta::assert_debug_snapshot!(Command::GetProperty(Property::CurrentVersion).hid_packet());
}
}