use std::collections::HashMap;
use std::sync::Arc;
use sha2::{Digest as _, Sha256};
use crate::image::{detect_chip, ChipError};
use crate::{layout, Chip};
pub type Digest = [u8; 32];
const MASKED: [layout::Range; 5] = [
layout::BOOT_PAGE,
layout::UHD_REVOCATION,
layout::BD_REVOCATION,
layout::SETTINGS,
layout::CALIBRATION,
];
pub fn content_key(image: &[u8]) -> Option<Digest> {
if image.len() != layout::IMAGE_SIZE {
return None;
}
let mut masked: Vec<layout::Range> = MASKED.to_vec();
masked.sort_by_key(|r| r.start);
let mut hasher = Sha256::new();
let mut at = 0;
for range in masked {
hasher.update(&image[at..range.start]);
at = range.end();
}
hasher.update(&image[at..]);
Some(hasher.finalize().into())
}
pub fn sha256(bytes: &[u8]) -> Digest {
Sha256::digest(bytes).into()
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Factory {
boot_page: Arc<[u8]>,
regions: [Arc<[u8]>; 4],
}
impl Factory {
pub fn new(boot_page: Arc<[u8]>, regions: [Arc<[u8]>; 4]) -> Result<Self, RebuildError> {
if boot_page.len() != layout::BOOT_PAGE.len {
return Err(RebuildError::Factory(layout::BOOT_PAGE.name));
}
for (range, bytes) in layout::DRIVE_WRITTEN.iter().zip(®ions) {
if bytes.len() != range.len {
return Err(RebuildError::Factory(range.name));
}
}
Ok(Self { boot_page, regions })
}
pub fn from_image(image: &[u8]) -> Result<Self, RebuildError> {
if image.len() != layout::IMAGE_SIZE {
return Err(RebuildError::Size(image.len()));
}
if crate::image::is_drive_read(image) {
return Err(RebuildError::Factory(layout::BOOT_PAGE.name));
}
let take =
|range: layout::Range| -> Arc<[u8]> { Arc::from(&image[range.start..range.end()]) };
Self::new(take(layout::BOOT_PAGE), layout::DRIVE_WRITTEN.map(take))
}
pub fn boot_page(&self) -> &[u8] {
&self.boot_page
}
pub fn regions(&self) -> &[Arc<[u8]>; 4] {
&self.regions
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Build {
pub model: String,
pub revision: String,
pub factory: Factory,
}
#[derive(Clone, Debug, Default)]
pub struct Catalog {
exact: HashMap<Digest, Build>,
defaults: Vec<(Chip, Factory)>,
}
impl Catalog {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, key: Digest, build: Build) {
self.exact.insert(key, build);
}
pub fn set_default(&mut self, chip: Chip, factory: Factory) {
self.defaults.retain(|(c, _)| *c != chip);
self.defaults.push((chip, factory));
}
pub fn build(&self, key: &Digest) -> Option<&Build> {
self.exact.get(key)
}
pub fn default_for(&self, chip: Chip) -> Option<&Factory> {
self.defaults
.iter()
.find(|(c, _)| *c == chip)
.map(|(_, f)| f)
}
pub fn knows_boot_page(&self, page: &[u8]) -> bool {
self.defaults.iter().any(|(_, f)| f.boot_page() == page)
|| self.exact.values().any(|b| b.factory.boot_page() == page)
}
pub fn len(&self) -> usize {
self.exact.len()
}
pub fn is_empty(&self) -> bool {
self.exact.is_empty()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Source {
Exact {
model: String,
revision: String,
},
ChipDefault,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BootPage {
Stored,
Restored,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Provenance {
pub chip: Chip,
pub source: Source,
pub boot_page: BootPage,
pub regions_reset: Vec<(&'static str, usize)>,
}
impl Provenance {
pub fn is_exact(&self) -> bool {
matches!(self.source, Source::Exact { .. })
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Rebuild {
pub image: Vec<u8>,
pub provenance: Provenance,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum RebuildError {
Size(usize),
Chip(ChipError),
NoFactory(Chip),
UnknownBootPage(Digest),
Factory(&'static str),
}
impl core::fmt::Display for RebuildError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Size(n) => write!(
f,
"image is {n} bytes; an OEM image is exactly {} bytes",
layout::IMAGE_SIZE
),
Self::Chip(e) => write!(f, "{e}"),
Self::NoFactory(chip) => write!(f, "no factory contents are known for {chip}"),
Self::UnknownBootPage(sha) => {
f.write_str(
"boot page is neither a stored page nor a drive read's mirror (sha256 ",
)?;
sha.iter().try_for_each(|b| write!(f, "{b:02x}"))?;
f.write_str(")")
}
Self::Factory(name) => write!(f, "factory {name} has the wrong length or form"),
}
}
}
impl std::error::Error for RebuildError {}
pub fn rebuild(read: &[u8], catalog: &Catalog) -> Result<Rebuild, RebuildError> {
let key = content_key(read).ok_or(RebuildError::Size(read.len()))?;
let chip = detect_chip(read).map_err(RebuildError::Chip)?.family;
let (factory, source, exact) = match catalog.build(&key) {
Some(build) => (
&build.factory,
Source::Exact {
model: build.model.clone(),
revision: build.revision.clone(),
},
true,
),
None => (
catalog
.default_for(chip)
.ok_or(RebuildError::NoFactory(chip))?,
Source::ChipDefault,
false,
),
};
let mut image = read.to_vec();
let boot = &read[..layout::BOOT_PAGE.len];
let boot_page = if boot == factory.boot_page() {
BootPage::Stored
} else if crate::image::is_drive_read(read) {
image[..layout::BOOT_PAGE.len].copy_from_slice(factory.boot_page());
BootPage::Restored
} else {
return Err(RebuildError::UnknownBootPage(sha256(boot)));
};
let mut regions_reset = Vec::new();
for (range, contents) in layout::DRIVE_WRITTEN.iter().zip(factory.regions()) {
let current = &read[range.start..range.end()];
if !exact && layout::is_erased(current) {
continue;
}
let changed = current
.iter()
.zip(contents.iter())
.filter(|(a, b)| a != b)
.count();
if changed > 0 {
image[range.start..range.end()].copy_from_slice(contents);
regions_reset.push((range.name, changed));
}
}
Ok(Rebuild {
image,
provenance: Provenance {
chip,
source,
boot_page,
regions_reset,
},
})
}
#[cfg(test)]
#[path = "oem_tests.rs"]
mod tests;