use super::{Encoding, Receiver};
use crate::{envelope::Envelope, update};
#[derive(Debug)]
pub struct PreparedUpdate {
plan: TransferPlan,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TransferEncoding {
pub header: Encoding,
pub body: Encoding,
}
#[derive(Debug, Clone)]
pub struct TransferPlan {
receiver: Receiver,
class: crate::DriveClass,
image: Vec<u8>,
transfer: Option<Vec<u8>>,
encoding: TransferEncoding,
prepare: crate::cdb::Prepare,
mode: update::flash::ExecutionMode,
}
#[derive(Debug)]
pub enum ExecuteError<E> {
Receiver(super::Error<E>),
Flash(update::FlashError<E>),
}
impl<E: core::fmt::Debug> core::fmt::Display for ExecuteError<E> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Receiver(error) => {
write!(f, "receiver revalidation failed before writing: {error}")
}
Self::Flash(error) => write!(f, "{error}"),
}
}
}
impl<E: core::fmt::Debug + 'static> std::error::Error for ExecuteError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Receiver(source) => Some(source),
Self::Flash(source) => Some(source),
}
}
}
impl Receiver {
pub fn bind_prepared(
&self,
update: &update::PreparedUpdate,
) -> Result<PreparedUpdate, update::PrepareError> {
Ok(PreparedUpdate {
plan: TransferPlan::new(
self,
update.image(),
update.class(),
update::flash::ExecutionMode::Flash,
)?,
})
}
pub fn prepare_without_installed(
&self,
envelope: &Envelope,
live: &crate::Identity,
) -> Result<PreparedUpdate, update::PrepareError> {
let update = update::PreparedUpdate::prepare_without_installed(envelope.image(), live)?;
self.bind_prepared(&update)
}
pub fn prepare(
&self,
envelope: &Envelope,
installed: &update::InstalledFirmware,
) -> Result<PreparedUpdate, update::PrepareError> {
let update = update::PreparedUpdate::prepare(envelope.image(), installed)?;
self.bind_prepared(&update)
}
}
impl PreparedUpdate {
pub fn plan(&self) -> &TransferPlan {
&self.plan
}
pub fn flash<R, W, H>(
&self,
io: update::FlashTransport<'_, R, W>,
runtime: &mut H,
) -> Result<update::Verified, ExecuteError<W::Error>>
where
W: crate::drive::Transport,
R: crate::drive::Transport<Error = W::Error>,
H: update::FlashRuntime<W::Error>,
{
self.flash_completed(io, runtime)?
.into_verified()
.map_err(ExecuteError::Flash)
}
pub fn flash_completed<R, W, H>(
&self,
io: update::FlashTransport<'_, R, W>,
runtime: &mut H,
) -> Result<update::Completion, ExecuteError<W::Error>>
where
W: crate::drive::Transport,
R: crate::drive::Transport<Error = W::Error>,
H: update::FlashRuntime<W::Error>,
{
self.plan.execute_completed(io, runtime)
}
}
fn encode_transfer(
receiver: &Receiver,
image: &[u8],
) -> Result<(Option<Vec<u8>>, TransferEncoding, bool), update::PrepareError> {
if let Some(analysis) = receiver.application_analysis() {
if analysis.conflicting_arguments() {
return Err(update::PrepareError::ReceiverContract);
}
if !analysis.scratch_decoders().is_empty() {
let contract = receiver
.profile
.as_ref()
.and_then(|p| p.mixed.as_ref())
.ok_or(update::PrepareError::ReceiverContract)?;
if image.get(contract.descriptor_byte()) != Some(&0xde) {
return Err(update::PrepareError::ReceiverContract);
}
use super::layout::Operation;
let mut operations = vec![Operation::CheckDecrypted(contract.header_range())];
for entry in crate::image::cmac::parse_table(image)
.map_err(|_| update::PrepareError::Integrity)?
.into_iter()
.filter(|entry| entry.is_active())
{
let range = entry.start as usize..entry.end as usize + 1;
if !contract.programmed_ranges().iter().any(|programmed| {
programmed.start <= range.start && programmed.end >= range.end
}) {
return Err(update::PrepareError::ReceiverContract);
}
operations.push(Operation::CheckPlaintext(range));
}
for range in contract.programmed_ranges() {
operations.push(Operation::Program(range.clone()));
}
let layout = super::layout::analyze(image.len(), &operations)
.map_err(|_| update::PrepareError::ReceiverContract)?;
let wire = layout
.encode(image)
.map_err(|_| update::PrepareError::ReceiverContract)?;
return Ok((
Some(wire),
TransferEncoding {
header: Encoding::Encrypted,
body: Encoding::Plaintext,
},
true,
));
}
if receiver.encoding() == Encoding::Encrypted {
let [bulk] = analysis.bulk_decoders() else {
return Err(update::PrepareError::ReceiverContract);
};
use super::decoding::Value;
let Value::Loaded { cell, offset: 0 } = bulk.from_zero.source else {
return Err(update::PrepareError::ReceiverContract);
};
if bulk.from_zero.destination != bulk.from_zero.source
|| bulk.from_zero.length as usize != image.len()
|| bulk.header.source
!= (Value::Loaded {
cell,
offset: 0x3000,
})
|| bulk.header.length != 16
|| !matches!(bulk.header.destination, Value::Stack(_))
|| bulk.header.target != bulk.from_zero.target
{
return Err(update::PrepareError::ReceiverContract);
}
let mut wire = image.to_vec();
crate::enc::encrypt(&mut wire).map_err(|_| update::PrepareError::ReceiverContract)?;
return Ok((
Some(wire),
TransferEncoding {
header: Encoding::Encrypted,
body: Encoding::Encrypted,
},
false,
));
}
if !analysis.bulk_decoders().is_empty() {
return Err(update::PrepareError::ReceiverContract);
}
}
if receiver.encoding() == Encoding::Encrypted {
let mut wire = image.to_vec();
crate::enc::encrypt(&mut wire).map_err(|_| update::PrepareError::ReceiverContract)?;
Ok((
Some(wire),
TransferEncoding {
header: Encoding::Encrypted,
body: Encoding::Encrypted,
},
false,
))
} else {
Ok((
None,
TransferEncoding {
header: Encoding::Plaintext,
body: Encoding::Plaintext,
},
false,
))
}
}
impl TransferPlan {
pub(crate) fn new(
receiver: &Receiver,
image: &[u8],
class: crate::DriveClass,
mode: update::flash::ExecutionMode,
) -> Result<Self, update::PrepareError> {
let profile = receiver
.profile
.as_ref()
.ok_or(update::PrepareError::ReceiverEntry)?;
let image_chip = crate::Chip::from_banner(
crate::layout::BANNER
.of(image)
.ok_or(update::PrepareError::UnknownChip)?,
)
.ok_or(update::PrepareError::UnknownChip)?;
if image_chip != profile.chip {
return Err(update::PrepareError::ChipMismatch {
image: image_chip,
drive: profile.chip,
});
}
let mut prepare = profile.prepare;
let (transfer, encoding, preserve_boot) = encode_transfer(receiver, image)?;
if preserve_boot {
use crate::cdb::Prepare;
prepare = match prepare {
Prepare::ModeOne | Prepare::ModeOnePreserveBoot => Prepare::ModeOnePreserveBoot,
Prepare::ModeSix67 | Prepare::ModeSix67PreserveBoot => {
Prepare::ModeSix67PreserveBoot
}
};
}
Ok(Self {
receiver: receiver.clone(),
class,
image: image.to_vec(),
transfer,
encoding,
prepare,
mode,
})
}
pub(crate) fn execute_completed<R, W, H>(
&self,
io: update::FlashTransport<'_, R, W>,
runtime: &mut H,
) -> Result<update::Completion, ExecuteError<W::Error>>
where
W: crate::drive::Transport,
R: crate::drive::Transport<Error = W::Error>,
H: update::FlashRuntime<W::Error>,
{
self.receiver
.revalidate(io.reads)
.map_err(ExecuteError::Receiver)?;
update::flash::Payload {
class: self.class,
image: self.image(),
transfer: self.transfer(),
}
.flash_completed(io, runtime, self.mode, self.prepare)
.map_err(ExecuteError::Flash)
}
pub fn encoding(&self) -> TransferEncoding {
self.encoding
}
pub fn programmed_ranges(&self) -> Option<&[core::ops::Range<usize>]> {
self.receiver
.profile
.as_ref()?
.mixed
.as_ref()
.map(|contract| contract.programmed_ranges())
}
pub fn preserved_ranges(&self) -> Option<Vec<core::ops::Range<usize>>> {
let programmed = self.programmed_ranges()?;
let mut preserved = Vec::new();
let mut end = 0;
for range in programmed {
if end < range.start {
preserved.push(end..range.start);
}
end = range.end;
}
if end < self.image.len() {
preserved.push(end..self.image.len());
}
Some(preserved)
}
pub fn prepare_command(&self) -> crate::cdb::Prepare {
self.prepare
}
pub fn class(&self) -> crate::DriveClass {
self.class
}
pub fn receiver(&self) -> &Receiver {
&self.receiver
}
pub fn image(&self) -> &[u8] {
&self.image
}
pub fn transfer(&self) -> &[u8] {
self.transfer.as_deref().unwrap_or(&self.image)
}
pub fn steps(&self) -> std::vec::Vec<update::Step> {
let mut steps = self.class.steps();
if self.mode == update::flash::ExecutionMode::Recovery {
steps.drain(..2);
}
for step in &mut steps {
if step.label == "PREPARE" {
step.cdb = self.prepare.command();
}
}
steps
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
fixtures::{self, BU3},
layout, Identity, INQUIRY_LEN,
};
#[test]
fn input_envelope_never_selects_the_running_receivers_wire_encoding() {
let image = fixtures::image(&BU3, 7);
let identity = Identity::parse(
&[0; INQUIRY_LEN],
layout::BANNER.of(&image).unwrap(),
layout::DESCRIPTOR.of(&image).unwrap(),
)
.unwrap();
let installed =
update::InstalledFirmware::new(identity, &fixtures::drive_read(&image)).unwrap();
let mut ciphertext = image.clone();
crate::enc::encrypt(&mut ciphertext).unwrap();
for encoding in [Encoding::Plaintext, Encoding::Encrypted] {
let receiver = Receiver {
identity: super::super::DetectedMode {
mode: super::super::Mode::Firmware,
inquiry: [0; INQUIRY_LEN],
},
encoding,
site: super::super::Evidence::new(0x98000, &[1, 2]),
transform: None,
reference: None,
profile: Some(super::super::profile::Profile::testing()),
};
for input in [&image, &ciphertext] {
let prepared = receiver
.prepare(&Envelope::load(input).unwrap(), &installed)
.unwrap();
assert_eq!(prepared.plan().image(), image);
assert_eq!(
prepared.plan().transfer(),
if encoding == Encoding::Encrypted {
&ciphertext
} else {
&image
}
);
assert_eq!(prepared.plan().receiver(), &receiver);
let steps = prepared.plan().steps();
assert_eq!(
steps[..3].iter().map(|s| s.label).collect::<Vec<_>>(),
["PROBE", "READY", "PREPARE"]
);
let recovery = TransferPlan::new(
&receiver,
&image,
prepared.plan().class(),
update::flash::ExecutionMode::Recovery,
)
.unwrap();
let recovery_steps = recovery.steps();
assert_eq!(recovery_steps[0].label, "PREPARE");
assert_eq!(recovery_steps.len() + 2, steps.len());
for (recovery, flash) in recovery_steps.iter().zip(&steps[2..]) {
assert_eq!(recovery.cdb, flash.cdb);
assert_eq!(recovery.direction, flash.direction);
assert_eq!(recovery.length, flash.length);
}
}
}
}
}