Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions services/orchestrator/driver/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Licensed under the Apache-2.0 license
# SPDX-License-Identifier: Apache-2.0

load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")

rust_library(
name = "orchestrator_driver",
srcs = [
"src/board.rs",
"src/driver.rs",
"src/lib.rs",
"src/tests.rs",
],
crate_name = "openprot_orchestrator_driver",
edition = "2024",
visibility = ["//visibility:public"],
deps = [
"//services/orchestrator/sm:orchestrator_sm",
"@rust_crates//:heapless",
],
)

# Host tests: build on the host platform, no kernel/QEMU.
rust_test(
name = "orchestrator_driver_test",
crate = ":orchestrator_driver",
edition = "2024",
)
27 changes: 27 additions & 0 deletions services/orchestrator/driver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!-- Licensed under the Apache-2.0 license -->
<!-- SPDX-License-Identifier: Apache-2.0 -->

# orchestrator platform driver (`openprot_orchestrator_driver`)

The effect-executing layer around the orchestrator state machine. `PlatformDriver`
implements the SM's `Platform` seam: one method per `Effect`, each
documenting its obligation from the platform-boundary contract
([orchestrator-model.md §6](../../../docs/src/design/orchestrator/orchestrator-model.md)). Unimplemented executors return
`DriverError::NotImplemented`; the SM fail-closes on them.

Everything device-specific arrives through the seams in `board.rs`
(`ImageSource`, `Verifier`, bundled in `Board`); executor-produced events
return to the SM via `PlatformDriver::take_event`. The event loop dispatches an
outside event, then keeps dispatching what the executors produced until
`take_event` returns `None`:

```rust
orch.dispatch(&mut driver, event);
while let Some(ev) = driver.take_event() {
orch.dispatch(&mut driver, ev);
}
```

Implemented executors: `read_firmware`, `verify_firmware`. Everything else
returns `NotImplemented` until its pillar lands (boot walk, recovery,
update path, attestation, reporting).
121 changes: 121 additions & 0 deletions services/orchestrator/driver/src/board.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

//! What the board supplies to the driver: traits and wiring data only.
//! Boards (or test mocks) implement these.

use openprot_orchestrator_sm::ComponentId;

/// Access to one component's active firmware image, however it is reached —
/// interposed flash, a PLDM/MCTP transfer, a RAM copy in tests.
pub trait ImageSource {
/// The error type reported by this source.
type Error: core::error::Error;

/// Makes the image readable (claim the flash, open the transfer).
/// Idempotent; a later `open` re-stages the image.
fn open(&mut self) -> Result<(), Self::Error>;

/// Image length in bytes.
fn size(&mut self) -> Result<usize, Self::Error>;

/// Reads `buf.len()` bytes starting at byte `offset` of the image.
fn read_at(&mut self, offset: usize, buf: &mut [u8]) -> Result<(), Self::Error>;
}

impl<S: ImageSource> ImageSource for &mut S {
type Error = S::Error;

#[inline(always)]
fn open(&mut self) -> Result<(), Self::Error> {
(**self).open()
}

#[inline(always)]
fn size(&mut self) -> Result<usize, Self::Error> {
(**self).size()
}

#[inline(always)]
fn read_at(&mut self, offset: usize, buf: &mut [u8]) -> Result<(), Self::Error> {
(**self).read_at(offset, buf)
}
}

/// Judges a component's firmware image; board wiring decides what
/// "authentic" means.
pub trait Verifier {
/// The error type reported by this verifier.
type Error: core::error::Error;

/// Judges `id`'s image, reading it from `image`.
///
/// # Errors
///
/// Only when the check could not be performed (crypto fault, missing
/// key, unreadable source). A checked-and-bad image is
/// `Ok(Verdict::Rejected)` — an actuation fault must not forge a
/// verdict.
fn verify(
&mut self,
id: ComponentId,
image: &mut impl ImageSource,
) -> Result<Verdict, Self::Error>;
}

impl<V: Verifier> Verifier for &mut V {
type Error = V::Error;

#[inline(always)]
fn verify(
&mut self,
id: ComponentId,
image: &mut impl ImageSource,
) -> Result<Verdict, Self::Error> {
(**self).verify(id, image)
}
}

/// A [`Verifier`]'s judgment of one image.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Verdict {
/// Reported as `Event::VerificationPassed`.
Authentic,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: if we cryptographically authenticated an image, I would name it Authenticated, since this is common naming in cryptographic contexts.

/// Reported as `Event::VerificationFailed`.
Rejected,
}

/// One board's type choices, named by a marker type. A new seam adds an
/// associated type here and a field on [`Board`] — never another parameter.
pub trait BoardTypes {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pease rename BoardTypes to BoardCapabilities. This is an architectural clarification, not just a naming cleanup. The trait defines the set of platform capabilities that can be composed into a PlatformDriver,

/// Image access for the managed components.
type Image: ImageSource;
/// Judges images for every component.
type Verifier: Verifier;
// Later seams: Reset (release/assert_reset), Evidence (checkpoint
// walk), Recovery, Staging.
}

/// Everything the board supplies, built once at bring-up and handed to
/// `PlatformDriver::new`. Fields are public: executors may need two parts at once
/// (disjoint borrows).
///
/// ```ignore
/// struct Ast1060Board;
/// impl BoardTypes for Ast1060Board {
/// type Image = SpiFlashImage; // interposed flash, offsets from the slot layout
/// type Verifier = ManifestVerifier; // signature + SVN via the crypto engine
/// }
/// let board = Board::<Ast1060Board, 2> {
/// images: [bmc_image, cpld_image],
/// verifier,
/// };
/// ```
pub struct Board<B: BoardTypes, const N: usize> {
/// `images[i]` belongs to `ComponentId(i)` — device index = chain
/// position = table declaration order.
pub images: [B::Image; N],
/// Judges images for every component.
pub verifier: B::Verifier,
// Later seams add fields, e.g. resets: [B::Reset; N].
}
Loading