use core::convert::{TryFrom, TryInto};
use serde::{Deserialize, Serialize};
use std::fs;
use nom::{
bytes::complete::take,
number::complete::{
le_u16,
le_u32, u8,
},
sequence::tuple,
};
use crate::crypto::crc32;
use crate::util::is_default;
const START_OF_PROTECTED_FLASH: u32 = 0x9_DE00;
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BootTag {
Nop = 0,
Tag = 1,
Load = 2,
Fill = 3,
Jump = 4,
Call = 5,
ChangeBootMode = 6,
Erase = 7,
Reset = 8,
MemoryEnable = 9,
ProgramPersistentBits = 0xA,
CheckFirmwareVersion = 0xB,
KeystoreToNonvolatile = 0xC,
KeystoreFromNonvolatile = 0xD,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct RawBootCommand {
pub checksum: u8,
pub tag: u8,
pub flags: u16,
pub address: u32,
pub count: u32,
pub data: u32,
}
impl RawBootCommand {
pub fn to_bytes(&self) -> [u8; 16] {
let mut buffer = vec![0, self.tag];
buffer.extend_from_slice(self.flags.to_le_bytes().as_ref());
buffer.extend_from_slice(self.address.to_le_bytes().as_ref());
buffer.extend_from_slice(self.count.to_le_bytes().as_ref());
buffer.extend_from_slice(self.data.to_le_bytes().as_ref());
let checksum = buffer[1..]
.iter()
.fold(0x5au8, |acc, x| acc.wrapping_add(*x));
buffer[0] = checksum;
buffer.try_into().unwrap()
}
pub fn from_bytes(bytes: &[u8]) -> nom::IResult<&[u8], Self, ()> {
let (i, (checksum, tag, flags, address, count, data)) =
tuple((u8, u8, le_u16, le_u32, le_u32, le_u32))(bytes)?;
info!("raw boot command: {}", hex_str!(&bytes[..16]));
let calculated_checksum = bytes[1..16]
.iter()
.fold(0x5au8, |acc, x| acc.wrapping_add(*x));
assert_eq!(calculated_checksum, checksum);
Ok((
i,
Self {
checksum,
tag,
flags,
address,
count,
data,
},
))
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(tag = "cmd")]
pub enum SingleBootCommandDescription {
Erase {
start: u32,
end: u32,
},
Load {
file: String,
#[serde(default)]
#[serde(skip_serializing_if = "is_default")]
src: u32,
#[serde(default)]
#[serde(skip_serializing_if = "is_default")]
dst: u32,
#[serde(skip_serializing_if = "Option::is_none")]
len: Option<u32>,
},
CheckNonsecureFirmwareVersion {
version: u32,
},
CheckSecureFirmwareVersion {
version: u32,
},
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(tag = "seq")]
pub enum BootCommandSequenceDescription {
UploadSignedImage,
CheckDerivedFirmwareVersions,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum BootCommandDescription {
Single(SingleBootCommandDescription),
Sequence(BootCommandSequenceDescription),
}
impl<'a> TryFrom<&'a SingleBootCommandDescription> for BootCommand {
type Error = anyhow::Error;
fn try_from(cmd: &'a SingleBootCommandDescription) -> anyhow::Result<BootCommand> {
use SingleBootCommandDescription::*;
Ok(match cmd {
Erase { start, end } => BootCommand::EraseRegion {
address: *start,
bytes: core::cmp::max(0, *end - *start),
},
Load {
file,
src,
dst,
len,
} => {
let image = fs::read(file)?;
if let Some(len) = len {
if (image.len() as u32) < len + src {
return Err(anyhow::anyhow!("image too small!"));
}
}
let src_len = image.len() - *src as usize;
let len = len.unwrap_or(src_len as u32) as usize;
let data = Vec::from(&image[*src as usize..][..len]);
BootCommand::Load {
address: *dst,
data,
}
}
CheckNonsecureFirmwareVersion { version } => {
BootCommand::CheckNonsecureFirmwareVersion { version: *version }
}
CheckSecureFirmwareVersion { version } => {
BootCommand::CheckSecureFirmwareVersion { version: *version }
}
})
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum BootCommand {
Nop,
Tag {
last: bool,
tag: u32,
flags: u32,
cipher_blocks: u32,
},
Load {
address: u32,
data: Vec<u8>,
},
Fill {
address: u32,
bytes: u32,
pattern: u32,
},
EraseAll,
EraseRegion {
address: u32,
bytes: u32,
},
CheckSecureFirmwareVersion {
version: u32,
},
CheckNonsecureFirmwareVersion {
version: u32,
},
}
impl BootCommand {
pub fn to_bytes(&self) -> Vec<u8> {
use BootCommand::*;
let mut cmd: RawBootCommand = Default::default();
match self {
Nop => {
cmd.tag = BootTag::Nop as u8;
Vec::from(cmd.to_bytes().as_ref())
}
Tag {
last,
tag,
flags,
cipher_blocks,
} => {
cmd.tag = BootTag::Tag as u8;
if *last {
cmd.flags = 1;
} else {
cmd.flags = 0x8001;
}
cmd.address = *tag;
cmd.data = *flags;
cmd.count = *cipher_blocks;
Vec::from(cmd.to_bytes().as_ref())
}
Load { address, data } => {
if address + data.len() as u32 >= START_OF_PROTECTED_FLASH {
panic!("It is nearly always a mistake to write into the protected flash area");
}
cmd.tag = BootTag::Load as u8;
cmd.address = *address;
cmd.count = data.len() as u32;
cmd.data = crc32(data);
let blocks = (data.len() + 15) / 16;
let mut vec = Vec::from(cmd.to_bytes().as_ref());
println!("generated {}", &hex_str!(&vec, 4));
vec.extend_from_slice(data.as_ref());
vec.resize(16 + 16 * blocks, 0);
vec
}
EraseAll => {
cmd.tag = BootTag::Erase as u8;
cmd.flags = 1;
Vec::from(cmd.to_bytes().as_ref())
}
EraseRegion { address, bytes } => {
if address + bytes >= START_OF_PROTECTED_FLASH {
panic!("It is nearly always a mistake to erase the protected flash area");
}
cmd.tag = BootTag::Erase as u8;
cmd.address = *address;
cmd.count = *bytes;
Vec::from(cmd.to_bytes().as_ref())
}
CheckSecureFirmwareVersion { version } => {
cmd.tag = BootTag::CheckFirmwareVersion as u8;
cmd.address = 0;
cmd.count = *version;
Vec::from(cmd.to_bytes().as_ref())
}
CheckNonsecureFirmwareVersion { version } => {
cmd.tag = BootTag::CheckFirmwareVersion as u8;
cmd.address = 1;
cmd.count = *version;
Vec::from(cmd.to_bytes().as_ref())
}
_ => todo!(),
}
}
pub fn from_bytes(bytes: &[u8]) -> nom::IResult<&[u8], Self, ()> {
let (i, raw) = RawBootCommand::from_bytes(bytes)?;
Ok(match raw.tag {
0 => {
(i, Self::Nop)
}
1 => (
i,
Self::Tag {
last: (raw.flags & 1) != 0,
tag: raw.address,
flags: raw.data,
cipher_blocks: raw.count,
},
),
2 => {
let blocks = (raw.count as usize + 15) / 16;
let (i, data_ref) = take(blocks * 16)(i)?;
let data = Vec::from(&data_ref[..raw.count as usize]);
if raw.count as usize != data_ref.len() {
info!(
"surplus random bytes skipped when reading: {}",
hex_str!(&data_ref[raw.count as usize..])
);
}
let calculated_crc = crc32(data_ref);
assert_eq!(calculated_crc, raw.data);
(
i,
Self::Load {
address: raw.address,
data,
},
)
}
3 => (
i,
Self::Fill {
address: raw.address,
bytes: raw.count,
pattern: raw.data,
},
),
7 => {
let erase_all = (raw.flags & 1) != 0;
let disable_flash_security_state = (raw.flags & 2) != 0;
assert!(!disable_flash_security_state);
let memory_controller_id = (raw.flags >> 8) & 0b1111;
assert_eq!(memory_controller_id, 0x0);
if erase_all {
(i, Self::EraseAll)
} else {
(
i,
Self::EraseRegion {
address: raw.address,
bytes: raw.count,
},
)
}
}
0xB => {
let nonsecure_version = (raw.address & 1) != 0;
if nonsecure_version {
(
i,
Self::CheckNonsecureFirmwareVersion { version: raw.count },
)
} else {
(i, Self::CheckSecureFirmwareVersion { version: raw.count })
}
}
_ => todo!("implement other boot commands"),
})
}
}