use pamoja_security::PublicIdentity;
use crate::error::{Refusal, Result};
use crate::manifest::{Envelope, Manifest, ID_LEN};
use crate::slots::{SlotRecord, SlotState, SlotStore};
use crate::trust::Delegation;
use crate::verify::ImageVerifier;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Boot {
Confirmed(u8),
Trying(u8),
Reverted {
failed: u8,
fallback: u8,
},
}
impl Boot {
pub fn action(self) -> &'static str {
match self {
Boot::Confirmed(_) => "Confirmed",
Boot::Trying(_) => "Trying",
Boot::Reverted { .. } => "Reverted",
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Device {
pub vendor_id: [u8; ID_LEN],
pub class_id: [u8; ID_LEN],
pub anchor: PublicIdentity,
}
pub struct Updater<S> {
device: Device,
store: S,
delegation: Option<Delegation>,
}
impl<S: SlotStore> Updater<S> {
pub fn new(device: Device, store: S) -> Self {
Self {
device,
store,
delegation: None,
}
}
pub fn with_delegation(mut self, envelope: &[u8], now: Option<u64>) -> Result<Self> {
self.adopt(envelope, now)?;
Ok(self)
}
pub fn adopt(&mut self, envelope: &[u8], now: Option<u64>) -> Result<Delegation> {
let delegation = Delegation::open(envelope, &self.device.anchor)?;
if let Some(held) = self.delegation {
if delegation.epoch <= held.epoch {
return Err(Refusal::Rollback);
}
}
if delegation.expires != 0 {
match now {
Some(now) if now < delegation.expires => {}
Some(_) => return Err(Refusal::Expired),
None => return Err(Refusal::NoClock),
}
}
delegation.signer()?;
self.delegation = Some(delegation);
Ok(delegation)
}
pub fn delegation(&self) -> Option<Delegation> {
self.delegation
}
fn signing_key(&self) -> Result<PublicIdentity> {
match self.delegation {
Some(delegation) => delegation.signer(),
None => Ok(self.device.anchor),
}
}
pub fn store(&self) -> &S {
&self.store
}
pub fn installed_sequence(&self) -> Result<u64> {
let mut highest = 0;
for slot in 0..self.store.slot_count() {
let record = self.store.record(slot)?;
if !matches!(record.state, SlotState::Empty | SlotState::Receiving) {
highest = highest.max(record.sequence);
}
}
Ok(highest)
}
pub fn begin(&mut self, envelope: &[u8]) -> Result<Staging<'_, S>> {
self.begin_at(envelope, None)
}
pub fn begin_at(&mut self, envelope: &[u8], now: Option<u64>) -> Result<Staging<'_, S>> {
let manifest = Envelope::decode(envelope)?.verify(&self.signing_key()?)?;
self.check(&manifest, now)?;
self.open(manifest)
}
fn check(&self, manifest: &Manifest, now: Option<u64>) -> Result<()> {
if manifest.vendor_id != self.device.vendor_id || manifest.class_id != self.device.class_id
{
return Err(Refusal::WrongDevice);
}
if manifest.expires != 0 {
match now {
Some(now) if now < manifest.expires => {}
Some(_) => return Err(Refusal::Expired),
None => return Err(Refusal::NoClock),
}
}
if manifest.sequence <= self.installed_sequence()? {
return Err(Refusal::Rollback);
}
let slot = manifest.storage;
if manifest.size > self.store.capacity(slot)? {
return Err(Refusal::SlotTooSmall);
}
if self.store.record(slot)?.state == SlotState::Confirmed {
return Err(Refusal::WrongState);
}
Ok(())
}
fn open(&mut self, manifest: Manifest) -> Result<Staging<'_, S>> {
let slot = manifest.storage;
self.store.erase(slot)?;
self.store.set_record(
slot,
SlotRecord {
state: SlotState::Receiving,
sequence: manifest.sequence,
size: manifest.size,
digest: manifest.digest,
written: 0,
},
)?;
Ok(Staging {
store: &mut self.store,
slot,
verifier: ImageVerifier::new(&manifest),
manifest,
offset: 0,
})
}
pub fn resume_at(&mut self, envelope: &[u8], now: Option<u64>) -> Result<Staging<'_, S>> {
let manifest = Envelope::decode(envelope)?.verify(&self.signing_key()?)?;
self.check(&manifest, now)?;
let slot = manifest.storage;
let record = self.store.record(slot)?;
let resumable = record.state == SlotState::Receiving
&& record.digest == manifest.digest
&& record.size == manifest.size
&& record.written <= manifest.size;
if !resumable {
return self.open(manifest);
}
let mut verifier = ImageVerifier::new(&manifest);
let mut buf = [0u8; 256];
let mut at = 0u32;
while at < record.written {
let want = buf.len().min((record.written - at) as usize);
let read = self.store.read(slot, at, &mut buf[..want])?;
if read == 0 {
return Err(Refusal::Malformed);
}
verifier.update(&buf[..read])?;
at += read as u32;
}
Ok(Staging {
store: &mut self.store,
slot,
verifier,
manifest,
offset: record.written,
})
}
pub fn stage(&mut self, envelope: &[u8], image: &[u8]) -> Result<u8> {
self.stage_at(envelope, image, None)
}
pub fn stage_at(&mut self, envelope: &[u8], image: &[u8], now: Option<u64>) -> Result<u8> {
let mut staging = self.begin_at(envelope, now)?;
staging.write(image)?;
staging.finish()
}
pub fn on_boot(&mut self) -> Result<Boot> {
if let Some(pending) = self.find(SlotState::Pending)? {
self.set_state(pending, SlotState::Failed)?;
let fallback = self
.find(SlotState::Confirmed)?
.ok_or(Refusal::NothingToRevert)?;
return Ok(Boot::Reverted {
failed: pending,
fallback,
});
}
if let Some(staged) = self.find(SlotState::Staged)? {
self.set_state(staged, SlotState::Pending)?;
return Ok(Boot::Trying(staged));
}
self.find(SlotState::Confirmed)?
.map(Boot::Confirmed)
.ok_or(Refusal::NothingToRevert)
}
pub fn confirm(&mut self) -> Result<u8> {
let pending = self.find(SlotState::Pending)?.ok_or(Refusal::WrongState)?;
if let Some(previous) = self.find(SlotState::Confirmed)? {
self.store.erase(previous)?;
}
self.set_state(pending, SlotState::Confirmed)?;
Ok(pending)
}
pub fn revert(&mut self) -> Result<u8> {
let pending = self.find(SlotState::Pending)?.ok_or(Refusal::WrongState)?;
let fallback = self
.find(SlotState::Confirmed)?
.ok_or(Refusal::NothingToRevert)?;
self.set_state(pending, SlotState::Failed)?;
Ok(fallback)
}
pub fn provision(&mut self, slot: u8, sequence: u64) -> Result<()> {
if self.find(SlotState::Confirmed)?.is_some() {
return Err(Refusal::WrongState);
}
let mut record = self.store.record(slot)?;
record.state = SlotState::Confirmed;
record.sequence = sequence;
self.store.set_record(slot, record)
}
fn find(&self, state: SlotState) -> Result<Option<u8>> {
for slot in 0..self.store.slot_count() {
if self.store.record(slot)?.state == state {
return Ok(Some(slot));
}
}
Ok(None)
}
fn set_state(&mut self, slot: u8, state: SlotState) -> Result<()> {
let mut record = self.store.record(slot)?;
record.state = state;
self.store.set_record(slot, record)
}
}
pub struct Staging<'a, S: SlotStore> {
store: &'a mut S,
slot: u8,
manifest: Manifest,
verifier: ImageVerifier,
offset: u32,
}
impl<S: SlotStore> Staging<'_, S> {
pub fn write(&mut self, chunk: &[u8]) -> Result<()> {
self.verifier.update(chunk)?;
self.store.write(self.slot, self.offset, chunk)?;
self.offset += chunk.len() as u32;
let mut record = self.store.record(self.slot)?;
record.written = self.offset;
self.store.set_record(self.slot, record)
}
pub fn progress(&self) -> (u32, u32) {
(self.offset, self.manifest.size)
}
pub fn finish(self) -> Result<u8> {
let verified = self.verifier.finish()?;
self.store.set_record(
self.slot,
SlotRecord {
state: SlotState::Staged,
sequence: self.manifest.sequence,
size: verified.size(),
digest: verified.digest(),
written: verified.size(),
},
)?;
Ok(self.slot)
}
pub fn manifest(&self) -> &Manifest {
&self.manifest
}
}