orchestrator: Rename the shell to platform driver PR #357 already settled this word: 'shell' reads as bash, and the docs say platform driver. The crate follows: PlatformDriver in driver.rs, DriverError, openprot_orchestrator_driver. The outer pump is now the event loop, freeing 'driver' for the struct. Assisted-by: Claude:claude-fable-5 Signed-off-by: Christina Quast <christina.quast@9elements.com>
diff --git a/services/orchestrator/shell/BUILD.bazel b/services/orchestrator/driver/BUILD.bazel similarity index 73% rename from services/orchestrator/shell/BUILD.bazel rename to services/orchestrator/driver/BUILD.bazel index 254b0a8..b9ee0aa 100644 --- a/services/orchestrator/shell/BUILD.bazel +++ b/services/orchestrator/driver/BUILD.bazel
@@ -4,14 +4,14 @@ load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") rust_library( - name = "orchestrator_shell", + name = "orchestrator_driver", srcs = [ "src/board.rs", + "src/driver.rs", "src/lib.rs", - "src/shell.rs", "src/tests.rs", ], - crate_name = "openprot_orchestrator_shell", + crate_name = "openprot_orchestrator_driver", edition = "2024", visibility = ["//visibility:public"], deps = [ @@ -22,7 +22,7 @@ # Host tests: build on the host platform, no kernel/QEMU. rust_test( - name = "orchestrator_shell_test", - crate = ":orchestrator_shell", + name = "orchestrator_driver_test", + crate = ":orchestrator_driver", edition = "2024", )
diff --git a/services/orchestrator/shell/README.md b/services/orchestrator/driver/README.md similarity index 71% rename from services/orchestrator/shell/README.md rename to services/orchestrator/driver/README.md index ae56a73..51e03b3 100644 --- a/services/orchestrator/shell/README.md +++ b/services/orchestrator/driver/README.md
@@ -1,24 +1,24 @@ <!-- Licensed under the Apache-2.0 license --> <!-- SPDX-License-Identifier: Apache-2.0 --> -# orchestrator shell (`openprot_orchestrator_shell`) +# orchestrator platform driver (`openprot_orchestrator_driver`) -The effect-executing layer around the orchestrator state machine. `Shell` +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 -`ShellError::NotImplemented`; the SM fail-closes on them. +`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 `Shell::take_event`. The driver loop dispatches an +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 shell, event); -while let Some(ev) = shell.take_event() { - orch.dispatch(&mut shell, ev); +orch.dispatch(&mut driver, event); +while let Some(ev) = driver.take_event() { + orch.dispatch(&mut driver, ev); } ```
diff --git a/services/orchestrator/shell/src/board.rs b/services/orchestrator/driver/src/board.rs similarity index 95% rename from services/orchestrator/shell/src/board.rs rename to services/orchestrator/driver/src/board.rs index 1b193fb..d4bd066 100644 --- a/services/orchestrator/shell/src/board.rs +++ b/services/orchestrator/driver/src/board.rs
@@ -1,7 +1,7 @@ // Licensed under the Apache-2.0 license // SPDX-License-Identifier: Apache-2.0 -//! What the board supplies to the shell: traits and wiring data only. +//! What the board supplies to the driver: traits and wiring data only. //! Boards (or test mocks) implement these. use openprot_orchestrator_sm::ComponentId; @@ -97,7 +97,7 @@ } /// Everything the board supplies, built once at bring-up and handed to -/// `Shell::new`. Fields are public: executors may need two parts at once +/// `PlatformDriver::new`. Fields are public: executors may need two parts at once /// (disjoint borrows). /// /// ```ignore
diff --git a/services/orchestrator/shell/src/shell.rs b/services/orchestrator/driver/src/driver.rs similarity index 69% rename from services/orchestrator/shell/src/shell.rs rename to services/orchestrator/driver/src/driver.rs index ecdc7c5..95fd238 100644 --- a/services/orchestrator/shell/src/shell.rs +++ b/services/orchestrator/driver/src/driver.rs
@@ -1,26 +1,26 @@ // Licensed under the Apache-2.0 license // SPDX-License-Identifier: Apache-2.0 -//! The [`Shell`]: one executor method per [`Effect`] variant, routed from +//! The [`PlatformDriver`]: one executor method per [`Effect`] variant, routed from //! the SM through the [`Platform`] impl. use openprot_orchestrator_sm::{ComponentId, Effect, EffectError, Event, Platform}; use crate::board::{Board, BoardTypes, ImageSource, Verdict, Verifier}; -/// Queue bound. Executors produce at most one event per effect, the driver -/// drains after every dispatch, and the largest SM effect batch today is +/// Queue bound. Executors produce at most one event per effect, the event loop +/// drains it after every dispatch, and the largest SM effect batch today is /// two (ReadFirmware + VerifyFirmware) — 4 is that worst case with -/// headroom. Overflow is reported ([`ShellError::QueueFull`]), never +/// headroom. Overflow is reported ([`DriverError::QueueFull`]), never /// silent loss. const EVENT_CAP: usize = 4; -/// Why the shell could not carry out an effect. +/// Why the driver could not carry out an effect. #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum ShellError { +pub enum DriverError { /// The executor for this effect has not been written yet. NotImplemented, - /// The effect names a component the shell has no device for. + /// The effect names a component the driver has no device for. UnknownComponent, /// The component's image source could not be opened. ImageUnavailable, @@ -34,24 +34,24 @@ QueueFull, } -impl core::fmt::Display for ShellError { +impl core::fmt::Display for DriverError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str(match self { - ShellError::NotImplemented => "executor not implemented", - ShellError::UnknownComponent => "no device for this component id", - ShellError::ImageUnavailable => "image source could not be opened", - ShellError::NoImage => "no image staged for this component", - ShellError::VerifierFault => "verifier could not perform the check", - ShellError::QueueFull => "event queue full", + DriverError::NotImplemented => "executor not implemented", + DriverError::UnknownComponent => "no device for this component id", + DriverError::ImageUnavailable => "image source could not be opened", + DriverError::NoImage => "no image staged for this component", + DriverError::VerifierFault => "verifier could not perform the check", + DriverError::QueueFull => "event queue full", }) } } -impl core::error::Error for ShellError {} +impl core::error::Error for DriverError {} /// The effect executors. Everything device-specific lives in the [`Board`]; -/// the shell's own fields are bookkeeping. -pub struct Shell<B: BoardTypes, const N: usize> { +/// the driver's own fields are bookkeeping. +pub struct PlatformDriver<B: BoardTypes, const N: usize> { board: Board<B, N>, /// Component whose image is staged (source opened) for verification. staged: Option<ComponentId>, @@ -59,7 +59,7 @@ pending: heapless::Deque<Event, EVENT_CAP>, } -impl<B: BoardTypes, const N: usize> Shell<B, N> { +impl<B: BoardTypes, const N: usize> PlatformDriver<B, N> { pub fn new(board: Board<B, N>) -> Self { Self { board, @@ -68,48 +68,48 @@ } } - /// Next event owed to the SM; the driver loop drains this after each + /// Next event owed to the SM; the event loop drains this after each /// dispatch. pub fn take_event(&mut self) -> Option<Event> { self.pending.pop_front() } - fn enqueue(&mut self, event: Event) -> Result<(), ShellError> { + fn enqueue(&mut self, event: Event) -> Result<(), DriverError> { self.pending .push_back(event) - .map_err(|_| ShellError::QueueFull) + .map_err(|_| DriverError::QueueFull) } /// Stage `id`'s image: open its source so /// [`verify_firmware`](Self::verify_firmware) can read it. - pub fn read_firmware(&mut self, id: ComponentId) -> Result<(), ShellError> { + pub fn read_firmware(&mut self, id: ComponentId) -> Result<(), DriverError> { self.staged = None; let source = self .board .images .get_mut(id.get() as usize) - .ok_or(ShellError::UnknownComponent)?; - source.open().map_err(|_| ShellError::ImageUnavailable)?; + .ok_or(DriverError::UnknownComponent)?; + source.open().map_err(|_| DriverError::ImageUnavailable)?; self.staged = Some(id); Ok(()) } /// Judge the staged image via the [`Verifier`] and queue the verdict: /// `Event::VerificationPassed(id)` or `Event::VerificationFailed(id)`. - pub fn verify_firmware(&mut self, id: ComponentId) -> Result<(), ShellError> { + pub fn verify_firmware(&mut self, id: ComponentId) -> Result<(), DriverError> { if self.staged != Some(id) { - return Err(ShellError::NoImage); + return Err(DriverError::NoImage); } let source = self .board .images .get_mut(id.get() as usize) - .ok_or(ShellError::UnknownComponent)?; + .ok_or(DriverError::UnknownComponent)?; let verdict = self .board .verifier .verify(id, source) - .map_err(|_| ShellError::VerifierFault)?; + .map_err(|_| DriverError::VerifierFault)?; self.enqueue(match verdict { Verdict::Authentic => Event::VerificationPassed(id), Verdict::Rejected => Event::VerificationFailed(id), @@ -119,89 +119,89 @@ /// Release `id` from reset, then walk its boot checkpoints; feed back /// one `Event::ComponentReady(id)` (Active) or `Event::Booted(id)` /// (Passive), or `Event::Timeout(id)` on window expiry. - pub fn release_reset(&mut self, _id: ComponentId) -> Result<(), ShellError> { - Err(ShellError::NotImplemented) + pub fn release_reset(&mut self, _id: ComponentId) -> Result<(), DriverError> { + Err(DriverError::NotImplemented) } /// Hold `id` in reset — a durable quiesce, not a pulse; at-rest /// verification and the recovery re-walk depend on it. - pub fn assert_reset(&mut self, _id: ComponentId) -> Result<(), ShellError> { - Err(ShellError::NotImplemented) + pub fn assert_reset(&mut self, _id: ComponentId) -> Result<(), DriverError> { + Err(DriverError::NotImplemented) } /// Restore `id` from its configured recovery source (the mechanism is /// board config); feed back `Event::Restored(id)` or /// `Event::RecoveryFailed`. - pub fn recover_component(&mut self, _id: ComponentId) -> Result<(), ShellError> { - Err(ShellError::NotImplemented) + pub fn recover_component(&mut self, _id: ComponentId) -> Result<(), DriverError> { + Err(DriverError::NotImplemented) } /// Authenticate the staged update; feed back `Event::UpdateVerified` or /// `Event::UpdateRejected`. - pub fn authenticate_update(&mut self) -> Result<(), ShellError> { - Err(ShellError::NotImplemented) + pub fn authenticate_update(&mut self) -> Result<(), DriverError> { + Err(DriverError::NotImplemented) } /// Write the incoming update image into the staging region. - pub fn stage_update(&mut self) -> Result<(), ShellError> { - Err(ShellError::NotImplemented) + pub fn stage_update(&mut self) -> Result<(), DriverError> { + Err(DriverError::NotImplemented) } /// Trial-boot the staged image and arm the commit watchdog; feed back /// `Event::BootConfirmed(id)` or `Event::CommitTimeout`. - pub fn activate_update(&mut self) -> Result<(), ShellError> { - Err(ShellError::NotImplemented) + pub fn activate_update(&mut self) -> Result<(), DriverError> { + Err(DriverError::NotImplemented) } /// Discard the staged image. - pub fn discard_staged(&mut self) -> Result<(), ShellError> { - Err(ShellError::NotImplemented) + pub fn discard_staged(&mut self) -> Result<(), DriverError> { + Err(DriverError::NotImplemented) } /// Advance the SVN floor past `id`'s confirmed image; cancels the /// commit watchdog armed by [`activate_update`](Self::activate_update). - pub fn commit_svn_floor(&mut self, _id: ComponentId) -> Result<(), ShellError> { - Err(ShellError::NotImplemented) + pub fn commit_svn_floor(&mut self, _id: ComponentId) -> Result<(), DriverError> { + Err(DriverError::NotImplemented) } /// Produce a signed attestation for the pending challenge. - pub fn sign_attestation(&mut self) -> Result<(), ShellError> { - Err(ShellError::NotImplemented) + pub fn sign_attestation(&mut self) -> Result<(), DriverError> { + Err(DriverError::NotImplemented) } /// Report `id` gated and the platform degraded (CSA degraded-mode /// clause). - pub fn report_isolated(&mut self, _id: ComponentId) -> Result<(), ShellError> { - Err(ShellError::NotImplemented) + pub fn report_isolated(&mut self, _id: ComponentId) -> Result<(), DriverError> { + Err(DriverError::NotImplemented) } /// Report that `id` exhausted recovery, immediately before the machine /// latches `Locked`. - pub fn report_recovery_failed(&mut self, _id: ComponentId) -> Result<(), ShellError> { - Err(ShellError::NotImplemented) + pub fn report_recovery_failed(&mut self, _id: ComponentId) -> Result<(), DriverError> { + Err(DriverError::NotImplemented) } /// Answer the requester: update declined, machine busy (e.g. a PLDM /// "retry later" completion code). - pub fn report_update_deferred(&mut self) -> Result<(), ShellError> { - Err(ShellError::NotImplemented) + pub fn report_update_deferred(&mut self) -> Result<(), DriverError> { + Err(DriverError::NotImplemented) } /// Answer the requester: its in-flight update was superseded by /// recovery. - pub fn report_update_aborted(&mut self) -> Result<(), ShellError> { - Err(ShellError::NotImplemented) + pub fn report_update_aborted(&mut self) -> Result<(), DriverError> { + Err(DriverError::NotImplemented) } /// Latch the terminal safe state. A failure here is a hard fault: the /// SM believes it is `Locked`, so the real executor must halt, not /// recover. - pub fn latch_lockdown(&mut self) -> Result<(), ShellError> { - Err(ShellError::NotImplemented) + pub fn latch_lockdown(&mut self) -> Result<(), DriverError> { + Err(DriverError::NotImplemented) } } -impl<B: BoardTypes, const N: usize> Platform for Shell<B, N> { +impl<B: BoardTypes, const N: usize> Platform for PlatformDriver<B, N> { /// Routes each effect to its executor. Exhaustive: a new [`Effect`] /// variant must get an executor before this compiles. Every executor /// error reports as [`EffectError`] — the SM treats all actuation
diff --git a/services/orchestrator/shell/src/lib.rs b/services/orchestrator/driver/src/lib.rs similarity index 62% rename from services/orchestrator/shell/src/lib.rs rename to services/orchestrator/driver/src/lib.rs index 93fc6e0..6be41b8 100644 --- a/services/orchestrator/shell/src/lib.rs +++ b/services/orchestrator/driver/src/lib.rs
@@ -1,17 +1,17 @@ // Licensed under the Apache-2.0 license // SPDX-License-Identifier: Apache-2.0 -//! `openprot_orchestrator_shell` — the effect-executing layer around the +//! `openprot_orchestrator_driver` — the effect-executing layer around the //! orchestrator state machine. //! -//! [`Shell`] implements the SM's [`Platform`] seam: one method per `Effect`, +//! [`PlatformDriver`] implements the SM's [`Platform`] seam: one method per `Effect`, //! each documenting its obligation from the platform-boundary contract //! (`docs/src/design/orchestrator/orchestrator-model.md` §6). Unimplemented -//! executors return [`ShellError::NotImplemented`]; the SM fail-closes on +//! executors return [`DriverError::NotImplemented`]; the SM fail-closes on //! them. //! -//! Executor-produced events queue in the shell; the driver loop drains them -//! via [`Shell::take_event`] and dispatches each. +//! Executor-produced events queue in the driver; the event loop drains them +//! via [`PlatformDriver::take_event`] and dispatches each. //! //! Everything device-specific arrives through the seams in [`board`]: //! image access ([`ImageSource`]) and image judgment ([`Verifier`]), @@ -23,9 +23,9 @@ #![forbid(unsafe_code)] mod board; -mod shell; +mod driver; #[cfg(test)] mod tests; pub use board::{Board, BoardTypes, ImageSource, Verdict, Verifier}; -pub use shell::{Shell, ShellError}; +pub use driver::{DriverError, PlatformDriver};
diff --git a/services/orchestrator/shell/src/tests.rs b/services/orchestrator/driver/src/tests.rs similarity index 74% rename from services/orchestrator/shell/src/tests.rs rename to services/orchestrator/driver/src/tests.rs index ad37c44..f797e74 100644 --- a/services/orchestrator/shell/src/tests.rs +++ b/services/orchestrator/driver/src/tests.rs
@@ -136,8 +136,8 @@ type Verifier = XorVerifier; } -fn shell(images: [MemImage; 1]) -> Shell<MockBoard, 1> { - Shell::new(Board { +fn driver(images: [MemImage; 1]) -> PlatformDriver<MockBoard, 1> { + PlatformDriver::new(Board { images, verifier: XorVerifier { fault: false }, }) @@ -151,17 +151,17 @@ Orchestrator::new(chain.try_into().unwrap(), 3) } -// PowerGood drives ReadFirmware + VerifyFirmware into the shell; the +// PowerGood drives ReadFirmware + VerifyFirmware into the driver; the // verdict comes back as an event. #[test] fn boot_verifies_the_first_component() { let mut orch = orchestrator(); - let mut shell = shell([MemImage::holding(valid_image())]); + let mut driver = driver([MemImage::holding(valid_image())]); - orch.dispatch(&mut shell, Event::PowerGood(PowerOnResult::Provisioned)); + orch.dispatch(&mut driver, Event::PowerGood(PowerOnResult::Provisioned)); - assert_eq!(shell.take_event(), Some(Event::VerificationPassed(C0))); - assert_eq!(shell.take_event(), None); + assert_eq!(driver.take_event(), Some(Event::VerificationPassed(C0))); + assert_eq!(driver.take_event(), None); assert_eq!(orch.state(), State::PreSupervision); } @@ -169,20 +169,20 @@ fn corrupt_image_fails_verification() { let mut corrupt = valid_image(); corrupt[7] ^= 0x01; - let mut shell = shell([MemImage::holding(corrupt)]); + let mut driver = driver([MemImage::holding(corrupt)]); - shell.read_firmware(C0).unwrap(); - shell.verify_firmware(C0).unwrap(); + driver.read_firmware(C0).unwrap(); + driver.verify_firmware(C0).unwrap(); - assert_eq!(shell.take_event(), Some(Event::VerificationFailed(C0))); + assert_eq!(driver.take_event(), Some(Event::VerificationFailed(C0))); } #[test] fn verify_without_read_is_refused() { - let mut shell = shell([MemImage::holding(valid_image())]); + let mut driver = driver([MemImage::holding(valid_image())]); - assert_eq!(shell.verify_firmware(C0), Err(ShellError::NoImage)); - assert_eq!(shell.take_event(), None); + assert_eq!(driver.verify_firmware(C0), Err(DriverError::NoImage)); + assert_eq!(driver.take_event(), None); } // An unopenable source is a failed actuation, not a verdict: the SM @@ -192,12 +192,12 @@ let mut orch = orchestrator(); let mut image = MemImage::holding(valid_image()); image.fail_open = true; - let mut shell = shell([image]); + let mut driver = driver([image]); - orch.dispatch(&mut shell, Event::PowerGood(PowerOnResult::Provisioned)); + orch.dispatch(&mut driver, Event::PowerGood(PowerOnResult::Provisioned)); assert_eq!(orch.state(), State::Locked); - assert_eq!(shell.take_event(), None); + assert_eq!(driver.take_event(), None); } // A source that opens but cannot be read fails the same way, via the @@ -207,34 +207,34 @@ let mut orch = orchestrator(); let mut image = MemImage::holding(valid_image()); image.fail_read = true; - let mut shell = shell([image]); + let mut driver = driver([image]); - orch.dispatch(&mut shell, Event::PowerGood(PowerOnResult::Provisioned)); + orch.dispatch(&mut driver, Event::PowerGood(PowerOnResult::Provisioned)); assert_eq!(orch.state(), State::Locked); - assert_eq!(shell.take_event(), None); + assert_eq!(driver.take_event(), None); } // So does a verifier that cannot run its check. #[test] fn verifier_fault_fails_closed() { let mut orch = orchestrator(); - let mut shell = Shell::<MockBoard, 1>::new(Board { + let mut driver = PlatformDriver::<MockBoard, 1>::new(Board { images: [MemImage::holding(valid_image())], verifier: XorVerifier { fault: true }, }); - orch.dispatch(&mut shell, Event::PowerGood(PowerOnResult::Provisioned)); + orch.dispatch(&mut driver, Event::PowerGood(PowerOnResult::Provisioned)); assert_eq!(orch.state(), State::Locked); - assert_eq!(shell.take_event(), None); + assert_eq!(driver.take_event(), None); } const C1: ComponentId = ComponentId::new(1); #[test] fn verify_for_a_different_component_is_refused() { - let mut shell = Shell::<MockBoard, 2>::new(Board { + let mut driver = PlatformDriver::<MockBoard, 2>::new(Board { images: [ MemImage::holding(valid_image()), MemImage::holding(valid_image()), @@ -242,18 +242,18 @@ verifier: XorVerifier { fault: false }, }); - shell.read_firmware(C0).unwrap(); + driver.read_firmware(C0).unwrap(); - assert_eq!(shell.verify_firmware(C1), Err(ShellError::NoImage)); + assert_eq!(driver.verify_firmware(C1), Err(DriverError::NoImage)); } #[test] fn unknown_component_is_refused() { - let mut shell = shell([MemImage::holding(valid_image())]); + let mut driver = driver([MemImage::holding(valid_image())]); assert_eq!( - shell.read_firmware(ComponentId::new(9)), - Err(ShellError::UnknownComponent) + driver.read_firmware(ComponentId::new(9)), + Err(DriverError::UnknownComponent) ); } @@ -261,15 +261,15 @@ // not silently dropped. #[test] fn event_queue_overflow_is_reported() { - let mut shell = shell([MemImage::holding(valid_image())]); + let mut driver = driver([MemImage::holding(valid_image())]); let mut queued = 0; loop { - shell.read_firmware(C0).unwrap(); - match shell.verify_firmware(C0) { + driver.read_firmware(C0).unwrap(); + match driver.verify_firmware(C0) { Ok(()) => queued += 1, Err(e) => { - assert_eq!(e, ShellError::QueueFull); + assert_eq!(e, DriverError::QueueFull); break; } } @@ -278,16 +278,16 @@ } // Effect::Emit is the orchestrator's internal channel and must never reach -// a Platform; the shell refuses it rather than acting on it. +// a Platform; the driver refuses it rather than acting on it. #[test] fn emit_is_refused() { use openprot_orchestrator_sm::{Effect, EffectError, Platform}; - let mut shell = shell([MemImage::holding(valid_image())]); + let mut driver = driver([MemImage::holding(valid_image())]); assert_eq!( - shell.execute(Effect::Emit(Event::UpdateRequest)), + driver.execute(Effect::Emit(Event::UpdateRequest)), Err(EffectError) ); - assert_eq!(shell.take_event(), None); + assert_eq!(driver.take_event(), None); }