-
Notifications
You must be signed in to change notification settings - Fork 11
[manifest] Add a Manager
type for managing manifest updates
#67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mcy
wants to merge
2
commits into
lowRISC:master
Choose a base branch
from
mcy:manifest-manager
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
// Copyright lowRISC contributors. | ||
// Licensed under the Apache License, Version 2.0, see LICENSE for details. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use core::marker::PhantomData; | ||
|
||
use crate::crypto::rsa; | ||
use crate::crypto::sha256; | ||
use crate::hardware::flash::Flash; | ||
use crate::manifest::provenance; | ||
use crate::manifest::Container; | ||
use crate::manifest::Error; | ||
use crate::manifest::Parse; | ||
use crate::manifest::ValidationTime; | ||
use crate::mem::Arena; | ||
use crate::mem::MapMut; | ||
|
||
/// A manifest manager, responsible for handling loading and updates of a | ||
/// [`Manifest`] in flash. | ||
/// | ||
/// A `Manager` tracks the flash location that is the long-term storage for a | ||
/// manifest, as well as the resource that the Manager guards, if any. For | ||
/// example, this would be the remote flash storage (containing platform | ||
/// firmware) that a PFM describes. | ||
pub struct Manager<'f, Manifest, Flash, Arena, Provenance = provenance::Signed> | ||
where | ||
Manifest: Parse<'f, Flash, Provenance>, | ||
Arena: crate::mem::Arena, | ||
{ | ||
manifest: MapMut< | ||
'f, | ||
Flash, | ||
<Manifest as Parse<'f, Flash, Provenance>>::Parsed, | ||
Arena, | ||
>, | ||
guarded: Manifest::Guarded, | ||
_ph: PhantomData<fn() -> Provenance>, | ||
} | ||
|
||
impl<'f, M, F, A, P> Manager<'f, M, F, A, P> | ||
where | ||
A: Arena, | ||
M: Parse<'f, F, P>, | ||
{ | ||
/// Creates a new `Manager` to track a manifest at `flash`. | ||
/// | ||
/// `arena` is used as auxiliary storage for parsing the manifest out of | ||
/// `flash`, as necessary; `guarded` is the resource that the [`Manifest`] | ||
/// type guards. | ||
pub fn new( | ||
flash: &'f mut F, | ||
arena: &'f mut A, | ||
guarded: M::Guarded, | ||
) -> Self { | ||
Self { | ||
manifest: MapMut::with_arena(flash, arena), | ||
guarded, | ||
_ph: PhantomData, | ||
} | ||
} | ||
|
||
/// Returns a reference to the cached parsed manifest value. | ||
/// | ||
/// This function will return `None` until either [`Manifest::init()`] | ||
/// or [`Manifest::activate()`] has been called. | ||
pub fn manifest(&self) -> Option<&M::Parsed> { | ||
self.manifest.mapped().ok() | ||
} | ||
|
||
/// Explicitly runs the managed manifest's validation routine, as if it were | ||
/// being run at `when` time. | ||
/// | ||
/// This function returns `Ok(())` without doing anything if not manifest | ||
/// has been parsed yet. | ||
pub fn validate(&self, when: ValidationTime) -> Result<(), Error> { | ||
let manifest = match self.manifest() { | ||
Some(m) => m, | ||
None => return Ok(()), | ||
}; | ||
M::validate(manifest, when, &self.guarded) | ||
} | ||
} | ||
|
||
impl<'f, M, F, A> Manager<'f, M, F, A, provenance::Signed> | ||
where | ||
M: Parse<'f, F, provenance::Signed>, | ||
F: Flash, | ||
A: Arena, | ||
{ | ||
/// Triggers startup initialization for this `Manager`. | ||
/// | ||
/// In effect, this function parses and verifies the manifest in the flash | ||
/// region that backs this `Manager`, and then runs [`Parse::validate()`] | ||
/// on the resulting structure. | ||
/// | ||
/// The `sha`, `rsa`, and `verify_arena` arguments are as those in | ||
/// [`Container::parse_and_verify()`]. | ||
pub fn startup( | ||
&mut self, | ||
sha: &impl sha256::Builder, | ||
rsa: &mut impl rsa::Engine, | ||
verify_arena: &impl Arena, | ||
) -> Result<&M::Parsed, Error> { | ||
let manifest = self.manifest.try_map(|flash, arena| { | ||
let container = Container::parse_and_verify( | ||
flash, | ||
sha, | ||
rsa, | ||
arena, | ||
verify_arena, | ||
)?; | ||
M::parse(container) | ||
})?; | ||
M::validate(manifest, ValidationTime::Startup, &self.guarded)?; | ||
Ok(manifest) | ||
} | ||
|
||
/// Activates a pending manifest located at `new_manifest`. | ||
/// | ||
/// This function parses a manifest out of `new_manifest`, runs | ||
/// [`Parse::validate()`] on it, and then writes it to the backing | ||
/// flash for this `Manager`. It then re-parses and re-verifies the | ||
/// newly activated manifest. | ||
/// | ||
/// This function can also be used to provision a new manifest without | ||
/// looking at the existing manifest in flash; it is sufficient that the | ||
/// new manifest be appropriately signed and valid (per | ||
/// [`Parse::validate()`]). | ||
/// | ||
/// The `sha`, `rsa`, `toc_arena` and `verify_arena` arguments are as those | ||
/// in [`Container::parse_and_verify()`]. | ||
pub fn activate<'f2, F2: Flash>( | ||
&mut self, | ||
new_manifest: &'f2 F2, | ||
sha: &impl sha256::Builder, | ||
rsa: &mut impl rsa::Engine, | ||
toc_arena: &'f2 impl Arena, | ||
verify_arena: &impl Arena, | ||
) -> Result<&<M as Parse<'f, F, provenance::Signed>>::Parsed, Error> | ||
where | ||
M: Parse<'f2, F2, provenance::Signed>, | ||
{ | ||
// First, parse and verify the signature of the new manifest. | ||
let container = Container::parse_and_verify( | ||
new_manifest, | ||
sha, | ||
rsa, | ||
toc_arena, | ||
verify_arena, | ||
)?; | ||
let new_parsed = M::parse(container)?; | ||
|
||
// Then, validate that it the guarded resource is consistent with this | ||
// manifest type. | ||
<M as Parse<'f2, F2, _>>::validate( | ||
&new_parsed, | ||
ValidationTime::Activation, | ||
&self.guarded, | ||
)?; | ||
|
||
// Finally copy it into its new home and replace `self.manifest` with | ||
// it. | ||
<M as Parse<'f2, F2, _>>::copy_to(&new_parsed, self.manifest.unmap())?; | ||
let manifest = self.manifest.try_map(|flash, arena| { | ||
let container = Container::parse_and_verify( | ||
flash, | ||
sha, | ||
rsa, | ||
arena, | ||
verify_arena, | ||
)?; | ||
M::parse(container) | ||
})?; | ||
|
||
Ok(manifest) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// Copyright lowRISC contributors. | ||
// Licensed under the Apache License, Version 2.0, see LICENSE for details. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
//! Self-referential helpers. | ||
|
||
#![allow(unsafe_code)] | ||
|
||
use core::marker::PhantomData; | ||
use core::ptr::NonNull; | ||
|
||
use crate::mem::Arena; | ||
use crate::mem::OutOfMemory; | ||
|
||
/// A mutable reference that can be "reversibly mapped". | ||
/// | ||
/// A `MapMut` holds a mutable reference to a value of type `Base`; this | ||
/// reference can be consumed to create a value of type `Derived`. | ||
/// | ||
/// An arena may also be included; this arena can be used to construct | ||
/// the mapped value, and it will be reset whenever the value is | ||
/// "unmapped". | ||
#[derive(Debug)] | ||
pub struct MapMut<'a, Base, Derived, A: Arena = OutOfMemory> { | ||
// Invariant: if `derived` is None, `base` can safely be dereferenced | ||
// without violating aliasing. | ||
base: NonNull<Base>, | ||
arena: NonNull<A>, | ||
derived: Option<Derived>, | ||
_ph: PhantomData<&'a mut (Base, A)>, | ||
} | ||
|
||
impl<'a, B, D> MapMut<'a, B, D> { | ||
/// Create a new `MapMut` wrapping `base`. | ||
pub fn new(base: &'a mut B) -> Self { | ||
Self::with_arena(base, OutOfMemory::static_mut()) | ||
} | ||
} | ||
|
||
impl<'a, B, D, A: Arena> MapMut<'a, B, D, A> { | ||
/// Create a new `MapMut` wrapping `base` with the given `arena`. | ||
pub fn with_arena(base: &'a mut B, arena: &'a mut A) -> Self { | ||
Self { | ||
base: NonNull::from(base), | ||
arena: NonNull::from(arena), | ||
derived: None, | ||
_ph: PhantomData, | ||
} | ||
} | ||
|
||
/// Returns a reference to the mapped value. | ||
/// | ||
/// If the value has not currently been mapped, a reference to the base | ||
/// type is returned in an `Err`, instead. | ||
pub fn mapped(&self) -> Result<&D, &B> { | ||
match &self.derived { | ||
Some(d) => Ok(d), | ||
// SAFETY: when `derived` is `None`, this reference is unique. | ||
None => unsafe { Err(self.base.as_ref()) }, | ||
} | ||
} | ||
|
||
/// Returns a mutable reference to the mapped value. | ||
/// | ||
/// If the value has not currently been mapped, a reference to the base | ||
/// type is returned in an `Err`, instead. | ||
pub fn mapped_mut(&mut self) -> Result<&mut D, &mut B> { | ||
match &mut self.derived { | ||
Some(d) => Ok(d), | ||
// SAFETY: when `derived` is `None`, this reference is unique. | ||
None => unsafe { Err(self.base.as_mut()) }, | ||
} | ||
} | ||
|
||
/// Maps the wrapped base value using `f`, returning a reference to the | ||
/// newly mapped value. | ||
/// | ||
/// Unlike other functions on this type, `f` receives a reference by | ||
/// the absolute lifetime `'a`, allowing the type `D` to depend on it. | ||
pub fn map(&mut self, f: impl FnOnce(&'a mut B, &'a mut A) -> D) -> &mut D { | ||
// We could do .unwrap(), but that would probably emit an extra bounds | ||
// check... | ||
match self.try_map::<core::convert::Infallible, _>(|b, a| Ok(f(b, a))) { | ||
Ok(x) => x, | ||
Err(e) => match e {}, // My kingdom for a never-type. | ||
} | ||
} | ||
|
||
/// Maps the wrapped base value using `f`, returning a reference to the | ||
/// newly mapped value. The mapping function is permitted to fail. | ||
/// | ||
/// Unlike other functions on this type, `f` receives a reference by | ||
/// the absolute lifetime `'a`, allowing the type `D` to depend on it. | ||
pub fn try_map<E, F>(&mut self, f: F) -> Result<&mut D, E> | ||
where | ||
F: FnOnce(&'a mut B, &'a mut A) -> Result<D, E>, | ||
{ | ||
// SAFETY: Ensure that any references to `self.base` are destroyed | ||
// before we convert it to a reference. | ||
self.unmap(); | ||
self.derived = Some(f( | ||
// NOTE: We cannot use .as_mut(), since that would use the | ||
// lifetime of &mut self; we want to manually dereference it | ||
// to make the lifetime "unbound"; this is fine, because the | ||
// lifetime it will pick is `'a`. | ||
unsafe { &mut *self.base.as_ptr() }, | ||
unsafe { &mut *self.arena.as_ptr() }, | ||
)?); | ||
Ok(self.derived.as_mut().unwrap()) | ||
} | ||
|
||
/// Destroys the derived value, if it exists, and returns a reference to | ||
/// the base value. | ||
pub fn unmap(&mut self) -> &mut B { | ||
// SAFETY: Ensure that any references to `self.base` are destroyed | ||
// before we convert it to a reference. | ||
self.derived = None; | ||
unsafe { | ||
self.arena.as_mut().reset(); | ||
self.base.as_mut() | ||
} | ||
} | ||
} | ||
|
||
impl<B, D, A: Arena> Drop for MapMut<'_, B, D, A> { | ||
fn drop(&mut self) { | ||
// Trigger an arena reset. | ||
self.unmap(); | ||
// No need to destroy the base or the arena, since we don't actually | ||
// own them. | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is technically incorrect (the pointer also needs to be aligned, and the alignment isn't guaranteed to be 1). It can probably be shortened to "This function is similar to
&mut []
, asOutOfMemory
holds no data.".