use std::ops::RangeInclusive;
use crate::image::{cmac, detect_chip, family, is_drive_read, Family};
use crate::{cdb, layout, Chip, DriveClass, Identity};
#[cfg(feature = "drive")]
mod completion;
#[cfg(feature = "drive")]
pub use completion::{Completion, ReadbackQuality, Startup, StartupError};
#[cfg(feature = "drive")]
pub(crate) mod flash;
#[cfg(feature = "drive")]
pub use flash::{FlashError, FlashRuntime, FlashTransport, Stage, Status, Verified};
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum PrepareError {
ReceiverEntry,
ReceiverContract,
Size(usize),
Integrity,
DriveRead,
UnknownChip,
UnknownDriveChip,
ChipMismatch {
image: Chip,
drive: Chip,
},
InstalledMismatch,
UnknownInstalledFamily,
UnknownTargetFamily,
FamilyMismatch {
installed: Family,
target: Family,
},
}
impl core::fmt::Display for PrepareError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::ReceiverEntry => f.write_str(
"receiver entry profile is unknown; detect the live receiver before preparing",
),
Self::ReceiverContract => {
f.write_str("receiver transfer contract is incomplete; no firmware was sent")
}
Self::Size(n) => write!(
f,
"image is {n} bytes; an update image is exactly {} bytes",
layout::IMAGE_SIZE
),
Self::Integrity => f.write_str("image fails its AES-CMAC integrity check"),
Self::DriveRead => f.write_str(
"image is a raw drive read (its boot page is the read-back copy), not a \
flashable image",
),
Self::UnknownChip => f.write_str("image names no MT1959 or MT1939 chip"),
Self::UnknownDriveChip => f.write_str(
"could not read this drive's controller generation (MT1959/MT1939) from its \
boot banner",
),
Self::ChipMismatch { image, drive } => write!(
f,
"image is {image} firmware but this drive is {drive} silicon — refusing to \
flash across chip generations"
),
Self::InstalledMismatch => f.write_str(
"installed firmware is not a complete image of the live drive (size, identity \
descriptor or boot banner differ)",
),
Self::UnknownInstalledFamily => {
f.write_str("could not determine the installed firmware family")
}
Self::UnknownTargetFamily => {
f.write_str("could not determine the target firmware family")
}
Self::FamilyMismatch { installed, target } => write!(
f,
"family mismatch: installed firmware is family {installed}, target is family \
{target}"
),
}
}
}
impl std::error::Error for PrepareError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Direction {
In,
Out,
None,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Step {
pub label: &'static str,
pub cdb: [u8; 12],
pub direction: Direction,
pub length: usize,
}
impl DriveClass {
pub fn steps(self) -> Vec<Step> {
let chunk = self.chunk();
let step = |label, cdb, direction, length| Step {
label,
cdb,
direction,
length,
};
let mut steps = vec![
step("PROBE", cdb::probe(), Direction::In, cdb::PROBE_LEN),
step("READY", cdb::test_unit_ready(), Direction::None, 0),
step("PREPARE", cdb::enter_update(), Direction::Out, 0),
];
steps.extend((0..layout::IMAGE_SIZE).step_by(chunk).map(|offset| {
step(
"STREAM",
cdb::transfer(offset as u32, chunk as u16),
Direction::Out,
chunk,
)
}));
steps.extend([
step("COMMIT", cdb::finish(), Direction::Out, 0),
step("READY", cdb::test_unit_ready(), Direction::None, 0),
step(
"STATUS",
cdb::request_sense(),
Direction::In,
cdb::REQUEST_SENSE_LEN,
),
]);
steps
}
pub fn verification_ranges(self, image: &[u8]) -> Vec<RangeInclusive<usize>> {
let Ok(entries) = cmac::parse_table(image) else {
return Vec::new();
};
entries
.iter()
.filter(|entry| entry.is_active())
.filter_map(|entry| {
let start = (entry.start as usize).max(self.verify_floor());
let end = entry.end as usize;
(start <= end).then_some(start..=end)
})
.collect()
}
}
#[derive(Clone, Debug)]
pub struct InstalledFirmware {
identity: Identity,
family: Option<Family>,
}
impl InstalledFirmware {
pub fn new(identity: Identity, image: &[u8]) -> Result<Self, PrepareError> {
if image.len() != layout::IMAGE_SIZE {
return Err(PrepareError::InstalledMismatch);
}
let descriptor = identity
.has_descriptor()
.then(|| layout::DESCRIPTOR.of(image) == Some(&identity.descriptor_bytes()[..]));
let banner = (!identity.banner().is_empty())
.then(|| layout::BANNER.of(image) == Some(&identity.banner_bytes()[..]));
match (descriptor, banner) {
(None, None) | (Some(false), _) | (_, Some(false)) => {
Err(PrepareError::InstalledMismatch)
}
_ => Ok(Self {
family: family(image),
identity,
}),
}
}
pub fn identity(&self) -> &Identity {
&self.identity
}
pub fn family(&self) -> Option<Family> {
self.family
}
}
#[derive(Clone, Debug)]
pub struct PreparedUpdate {
class: DriveClass,
family: Family,
image: Vec<u8>,
transfer: Option<Vec<u8>>,
}
impl PreparedUpdate {
pub fn check(image: &[u8], drive: &Identity) -> Result<Family, PrepareError> {
Self::checked(image, drive).map(|(_, family)| family)
}
fn checked(image: &[u8], drive: &Identity) -> Result<(DriveClass, Family), PrepareError> {
if image.len() != layout::IMAGE_SIZE {
return Err(PrepareError::Size(image.len()));
}
if !cmac::verify(image) {
return Err(PrepareError::Integrity);
}
if is_drive_read(image) {
return Err(PrepareError::DriveRead);
}
let image_chip = detect_chip(image).map_err(|_| PrepareError::UnknownChip)?;
let drive_chip =
Chip::from_banner(drive.banner_bytes()).ok_or(PrepareError::UnknownDriveChip)?;
if image_chip.family != drive_chip {
return Err(PrepareError::ChipMismatch {
image: image_chip.family,
drive: drive_chip,
});
}
let target = family(image).ok_or(PrepareError::UnknownTargetFamily)?;
Ok((DriveClass::of(drive_chip), target))
}
pub fn prepare(image: &[u8], installed: &InstalledFirmware) -> Result<Self, PrepareError> {
let (class, target) = Self::checked(image, installed.identity())?;
let installed_family = installed
.family()
.ok_or(PrepareError::UnknownInstalledFamily)?;
if installed_family != target {
return Err(PrepareError::FamilyMismatch {
installed: installed_family,
target,
});
}
Ok(Self {
class,
family: target,
image: image.to_vec(),
transfer: None,
})
}
pub fn prepare_without_installed(image: &[u8], live: &Identity) -> Result<Self, PrepareError> {
let (class, target) = Self::checked(image, live)?;
Ok(Self {
class,
family: target,
image: image.to_vec(),
transfer: None,
})
}
#[cfg(all(feature = "enc", test))]
pub(crate) fn encrypted(mut self) -> Self {
let mut transfer = self.image.clone();
crate::enc::encrypt(&mut transfer).expect("an update image is whole AES blocks");
self.transfer = Some(transfer);
self
}
pub fn class(&self) -> DriveClass {
self.class
}
pub fn family(&self) -> Family {
self.family
}
pub fn image(&self) -> &[u8] {
&self.image
}
pub fn transfer(&self) -> &[u8] {
self.transfer.as_deref().unwrap_or(&self.image)
}
pub fn steps(&self) -> Vec<Step> {
self.class.steps()
}
pub fn verification_ranges(&self) -> Vec<RangeInclusive<usize>> {
self.class.verification_ranges(&self.image)
}
}
#[cfg(test)]
#[path = "update_tests.rs"]
mod tests;