| # Generic Digest Server Design Document |
| |
| This document describes the design and architecture of a generic digest server for Hubris OS that supports both SPDM and PLDM protocol implementations. |
| |
| ## Requirements |
| |
| ### Primary Requirement |
| |
| **Enable SPDM and PLDM Protocol Support**: The digest server must provide cryptographic hash services to support both SPDM (Security Protocol and Data Model) and PLDM (Platform Level Data Model) protocol implementations in Hubris OS. |
| |
| ### Derived Requirements |
| |
| #### R1: Algorithm Support |
| - **R1.1**: Support SHA-256 for basic SPDM operations and PLDM firmware integrity validation |
| - **R1.2**: Support SHA-384 for enhanced security profiles in both SPDM and PLDM |
| - **R1.3**: Support SHA-512 for maximum security assurance |
| - **R1.4**: Reject unsupported algorithms (SHA-3) with clear error codes |
| |
| #### R2: Session Management |
| - **R2.1**: Support incremental hash computation for large certificate chains and firmware images |
| - **R2.2**: Support multiple concurrent digest sessions (hardware-dependent capacity) |
| - **R2.3**: Provide session isolation between different SPDM and PLDM protocol flows |
| - **R2.4**: Automatic session cleanup to prevent resource exhaustion |
| - **R2.5**: Session timeout mechanism for abandoned operations |
| |
| #### R3: SPDM and PLDM Use Cases |
| - **R3.1**: Certificate chain verification (hash large X.509 certificate data) |
| - **R3.2**: Measurement verification (hash firmware measurement data) |
| - **R3.3**: Challenge-response authentication (compute transcript hashes) |
| - **R3.4**: Session key derivation (hash key exchange material) |
| - **R3.5**: Message authentication (hash SPDM message sequences) |
| - **R3.6**: PLDM firmware image integrity validation (hash received firmware chunks) |
| - **R3.7**: PLDM component image verification (validate assembled image against manifest digest) |
| - **R3.8**: PLDM signature verification support (hash image data for signature validation) |
| |
| #### R4: Performance and Resource Constraints |
| - **R4.1**: Memory-efficient operation suitable for embedded systems |
| - **R4.2**: Zero-copy data processing using Hubris leased memory |
| - **R4.3**: Deterministic resource allocation (no dynamic allocation) |
| - **R4.4**: Bounded execution time for real-time guarantees |
| |
| #### R5: Hardware Abstraction |
| - **R5.1**: Generic interface supporting any hardware digest accelerator |
| - **R5.2**: Mock implementation for testing and development |
| - **R5.3**: Type-safe hardware abstraction with compile-time verification |
| - **R5.4**: Consistent API regardless of underlying hardware |
| |
| #### R6: Error Handling and Reliability |
| - **R6.1**: Comprehensive error reporting for SPDM protocol diagnostics |
| - **R6.2**: Graceful handling of hardware failures |
| - **R6.3**: Session state validation and corruption detection |
| - **R6.4**: Clear error propagation to SPDM layer |
| |
| #### R7: Integration Requirements |
| - **R7.1**: Synchronous IPC interface compatible with Hubris task model |
| - **R7.2**: Idol-generated API stubs for type-safe inter-process communication |
| - **R7.3**: Integration with Hubris memory management and scheduling |
| - **R7.4**: No dependency on async runtime or futures |
| |
| #### R8: Supervisor Integration Requirements |
| - **R8.1**: Configure appropriate task disposition (Restart recommended for production) |
| - **R8.2**: SPDM clients handle task generation changes transparently (no complex recovery logic needed) |
| - **R8.3**: Digest server fails fast on unrecoverable hardware errors rather than returning complex error states |
| - **R8.4**: Support debugging via jefe external interface during development |
| |
| ## Design Overview |
| |
| This digest server provides a generic implementation that can work with any device implementing the required digest traits from `openprot-hal-blocking`. The design supports both single-context and multi-context hardware through hardware-adaptive session management. |
| |
| ## Architecture |
| |
| ### System Context |
| |
| ```mermaid |
| graph LR |
| subgraph "SPDM Client Task" |
| SC[SPDM Client] |
| SCV[• Certificate verification<br/>• Transcript hashing<br/>• Challenge-response<br/>• Key derivation] |
| end |
| |
| subgraph "PLDM Client Task" |
| PC[PLDM Firmware Update] |
| PCV[• Image integrity validation<br/>• Component verification<br/>• Signature validation<br/>• Running digest computation] |
| end |
| |
| subgraph "Digest Server" |
| DS[ServerImpl<D>] |
| DSV[• Session management<br/>• Generic implementation<br/>• Resource management<br/>• Error handling] |
| end |
| |
| subgraph "Hardware Backend" |
| HW[Hardware Device] |
| HWV[• MockDigestDevice<br/>• Actual HW accelerator<br/>• Any device with traits] |
| end |
| |
| SC ---|Synchronous<br/>IPC/Idol| DS |
| PC ---|Synchronous<br/>IPC/Idol| DS |
| DS ---|HAL Traits| HW |
| |
| SC -.-> SCV |
| PC -.-> PCV |
| DS -.-> DSV |
| HW -.-> HWV |
| ``` |
| |
| ### Component Architecture |
| |
| ``` |
| ServerImpl<D> |
| ├── Generic Type Parameter D |
| │ └── Trait Bounds: DigestInit<Sha2_256/384/512> |
| ├── Session Management |
| │ ├── Static session storage (hardware-dependent capacity) |
| │ ├── Session lifecycle (init → update → finalize) |
| │ └── Automatic timeout and cleanup |
| └── Hardware Abstraction |
| ├── Static dispatch (no runtime polymorphism) |
| ├── Algorithm-specific methods |
| └── Error translation layer |
| ``` |
| |
| ### Data Flow |
| |
| ``` |
| SPDM Client Request |
| ↓ |
| Idol-generated stub |
| ↓ |
| ServerImpl<D> method |
| ↓ |
| Session validation/allocation |
| ↓ |
| Hardware context management (save/restore) |
| ↓ |
| Direct hardware streaming |
| ↓ |
| Result processing |
| ↓ |
| Response to client |
| ``` |
| |
| ### Hardware-Adaptive Implementation |
| |
| #### Platform-Specific Trait Implementations |
| ```rust |
| // Single-context hardware (ASPEED HACE) - context management happens in OpContext |
| impl DigestInit<Sha2_256> for Ast1060HashDevice { |
| type OpContext<'a> = Ast1060DigestContext<'a> where Self: 'a; |
| type Output = Digest<8>; |
| |
| fn init<'a>(&'a mut self, _: Sha2_256) -> Result<Self::OpContext<'a>, Self::Error> { |
| // Direct hardware initialization - no session management needed |
| Ok(Ast1060DigestContext::new_sha256(self)) |
| } |
| } |
| |
| impl DigestOp for Ast1060DigestContext<'_> { |
| fn update(&mut self, data: &[u8]) -> Result<(), Self::Error> { |
| // Direct streaming to hardware - blocking until complete |
| self.hardware.stream_data(data) |
| } |
| |
| fn finalize(self) -> Result<Self::Output, Self::Error> { |
| // Complete and return result - hardware auto-resets |
| self.hardware.finalize_sha256() |
| } |
| } |
| |
| // Multi-context hardware (hypothetical) - context switching hidden in traits |
| impl DigestInit<Sha2_256> for MultiContextDevice { |
| type OpContext<'a> = MultiContextDigestContext<'a> where Self: 'a; |
| type Output = Digest<8>; |
| |
| fn init<'a>(&'a mut self, _: Sha2_256) -> Result<Self::OpContext<'a>, Self::Error> { |
| // Complex session allocation happens here, hidden from server |
| let context_id = self.allocate_hardware_context()?; |
| Ok(MultiContextDigestContext::new(self, context_id)) |
| } |
| } |
| |
| impl DigestOp for MultiContextDigestContext<'_> { |
| fn update(&mut self, data: &[u8]) -> Result<(), Self::Error> { |
| // Context switching happens transparently here |
| self.hardware.ensure_context_active(self.context_id)?; |
| self.hardware.stream_data(data) |
| } |
| } |
| ``` |
| |
| #### Hardware-Specific Processing Patterns |
| |
| **Single-Context Hardware (ASPEED HACE Pattern)** |
| ```mermaid |
| sequenceDiagram |
| participant C1 as SPDM Client |
| participant C2 as PLDM Client |
| participant DS as Digest Server |
| participant HW as ASPEED HACE |
| |
| Note over C1,HW: Clients naturally serialize via blocking IPC |
| |
| C1->>DS: init_sha256() |
| DS->>HW: Initialize SHA-256 (direct hardware access) |
| HW-->>DS: Context initialized |
| DS-->>C1: session_id = 1 |
| |
| par Client 2 blocks waiting |
| C2->>DS: init_sha384() (BLOCKS until C1 finishes) |
| end |
| |
| C1->>DS: update(session_id=1, data_chunk_1) |
| DS->>HW: Stream data directly to hardware |
| HW->>HW: Process data incrementally |
| HW-->>DS: Update complete |
| DS-->>C1: Success |
| |
| C1->>DS: finalize_sha256(session_id=1) |
| DS->>HW: Finalize computation |
| HW->>HW: Complete hash calculation |
| HW-->>DS: Final digest result |
| DS-->>C1: SHA-256 digest |
| |
| Note over DS,HW: Hardware available for next client |
| |
| DS->>HW: Initialize SHA-384 for Client 2 |
| HW-->>DS: Context initialized |
| DS-->>C2: session_id = 2 (C2 unblocks) |
| ``` |
| |
| **Multi-Context Hardware Pattern (Hypothetical)** |
| ```mermaid |
| sequenceDiagram |
| participant C1 as SPDM Client |
| participant C2 as PLDM Client |
| participant DS as Digest Server |
| participant HW as Multi-Context Hardware |
| participant RAM as Context Storage |
| |
| Note over C1,RAM: Complex session management with context switching |
| |
| C1->>DS: init_sha256() |
| DS->>HW: Initialize SHA-256 context |
| DS->>DS: current_session = 0 |
| DS-->>C1: session_id = 1 |
| |
| C1->>DS: update(session_id=1, data_chunk_1) |
| DS->>HW: Stream data to active context |
| HW-->>DS: Update complete |
| DS-->>C1: Success |
| |
| C2->>DS: init_sha384() |
| Note over DS,RAM: Context switching required |
| DS->>RAM: Save session 0 context (SHA-256 state) |
| DS->>HW: Initialize SHA-384 context |
| DS->>DS: current_session = 1 |
| DS-->>C2: session_id = 2 |
| |
| C2->>DS: update(session_id=2, data_chunk_2) |
| DS->>HW: Stream data to active context |
| HW-->>DS: Update complete |
| DS-->>C2: Success |
| |
| C1->>DS: update(session_id=1, data_chunk_3) |
| Note over DS,RAM: Switch back to session 0 |
| DS->>RAM: Save session 1 context (SHA-384 state) |
| DS->>RAM: Restore session 0 context (SHA-256 state) |
| DS->>HW: Load SHA-256 context to hardware |
| DS->>DS: current_session = 0 |
| DS->>HW: Stream data to restored context |
| HW-->>DS: Update complete |
| DS-->>C1: Success |
| |
| C1->>DS: finalize_sha256(session_id=1) |
| DS->>HW: Finalize computation |
| HW-->>DS: Final digest result |
| DS-->>C1: SHA-256 digest |
| DS->>DS: current_session = None |
| ``` |
| |
| ## IPC Interface Definition |
| |
| The digest server exposes its functionality through a Hubris Idol IPC interface that provides both session-based streaming operations and one-shot convenience methods. |
| |
| ### Idol Interface Specification |
| |
| ```rust |
| // digest.idol - Hubris IPC interface definition |
| Interface( |
| name: "Digest", |
| ops: { |
| // Session-based streaming operations (enabled by owned API) |
| "init_sha256": ( |
| args: {}, |
| reply: Result( |
| ok: "u32", // Returns session ID for the digest context |
| err: CLike("DigestError"), |
| ), |
| ), |
| "init_sha384": ( |
| args: {}, |
| reply: Result( |
| ok: "u32", // Returns session ID for the digest context |
| err: CLike("DigestError"), |
| ), |
| ), |
| "init_sha512": ( |
| args: {}, |
| reply: Result( |
| ok: "u32", // Returns session ID for the digest context |
| err: CLike("DigestError"), |
| ), |
| ), |
| "update": ( |
| args: { |
| "session_id": "u32", |
| "len": "u32", |
| }, |
| leases: { |
| "data": (type: "[u8]", read: true, max_len: Some(1024)), |
| }, |
| reply: Result( |
| ok: "()", |
| err: CLike("DigestError"), |
| ), |
| ), |
| "finalize_sha256": ( |
| args: { |
| "session_id": "u32", |
| }, |
| leases: { |
| "digest_out": (type: "[u32; 8]", write: true), |
| }, |
| reply: Result( |
| ok: "()", |
| err: CLike("DigestError"), |
| ), |
| ), |
| "finalize_sha384": ( |
| args: { |
| "session_id": "u32", |
| }, |
| leases: { |
| "digest_out": (type: "[u32; 12]", write: true), |
| }, |
| reply: Result( |
| ok: "()", |
| err: CLike("DigestError"), |
| ), |
| ), |
| "finalize_sha512": ( |
| args: { |
| "session_id": "u32", |
| }, |
| leases: { |
| "digest_out": (type: "[u32; 16]", write: true), |
| }, |
| reply: Result( |
| ok: "()", |
| err: CLike("DigestError"), |
| ), |
| ), |
| "reset": ( |
| args: { |
| "session_id": "u32", |
| }, |
| reply: Result( |
| ok: "()", |
| err: CLike("DigestError"), |
| ), |
| ), |
| |
| // One-shot convenience operations (using scoped API internally) |
| "digest_oneshot_sha256": ( |
| args: { |
| "len": "u32", |
| }, |
| leases: { |
| "data": (type: "[u8]", read: true, max_len: Some(1024)), |
| "digest_out": (type: "[u32; 8]", write: true), |
| }, |
| reply: Result( |
| ok: "()", |
| err: CLike("DigestError"), |
| ), |
| ), |
| "digest_oneshot_sha384": ( |
| args: { |
| "len": "u32", |
| }, |
| leases: { |
| "data": (type: "[u8]", read: true, max_len: Some(1024)), |
| "digest_out": (type: "[u32; 12]", write: true), |
| }, |
| reply: Result( |
| ok: "()", |
| err: CLike("DigestError"), |
| ), |
| ), |
| "digest_oneshot_sha512": ( |
| args: { |
| "len": "u32", |
| }, |
| leases: { |
| "data": (type: "[u8]", read: true, max_len: Some(1024)), |
| "digest_out": (type: "[u32; 16]", write: true), |
| }, |
| reply: Result( |
| ok: "()", |
| err: CLike("DigestError"), |
| ), |
| ), |
| }, |
| ) |
| ``` |
| |
| ### IPC Design Rationale |
| |
| #### Session-Based Operations |
| - **init_sha256/384/512()**: Creates new session using owned API, returns session ID for storage |
| - **update(session_id, data)**: Updates specific session using move-based context operations |
| - **finalize_sha256/384/512(session_id)**: Completes session and recovers controller for reuse |
| - **reset(session_id)**: Cancels session early and recovers controller |
| |
| #### One-Shot Operations |
| - **digest_oneshot_sha256/384/512()**: Complete digest computation in single IPC call using scoped API |
| - **Convenience methods**: For simple use cases that don't need streaming |
| |
| #### Zero-Copy Data Transfer |
| - **Leased memory**: All data transfer uses Hubris leased memory system |
| - **Read leases**: Input data (`data`) passed by reference, no copying |
| - **Write leases**: Output digests (`digest_out`) written directly to client memory |
| - **Bounded transfers**: Maximum 1024 bytes per update for deterministic behavior |
| |
| #### Type Safety |
| - **Algorithm-specific finalize**: `finalize_sha256` only works with SHA-256 sessions |
| - **Sized output arrays**: `[u32; 8]` for SHA-256, `[u32; 12]` for SHA-384, `[u32; 16]` for SHA-512 |
| - **Session validation**: Invalid session IDs return `DigestError::InvalidSession` |
| |
| ### IPC Usage Patterns |
| |
| #### SPDM Certificate Verification (Streaming) |
| ```rust |
| // Client code using generated Idol stubs |
| let digest = Digest::from(DIGEST_SERVER_TASK_ID); |
| |
| let session_id = digest.init_sha256()?; |
| for chunk in certificate_data.chunks(1024) { |
| digest.update(session_id, chunk.len() as u32, chunk)?; |
| } |
| let mut cert_hash = [0u32; 8]; |
| digest.finalize_sha256(session_id, &mut cert_hash)?; |
| ``` |
| |
| #### Simple Hash Computation (One-Shot) |
| ```rust |
| // Client code for simple operations |
| let digest = Digest::from(DIGEST_SERVER_TASK_ID); |
| let mut hash_output = [0u32; 8]; |
| digest.digest_oneshot_sha256(data.len() as u32, data, &mut hash_output)?; |
| ``` |
| |
| ## Detailed Design |
| |
| ### Session Model |
| |
| #### Session Lifecycle |
| ``` |
| ┌─────────┐ init_sha256/384/512() ┌─────────┐ |
| │ FREE │ ────────────────────────→ │ ACTIVE │ |
| │ │ │ │ |
| └─────────┘ └─────────┘ |
| ↑ │ |
| │ finalize_sha256/384/512() │ update(data) |
| │ reset() │ (stream to hardware) |
| │ timeout_cleanup() │ |
| └───────────────────────────────────────┘ |
| ``` |
| |
| #### Hardware-Specific Session Management |
| |
| Different hardware platforms have varying capabilities for concurrent session support: |
| |
| ```rust |
| // Platform-specific capability trait |
| pub trait DigestHardwareCapabilities { |
| const MAX_CONCURRENT_SESSIONS: usize; |
| const SUPPORTS_HARDWARE_CONTEXT_SWITCHING: bool; |
| } |
| |
| // AST1060 implementation - single session, simple and efficient |
| impl DigestHardwareCapabilities for Ast1060HashDevice { |
| const MAX_CONCURRENT_SESSIONS: usize = 1; // Work with hardware, not against it |
| const SUPPORTS_HARDWARE_CONTEXT_SWITCHING: bool = false; |
| } |
| |
| // Example hypothetical multi-context implementation |
| impl DigestHardwareCapabilities for HypotheticalMultiContextDevice { |
| const MAX_CONCURRENT_SESSIONS: usize = 16; // Hardware-dependent capacity |
| const SUPPORTS_HARDWARE_CONTEXT_SWITCHING: bool = true; |
| } |
| |
| // Generic server implementation |
| pub struct ServerImpl<D: DigestHardwareCapabilities> { |
| sessions: FnvIndexMap<u32, DigestSession, {D::MAX_CONCURRENT_SESSIONS}>, |
| hardware: D, |
| next_session_id: u32, |
| } |
| |
| pub struct DigestSession { |
| algorithm: SessionAlgorithm, |
| timeout: Option<u64>, |
| // Hardware-specific context data only if supported |
| } |
| ``` |
| |
| ### Generic Hardware Abstraction with Platform-Adaptive Session Management |
| |
| #### Trait Requirements |
| The server is generic over type `D` where: |
| ```rust |
| D: DigestInit<Sha2_256> + DigestInit<Sha2_384> + DigestInit<Sha2_512> + ErrorType |
| ``` |
| |
| With the actual `openprot-hal-blocking` trait structure: |
| ```rust |
| // Hardware device implements DigestInit for each algorithm |
| impl DigestInit<Sha2_256> for MyDigestDevice { |
| type OpContext<'a> = MyDigestContext<'a> where Self: 'a; |
| type Output = Digest<8>; |
| |
| fn init<'a>(&'a mut self, _: Sha2_256) -> Result<Self::OpContext<'a>, Self::Error> { |
| // All hardware complexity (context management, save/restore) handled here |
| } |
| } |
| |
| // The context handles streaming operations |
| impl DigestOp for MyDigestContext<'_> { |
| type Output = Digest<8>; |
| |
| fn update(&mut self, data: &[u8]) -> Result<(), Self::Error> { |
| // Hardware-specific streaming implementation |
| // Context switching (if needed) happens transparently |
| } |
| |
| fn finalize(self) -> Result<Self::Output, Self::Error> { |
| // Complete digest computation |
| // Context cleanup happens automatically |
| } |
| } |
| ``` |
| |
| #### Hardware-Adaptive Architecture |
| - **Single-Context Hardware**: Direct operations, clients naturally serialize via blocking IPC |
| - **Multi-Context Hardware**: Native hardware session switching when supported |
| - **Compile-time optimization**: Session management code only included when needed |
| - **Platform-specific limits**: `MAX_CONCURRENT_SESSIONS` based on hardware capabilities |
| - **Synchronous IPC alignment**: Works naturally with Hubris blocking message passing |
| |
| #### Concurrency Patterns by Hardware Type |
| |
| **Single-Context Hardware (ASPEED HACE):** |
| ``` |
| Client A calls init_sha256() → Blocks until complete → Returns session_id |
| Client B calls init_sha384() → Blocks waiting for A to finish → Still blocked |
| Client A calls update(session_id) → Blocks until complete → Returns success |
| Client B calls update(session_id) → Still blocked waiting for A to finalize |
| Client A calls finalize() → Releases hardware → Client B can now proceed |
| ``` |
| |
| **Multi-Context Hardware (Hypothetical):** |
| ``` |
| Client A calls init_sha256() → Creates session context → Returns immediately |
| Client B calls init_sha384() → Creates different context → Returns immediately |
| Client A calls update(session_id) → Uses session context → Returns immediately |
| Client B calls update(session_id) → Uses different context → Returns immediately |
| ``` |
| |
| #### Session Management Flow (Hardware-Dependent) |
| ``` |
| Single-Context Hardware: Direct Operation → Hardware → Result |
| Multi-Context Hardware: Session Request → Hardware Context → Process → Save Context → Result |
| ``` |
| |
| #### Static Dispatch Pattern |
| - **Compile-time algorithm selection**: No runtime algorithm switching |
| - **Type safety**: Associated type constraints ensure output size compatibility |
| - **Zero-cost abstraction**: No virtual function calls or dynamic dispatch |
| - **Hardware flexibility**: Any device implementing the traits can be used |
| |
| ### Memory Management |
| |
| #### Static Allocation Strategy (Hardware-Adaptive) |
| ```rust |
| // Session storage sized based on hardware capabilities |
| static mut SESSION_STORAGE: [SessionData; D::MAX_CONCURRENT_SESSIONS] = [...]; |
| ``` |
| - **Hardware-aligned limits**: Session count matches hardware capabilities |
| - **Single-context optimization**: No session overhead for simple hardware |
| - **Multi-context support**: Full session management when hardware supports it |
| - **Deterministic memory usage**: No dynamic allocation |
| - **Real-time guarantees**: Bounded memory access patterns |
| |
| #### Hardware-Adaptive Data Flow |
| - **Zero-copy IPC**: Uses Hubris leased memory system |
| - **Platform optimization**: Direct operations for single-context hardware |
| - **Session management**: Only when hardware supports multiple contexts |
| - **Bounded updates**: Maximum 1024 bytes per update call (hardware limitation) |
| - **Memory safety**: All buffer accesses bounds-checked |
| - **Synchronous semantics**: Natural blocking behavior with Hubris IPC |
| |
| #### Platform-Specific Processing |
| ``` |
| Single-Context: Client Request → Direct Hardware → Result → Client Response |
| Multi-Context: Client Request → Session Management → Hardware Context → Result → Client Response |
| ``` |
| |
| ### Error Handling Strategy |
| |
| #### Hardware-Adaptive Error Model |
| ``` |
| Hardware Layer Error → DigestError → RequestError<DigestError> → Client Response |
| ``` |
| |
| #### Platform-Specific Error Categories |
| - **Hardware failures**: `DigestError::HardwareFailure` (all platforms) |
| - **Session management**: `DigestError::InvalidSession`, `DigestError::TooManySessions` (multi-context only) |
| - **Input validation**: `DigestError::InvalidInputLength` (hardware-specific limits) |
| - **Algorithm support**: `DigestError::UnsupportedAlgorithm` (capability-dependent) |
| |
| ### Hardware-Adaptive Session Architecture |
| |
| Instead of imposing a complex context management layer, the digest server adapts to hardware capabilities: |
| |
| ```mermaid |
| graph TB |
| subgraph "Single-Context Hardware (ASPEED HACE)" |
| SC1[Client Request] |
| SC2[Direct Hardware Operation] |
| SC3[Immediate Response] |
| SC1 --> SC2 --> SC3 |
| end |
| |
| subgraph "Multi-Context Hardware (Hypothetical)" |
| MC1[Session Pool] |
| MC2[Context Scheduler] |
| MC3[Hardware Contexts] |
| MC4[Session Management] |
| MC1 --> MC2 --> MC3 --> MC4 |
| end |
| ``` |
| |
| #### Hardware Capability Detection |
| |
| The digest server adapts to different hardware capabilities through compile-time trait bounds: |
| |
| ```rust |
| pub trait DigestHardwareCapabilities { |
| const MAX_CONCURRENT_SESSIONS: usize; // Hardware-dependent: 1 for single-context, 16+ for multi-context |
| const SUPPORTS_CONTEXT_SWITCHING: bool; |
| const MAX_UPDATE_SIZE: usize; |
| } |
| ``` |
| |
| Examples of hardware-specific session limits: |
| - **ASPEED AST1060**: `MAX_CONCURRENT_SESSIONS = 1` (single hardware context) |
| - **Multi-context accelerators**: `MAX_CONCURRENT_SESSIONS = 16` (or higher based on hardware design) |
| - **Software implementations**: Can support many concurrent sessions limited by memory |
| |
| #### Session Management Strategy |
| - **Single-context platforms**: Direct hardware operations, no session state |
| - **Multi-context platforms**: Full session management with context switching |
| - **Compile-time optimization**: Dead code elimination for unused features |
| |
| 3. **Context Initialization**: When starting new session |
| ```rust |
| #### Clean Server Implementation |
| |
| With proper trait encapsulation, the server implementation becomes much simpler: |
| |
| ```rust |
| impl<D> ServerImpl<D> |
| where |
| D: DigestInit<Sha2_256> + DigestInit<Sha2_384> + DigestInit<Sha2_512> + ErrorType |
| { |
| fn update_session(&mut self, session_id: u32, data: &[u8]) -> Result<(), DigestError> { |
| let session = self.get_session_mut(session_id)?; |
| |
| // Generic trait call - all hardware complexity hidden |
| session.op_context.update(data) |
| .map_err(|_| DigestError::HardwareFailure)?; |
| |
| Ok(()) |
| } |
| |
| fn finalize_session(&mut self, session_id: u32) -> Result<DigestOutput, DigestError> { |
| let session = self.take_session(session_id)?; |
| |
| // Trait handles finalization and automatic cleanup |
| session.op_context.finalize() |
| .map_err(|_| DigestError::HardwareFailure) |
| } |
| } |
| ``` |
| |
| #### Hardware Complexity Encapsulation |
| - **No save/restore methods**: All context management hidden in trait implementations |
| - **No platform-specific code**: Server only calls generic trait methods |
| - **Automatic optimization**: Single-context hardware avoids unnecessary overhead |
| - **Transparent complexity**: Multi-context hardware handles switching internally |
| |
| ### Concurrency Model |
| |
| #### Session Isolation |
| - Each session operates independently |
| - No shared mutable state between sessions |
| - Session IDs provide access control |
| - Timeout mechanism prevents resource leaks |
| |
| #### SPDM and PLDM Integration Points |
| 1. **SPDM Certificate Verification**: Hash certificate chains incrementally |
| 2. **SPDM Transcript Computation**: Hash sequences of SPDM messages |
| 3. **SPDM Challenge Processing**: Compute authentication hashes |
| 4. **SPDM Key Derivation**: Hash key exchange material |
| 5. **PLDM Firmware Integrity**: Hash received firmware image chunks during transfer |
| 6. **PLDM Component Validation**: Verify assembled components against manifest digests |
| 7. **PLDM Multi-Component Updates**: Concurrent digest computation for multiple firmware components |
| |
| ## Failure Scenarios |
| |
| ### Session Management Failures |
| |
| #### Session Exhaustion Scenarios |
| |
| **Single-Context Hardware (ASPEED HACE) - No Exhaustion Possible** |
| ```mermaid |
| sequenceDiagram |
| participant S1 as SPDM Client 1 |
| participant S2 as SPDM Client 2 |
| participant DS as Digest Server |
| participant HW as ASPEED HACE |
| |
| Note over DS,HW: Hardware only supports one active session |
| |
| S1->>DS: init_sha256() |
| DS->>HW: Direct hardware initialization |
| DS-->>S1: session_id = 1 |
| |
| S2->>DS: init_sha384() (BLOCKS on IPC until S1 finishes) |
| Note over S2: Client automatically waits - no error needed |
| |
| S1->>DS: finalize_sha256(session_id=1) |
| DS->>HW: Complete and release hardware |
| DS-->>S1: digest result |
| |
| Note over DS,HW: Hardware now available |
| DS->>HW: Initialize SHA-384 for S2 |
| DS-->>S2: session_id = 2 (S2 unblocks) |
| ``` |
| |
| **Multi-Context Hardware (Hypothetical) - True Session Exhaustion** |
| ```mermaid |
| sequenceDiagram |
| participant S1 as Client 1 |
| participant S2 as Client 9 |
| participant DS as Digest Server |
| participant HW as Multi-Context Hardware |
| |
| Note over DS: Hardware capacity reached, all contexts active |
| |
| S2->>DS: init_sha256() |
| DS->>DS: find_free_hardware_context() |
| DS-->>S2: Error: TooManySessions |
| |
| Note over S2: Client must wait for context to free up |
| S2->>DS: init_sha256() (retry after delay) |
| DS->>HW: Allocate available hardware context |
| DS-->>S2: session_id = 9 |
| ``` |
| |
| #### Session Timeout Recovery |
| ```mermaid |
| sequenceDiagram |
| participant SC as SPDM Client |
| participant DS as Digest Server |
| participant T as Timer |
| |
| SC->>DS: init_sha256() |
| DS-->>SC: session_id = 3 |
| |
| Note over T: 10,000 ticks pass |
| T->>DS: timer_tick |
| DS->>DS: cleanup_expired_sessions() |
| DS->>DS: session[3].timeout expired |
| DS->>DS: session[3] = FREE |
| |
| SC->>DS: update(session_id=3, data) |
| DS->>DS: validate_session(3) |
| DS-->>SC: Error: InvalidSession |
| |
| Note over SC: Client must reinitialize |
| SC->>DS: init_sha256() |
| DS-->>SC: session_id = 3 (reused) |
| ``` |
| |
| ### Hardware Failure Scenarios |
| |
| #### Hardware Device Failure |
| ```mermaid |
| flowchart TD |
| A[SPDM/PLDM Client Request] --> B[Digest Server] |
| B --> C{Hardware Available?} |
| |
| C -->|Yes| D[Call hardware.init] |
| C -->|No| E[panic! - Hardware unavailable] |
| |
| D --> F{Hardware Response} |
| F -->|Success| G[Process normally] |
| F -->|Error| H[panic! - Hardware failure] |
| |
| G --> I[Return result to client] |
| E --> J[Task fault → Jefe supervision] |
| H --> J |
| |
| style E fill:#ffcccc |
| style H fill:#ffcccc |
| style J fill:#fff2cc |
| ``` |
| |
| |
| ### Resource Exhaustion Scenarios |
| |
| #### Memory Pressure Handling |
| ```mermaid |
| flowchart LR |
| A[Large Data Update] --> B{Buffer Space Available?} |
| |
| B -->|Yes| C[Accept data into session buffer] |
| B -->|No| D[Return InvalidInputLength] |
| |
| C --> E{Session Buffer Full?} |
| E -->|No| F[Continue accepting updates] |
| E -->|Yes| G[Client must finalize before more updates] |
| |
| D --> H[Client must use smaller chunks] |
| G --> I[finalize_sha256/384/512] |
| H --> J[Retry with smaller data] |
| |
| style D fill:#ffcccc |
| style G fill:#fff2cc |
| style H fill:#ccffcc |
| ``` |
| |
| #### Session Lifecycle Error States |
| ```mermaid |
| stateDiagram-v2 |
| [*] --> FREE |
| FREE --> ACTIVE_SHA256: init_sha256() + hardware context init |
| FREE --> ACTIVE_SHA384: init_sha384() + hardware context init |
| FREE --> ACTIVE_SHA512: init_sha512() + hardware context init |
| |
| ACTIVE_SHA256 --> ACTIVE_SHA256: update(data) → stream to hardware |
| ACTIVE_SHA384 --> ACTIVE_SHA384: update(data) → stream to hardware |
| ACTIVE_SHA512 --> ACTIVE_SHA512: update(data) → stream to hardware |
| |
| ACTIVE_SHA256 --> FREE: finalize_sha256() → hardware result |
| ACTIVE_SHA384 --> FREE: finalize_sha384() → hardware result |
| ACTIVE_SHA512 --> FREE: finalize_sha512() → hardware result |
| |
| ACTIVE_SHA256 --> FREE: reset() + context cleanup |
| ACTIVE_SHA384 --> FREE: reset() + context cleanup |
| ACTIVE_SHA512 --> FREE: reset() + context cleanup |
| |
| ACTIVE_SHA256 --> FREE: timeout + context cleanup |
| ACTIVE_SHA384 --> FREE: timeout + context cleanup |
| ACTIVE_SHA512 --> FREE: timeout + context cleanup |
| |
| state ERROR_STATES { |
| [*] --> InvalidSession: Wrong session ID |
| [*] --> WrongAlgorithm: finalize_sha384() on SHA256 session |
| [*] --> ContextSwitchError: Hardware context save/restore failure |
| [*] --> HardwareError: Hardware streaming failure |
| } |
| |
| ACTIVE_SHA256 --> ERROR_STATES: Error conditions |
| ACTIVE_SHA384 --> ERROR_STATES: Error conditions |
| ACTIVE_SHA512 --> ERROR_STATES: Error conditions |
| ``` |
| |
| ### SPDM Protocol Impact Analysis |
| |
| #### Certificate Verification Failure Recovery |
| |
| **Single-Context Hardware (ASPEED HACE) - No Session Exhaustion** |
| ```mermaid |
| sequenceDiagram |
| participant SPDM as SPDM Protocol |
| participant DS as Digest Server |
| participant HW as ASPEED HACE |
| |
| SPDM->>DS: verify_certificate_chain() |
| DS->>HW: Direct hardware operation (blocks until complete) |
| HW-->>DS: Certificate hash result |
| DS-->>SPDM: Success |
| |
| Note over SPDM: No session management complexity needed |
| ``` |
| |
| **Multi-Context Hardware (Hypothetical) - True Session Management** |
| ```mermaid |
| sequenceDiagram |
| participant SPDM as SPDM Protocol |
| participant DS as Digest Server |
| participant HW as Multi-Context Hardware |
| |
| SPDM->>DS: verify_certificate_chain() |
| |
| alt Hardware context available |
| DS->>HW: Allocate context and process |
| HW-->>DS: Certificate hash result |
| DS-->>SPDM: Success |
| else All contexts busy |
| DS-->>SPDM: Error: TooManySessions |
| Note over SPDM: Client retry logic or wait |
| SPDM->>DS: verify_certificate_chain() (retry) |
| DS-->>SPDM: Success (context now available) |
| end |
| ``` |
| |
| #### Transcript Hash Failure Impact |
| ```mermaid |
| flowchart TD |
| A[SPDM Message Exchange] --> B[Compute Transcript Hash] |
| B --> C{Digest Server Available?} |
| |
| C -->|Yes| D[Normal transcript computation] |
| C -->|No| E[Digest server failure] |
| |
| E --> F{Failure Type} |
| F -->|Session Exhausted| G[Retry with backoff] |
| F -->|Hardware Failure| H[Abort authentication] |
| F -->|Timeout| I[Reinitialize session] |
| |
| G --> J{Retry Successful?} |
| J -->|Yes| D |
| J -->|No| K[Authentication failure] |
| |
| H --> K |
| I --> L{Reinit Successful?} |
| L -->|Yes| D |
| L -->|No| K |
| |
| D --> M[Continue SPDM protocol] |
| K --> N[Report to security policy] |
| |
| style E fill:#ffcccc |
| style K fill:#ff9999 |
| style N fill:#ffcccc |
| ``` |
| |
| ### Failure Recovery Strategies |
| |
| #### Error Propagation Chain |
| ```mermaid |
| flowchart LR |
| HW[Hardware Layer] -->|Any Error| PANIC[Task Panic] |
| |
| DS[Digest Server] -->|Recoverable DigestError| RE[RequestError wrapper] |
| RE -->|IPC| CLIENTS[SPDM/PLDM Clients] |
| CLIENTS -->|Simple Retry| POL[Security Policy] |
| |
| PANIC -->|Task Fault| JEFE[Jefe Supervisor] |
| JEFE -->|Task Restart| DS_NEW[Fresh Digest Server] |
| DS_NEW -->|Next IPC| CLIENTS |
| |
| subgraph "Recoverable Error Types" |
| E1[InvalidSession] |
| E2[TooManySessions] |
| E3[InvalidInputLength] |
| end |
| |
| subgraph "Simple Client Recovery" |
| R1[Session Cleanup] |
| R2[Retry with Backoff] |
| R3[Use One-shot API] |
| R4[Authentication Failure] |
| end |
| |
| DS --> E1 |
| DS --> E2 |
| DS --> E3 |
| |
| CLIENTS --> R1 |
| CLIENTS --> R2 |
| CLIENTS --> R3 |
| CLIENTS --> R4 |
| |
| style PANIC fill:#ffcccc |
| style DS_NEW fill:#ccffcc |
| ``` |
| |
| #### System-Level Failure Handling |
| ```mermaid |
| graph TB |
| subgraph "Digest Server Internal Failures" |
| F1[Session Exhaustion] |
| F2[Recoverable Hardware Failure] |
| F3[Input Validation Errors] |
| end |
| |
| subgraph "Task-Level Failures" |
| T1[Unrecoverable Hardware Failure] |
| T2[Memory Corruption] |
| T3[Syscall Faults] |
| T4[Explicit Panics] |
| end |
| |
| subgraph "SPDM Client Responses" |
| S1[Retry with Backoff] |
| S2[Fallback to One-shot] |
| S3[Graceful Degradation] |
| S4[Abort Authentication] |
| end |
| |
| subgraph "Jefe Supervisor Actions" |
| J1[Task Restart - Restart Disposition] |
| J2[Hold for Debug - Hold Disposition] |
| J3[Log Fault Information] |
| J4[External Debug Interface] |
| end |
| |
| subgraph "System-Level Responses" |
| R1[Continue with Fresh Task Instance] |
| R2[Debug Analysis Mode] |
| R3[System Reboot - Jefe Fault] |
| end |
| |
| F1 --> S1 |
| F2 --> S1 |
| F3 --> S4 |
| |
| T1 --> J1 |
| T2 --> J1 |
| T3 --> J1 |
| T4 --> J1 |
| |
| J1 --> R1 |
| J2 --> R2 |
| |
| S1 --> R1 |
| S2 --> R1 |
| S3 --> R1 |
| |
| R2 --> R3 |
| R1 --> R4 |
| R2 --> R4 |
| ``` |
| |
| ## Supervisor Integration and System-Level Failure Handling |
| |
| ### Jefe Supervisor Role |
| |
| The digest server operates under the supervision of Hubris OS's supervisor task ("jefe"), which provides system-level failure management beyond the server's internal error handling. |
| |
| #### Supervisor Architecture |
| ```mermaid |
| graph TB |
| subgraph "Supervisor Domain (Priority 0)" |
| JEFE[Jefe Supervisor Task] |
| JEFE_FEATURES[• Fault notification handling<br/>• Task restart decisions<br/>• Debugging interface<br/>• System restart capability] |
| end |
| |
| subgraph "Application Domain" |
| DS[Digest Server] |
| SPDM[SPDM Client] |
| OTHER[Other Tasks] |
| end |
| |
| KERNEL[Hubris Kernel] -->|Fault Notifications| JEFE |
| JEFE -->|reinit_task| KERNEL |
| JEFE -->|system_restart| KERNEL |
| |
| DS -.->|Task Fault| KERNEL |
| SPDM -.->|Task Fault| KERNEL |
| OTHER -.->|Task Fault| KERNEL |
| |
| JEFE -.-> JEFE_FEATURES |
| ``` |
| |
| #### Task Disposition Management |
| |
| Each task, including the digest server, has a configured disposition that determines jefe's response to failures: |
| |
| - **Restart Disposition**: Automatic recovery via `kipc::reinit_task()` |
| - **Hold Disposition**: Task remains faulted for debugging inspection |
| |
| #### Failure Escalation Hierarchy |
| |
| ```mermaid |
| sequenceDiagram |
| participant HW as Hardware |
| participant DS as Digest Server |
| participant SPDM as SPDM Client |
| participant K as Kernel |
| participant JEFE as Jefe Supervisor |
| |
| Note over DS: Fail immediately on any hardware failure |
| HW->>DS: Hardware fault |
| DS->>DS: panic!("Hardware failure detected") |
| DS->>K: Task fault occurs |
| K->>JEFE: Fault notification (bit 0) |
| |
| JEFE->>K: find_faulted_task() |
| K-->>JEFE: task_index (digest server) |
| |
| alt Restart disposition (production) |
| JEFE->>K: reinit_task(digest_server, true) |
| K->>DS: Task reinitialized with fresh hardware state |
| Note over SPDM: Next IPC gets fresh task, no special handling needed |
| else Hold disposition (debug) |
| JEFE->>JEFE: Mark holding_fault = true |
| Note over DS: Task remains faulted for debugging |
| Note over SPDM: IPC returns generation mismatch error |
| end |
| ``` |
| |
| ### System Failure Categories and Responses |
| |
| #### Recoverable Failures (Handled by Digest Server) |
| - **Session Management**: `TooManySessions`, `InvalidSession` → Return error to client |
| - **Input Validation**: `InvalidInputLength` → Return error to client |
| |
| #### Task-Level Failures (Handled by Jefe) |
| - **Any Hardware Failure**: Hardware errors of any kind → Task panic → Jefe restart |
| - **Hardware Resource Exhaustion**: Hardware cannot allocate resources → Task panic → Jefe restart |
| - **Memory Corruption**: Stack overflow, heap corruption → Task fault → Jefe restart |
| - **Syscall Faults**: Invalid kernel IPC usage → Task fault → Jefe restart |
| - **Explicit Panics**: `panic!()` in digest server code → Task fault → Jefe restart |
| |
| #### System-Level Failures (Handled by Kernel) |
| - **Supervisor Fault**: Jefe task failure → System reboot |
| - **Kernel Panic**: Critical kernel failure → System reset |
| - **Watchdog Timeout**: System hang detection → Hardware reset |
| |
| **Key Design Principle**: The digest server fails immediately on any hardware error without attempting recovery. This maximally simplifies the implementation and ensures consistent system behavior through jefe's supervision. |
| |
| ### External Debugging Interface |
| |
| Jefe provides an external interface for debugging digest server failures: |
| |
| ```rust |
| // External control commands available via debugger (Humility) |
| enum JefeRequest { |
| Hold, // Stop automatic restart of digest server |
| Start, // Manually restart digest server |
| Release, // Resume automatic restart behavior |
| Fault, // Force digest server to fault for testing |
| } |
| ``` |
| |
| This enables development workflows like: |
| 1. **Hold faulting server**: Examine failure state without automatic restart |
| 2. **Analyze dump data**: Extract task memory and register state |
| 3. **Test recovery**: Manually trigger restart after fixes |
| 4. **Fault injection**: Test SPDM client resilience |
| |
| ### Integration Requirements Update |
| |
| #### R8: Supervisor Integration Requirements |
| - **R8.1**: Configure appropriate task disposition (Restart recommended for production) |
| - **R8.2**: SPDM clients handle task generation changes transparently (no complex recovery logic needed) |
| - **R8.3**: Digest server fails fast on unrecoverable hardware errors rather than returning complex error states |
| - **R8.4**: Support debugging via jefe external interface during development |
| |
| ## SPDM Integration Examples |
| |
| ### Certificate Chain Verification (Requirement R3.1) |
| ```rust |
| // SPDM task verifying a certificate chain |
| fn verify_certificate_chain(&mut self, cert_chain: &[u8]) -> Result<bool, SpdmError> { |
| let digest = Digest::from(DIGEST_SERVER_TASK_ID); |
| |
| // Create session for certificate hash (R2.1: incremental computation) |
| let session_id = digest.init_sha256()?; // R1.1: SHA-256 support |
| |
| // Process certificate data incrementally (R4.2: zero-copy processing) |
| for chunk in cert_chain.chunks(512) { |
| digest.update(session_id, chunk.len() as u32, chunk)?; |
| } |
| |
| // Get final certificate hash |
| let mut cert_hash = [0u32; 8]; |
| digest.finalize_sha256(session_id, &mut cert_hash)?; |
| |
| // Verify against policy |
| self.verify_hash_against_policy(&cert_hash) |
| } |
| ``` |
| |
| ### SPDM Transcript Hash Computation (Requirement R3.3) |
| ```rust |
| // Computing hash of SPDM message sequence for authentication |
| fn compute_transcript_hash(&mut self, messages: &[SpdmMessage]) -> Result<[u32; 8], SpdmError> { |
| let digest = Digest::from(DIGEST_SERVER_TASK_ID); |
| let session_id = digest.init_sha256()?; // R2.3: session isolation |
| |
| // Hash all messages in the SPDM transcript (R3.5: message authentication) |
| for msg in messages { |
| let msg_bytes = msg.serialize()?; |
| digest.update(session_id, msg_bytes.len() as u32, &msg_bytes)?; |
| } |
| |
| let mut transcript_hash = [0u32; 8]; |
| digest.finalize_sha256(session_id, &mut transcript_hash)?; // R7.1: synchronous IPC |
| Ok(transcript_hash) |
| } |
| ``` |
| |
| ### Sequential SPDM Operations (Requirement R2.1) |
| ```rust |
| // SPDM task performing sequential operations using incremental hashing |
| impl SpdmResponder { |
| fn handle_certificate_and_transcript(&mut self, cert_data: &[u8], messages: &[SpdmMessage]) -> Result<(), SpdmError> { |
| let digest = Digest::from(DIGEST_SERVER_TASK_ID); |
| |
| // Operation 1: Certificate verification (R2.1: incremental computation) |
| let cert_session = digest.init_sha256()?; // R1.1: SHA-256 support |
| |
| // Process certificate incrementally |
| for chunk in cert_data.chunks(512) { |
| digest.update(cert_session, chunk.len() as u32, chunk)?; |
| } |
| |
| let mut cert_hash = [0u32; 8]; |
| digest.finalize_sha256(cert_session, &mut cert_hash)?; |
| |
| // Operation 2: Transcript hash computation (sequential, after cert verification) |
| let transcript_session = digest.init_sha256()?; // R2.3: new isolated session |
| |
| // Hash all SPDM messages in sequence |
| for msg in messages { |
| let msg_bytes = msg.serialize()?; |
| digest.update(transcript_session, msg_bytes.len() as u32, &msg_bytes)?; |
| } |
| |
| let mut transcript_hash = [0u32; 8]; |
| digest.finalize_sha256(transcript_session, &mut transcript_hash)?; |
| |
| // Use both hashes for SPDM protocol |
| self.process_verification_results(&cert_hash, &transcript_hash) |
| } |
| } |
| ``` |
| |
| ## PLDM Integration Examples |
| |
| ### PLDM Firmware Image Integrity Validation (Requirement R3.6) |
| ```rust |
| // PLDM task validating received firmware chunks |
| fn validate_firmware_image(&mut self, image_chunks: &[&[u8]], expected_digest: &[u32; 8]) -> Result<bool, PldmError> { |
| let digest = Digest::from(DIGEST_SERVER_TASK_ID); |
| |
| // Create session for running digest computation (R2.1: incremental computation) |
| let session_id = digest.init_sha256()?; // R1.1: SHA-256 commonly used in PLDM |
| |
| // Process firmware image incrementally as chunks are received (R4.2: zero-copy processing) |
| for chunk in image_chunks { |
| digest.update(session_id, chunk.len() as u32, chunk)?; |
| } |
| |
| // Get final image digest |
| let mut computed_digest = [0u32; 8]; |
| digest.finalize_sha256(session_id, &mut computed_digest)?; |
| |
| // Compare with manifest digest |
| Ok(computed_digest == *expected_digest) |
| } |
| ``` |
| |
| ### PLDM Component Verification During Transfer (Requirement R3.7) |
| ```rust |
| // PLDM task computing running digest during TransferFirmware |
| fn transfer_firmware_with_validation(&mut self, component_id: u16) -> Result<(), PldmError> { |
| let digest = Digest::from(DIGEST_SERVER_TASK_ID); |
| |
| // Initialize digest session for this component transfer (R2.3: session isolation) |
| let session_id = digest.init_sha384()?; // R1.2: SHA-384 for enhanced security |
| |
| // Store session for this component transfer |
| self.component_sessions.insert(component_id, session_id); |
| |
| // Firmware chunks will be processed via update() calls as they arrive |
| // This enables real-time validation during transfer rather than after |
| |
| Ok(()) |
| } |
| |
| fn process_firmware_chunk(&mut self, component_id: u16, chunk: &[u8]) -> Result<(), PldmError> { |
| let digest = Digest::from(DIGEST_SERVER_TASK_ID); |
| |
| // Retrieve session for this component |
| let session_id = self.component_sessions.get(&component_id) |
| .ok_or(PldmError::InvalidComponent)?; |
| |
| // Add chunk to running digest (R3.6: firmware image integrity) |
| digest.update(*session_id, chunk.len() as u32, chunk)?; |
| |
| Ok(()) |
| } |
| ``` |
| |
| ### PLDM Multi-Component Concurrent Updates (Requirement R2.2) |
| ```rust |
| // PLDM task handling multiple concurrent firmware updates |
| impl PldmFirmwareUpdate { |
| fn handle_concurrent_updates(&mut self) -> Result<(), PldmError> { |
| let digest = Digest::from(DIGEST_SERVER_TASK_ID); |
| |
| // Component 1: Main firmware using SHA-256 |
| let main_fw_session = digest.init_sha256()?; |
| |
| // Component 2: Boot loader using SHA-384 |
| let bootloader_session = digest.init_sha384()?; // R1.2: SHA-384 support |
| |
| // Component 3: FPGA bitstream using SHA-512 |
| let fpga_session = digest.init_sha512()?; // R1.3: SHA-512 support |
| |
| // All components can be updated concurrently (hardware-dependent capacity - R2.2) |
| // Each maintains independent digest state (R2.3: isolation) |
| |
| // Store sessions for component tracking |
| self.component_sessions.insert(MAIN_FW_COMPONENT, main_fw_session); |
| self.component_sessions.insert(BOOTLOADER_COMPONENT, bootloader_session); |
| self.component_sessions.insert(FPGA_COMPONENT, fpga_session); |
| |
| Ok(()) |
| } |
| } |
| ``` |
| |
| ## Requirements Validation |
| |
| ### ✅ Requirements Satisfied |
| |
| | Requirement | Status | Implementation | |
| |-------------|--------|----------------| |
| | **R1.1** SHA-256 support | ✅ | `init_sha256()`, `finalize_sha256()` with hardware context | |
| | **R1.2** SHA-384 support | ✅ | `init_sha384()`, `finalize_sha384()` with hardware context | |
| | **R1.3** SHA-512 support | ✅ | `init_sha512()`, `finalize_sha512()` with hardware context | |
| | **R1.4** Reject unsupported algorithms | ✅ | SHA-3 functions return `UnsupportedAlgorithm` | |
| | **R2.1** Incremental hash computation | ✅ | True streaming via `update_hardware_context()` | |
| | **R2.2** Multiple concurrent sessions | ✅ | Hardware-dependent capacity with context switching | |
| | **R2.3** Session isolation | ✅ | Independent hardware contexts in non-cacheable RAM | |
| | **R2.4** Automatic cleanup | ✅ | `cleanup_expired_sessions()` with context cleanup | |
| | **R2.5** Session timeout | ✅ | `SESSION_TIMEOUT_TICKS` with hardware context release | |
| | **R3.1-R3.5** SPDM use cases | ✅ | All supported via streaming session-based API | |
| | **R3.6-R3.8** PLDM use cases | ✅ | Firmware validation, component verification, streaming support | |
| | **R4.1** Memory efficient | ✅ | Static allocation, hardware context simulation | |
| | **R4.2** Zero-copy processing | ✅ | Direct streaming to hardware, no session buffering | |
| | **R4.3** Deterministic allocation | ✅ | No dynamic memory allocation | |
| | **R4.4** Bounded execution | ✅ | Fixed context switch costs, predictable timing | |
| | **R5.1** Generic hardware interface | ✅ | `ServerImpl<D>` with context management traits | |
| | **R5.2** Mock implementation | ✅ | `MockDigestDevice` with context simulation | |
| | **R5.3** Type-safe abstraction | ✅ | Associated type constraints + context safety | |
| | **R5.4** Consistent API | ✅ | Same streaming interface regardless of hardware | |
| | **R6.1** Comprehensive errors | ✅ | Full `DigestError` enumeration + context errors | |
| | **R6.2** Hardware failure handling | ✅ | `HardwareFailure` error propagation + context cleanup | |
| | **R6.3** Session state validation | ✅ | `validate_session()` + context state checks | |
| | **R6.4** Clear error propagation | ✅ | `RequestError<DigestError>` wrapper | |
| | **R7.1** Synchronous IPC | ✅ | No async/futures dependencies | |
| | **R7.2** Idol-generated stubs | ✅ | Type-safe IPC interface | |
| | **R7.3** Hubris integration | ✅ | Uses userlib, leased memory | |
| | **R7.4** No async runtime | ✅ | Pure synchronous implementation | |
| | **R8.1** Task disposition configuration | ✅ | Configured in app.toml | |
| | **R8.2** Transparent task generation handling | ✅ | SPDM clients get fresh task transparently | |
| | **R8.3** Fail-fast hardware error handling | ✅ | Task panic on unrecoverable hardware errors | |
| | **R8.4** Debugging support | ✅ | Jefe external interface available | |
| |
| ## Generic Design Summary |
| |
| The `ServerImpl<D>` struct is now generic over any device `D` that implements: |
| |
| ## Key Features |
| |
| 1. **True Hardware Streaming**: Data flows directly to hardware contexts with proper save/restore |
| 2. **Context Management**: Multiple sessions share hardware via non-cacheable RAM context switching |
| 3. **Type Safety**: Associated type constraints ensure digest output sizes match expectations |
| 4. **Zero Runtime Cost**: Uses static dispatch for optimal performance |
| 5. **Memory Efficient**: Static session storage with hardware context simulation |
| 6. **Concurrent Sessions**: Hardware-dependent concurrent digest operations with automatic context switching |
| |
| ## Usage Example |
| |
| To use with a custom hardware device that supports context management: |
| |
| ```rust |
| // Your hardware device must implement the required traits |
| struct MyDigestDevice { |
| // Hardware-specific context management fields |
| current_context: Option<DigestContext>, |
| context_save_addr: *mut u8, // Non-cacheable RAM base |
| } |
| |
| impl DigestInit<Sha2_256> for MyDigestDevice { |
| type Output = Digest<8>; |
| |
| fn init(&mut self, _: Sha2_256) -> Result<DigestContext, HardwareError> { |
| // Initialize hardware registers for SHA-256 |
| // Set up context for streaming operations |
| Ok(DigestContext::new_sha256()) |
| } |
| } |
| |
| impl DigestInit<Sha2_384> for MyDigestDevice { |
| type Output = Digest<12>; |
| // Similar implementation for SHA-384 |
| } |
| |
| impl DigestInit<Sha2_512> for MyDigestDevice { |
| type Output = Digest<16>; |
| // Similar implementation for SHA-512 |
| } |
| |
| impl DigestCtrlReset for MyDigestDevice { |
| fn reset(&mut self) -> Result<(), HardwareError> { |
| // Reset hardware to clean state |
| // Clear any active contexts |
| Ok(()) |
| } |
| } |
| |
| // Context management methods (hardware-specific) |
| impl MyDigestDevice { |
| fn save_context_to_ram(&mut self, session_id: usize) -> Result<(), HardwareError> { |
| // Save current hardware context to non-cacheable RAM |
| // Hardware-specific register read and memory write operations |
| } |
| |
| fn restore_context_from_ram(&mut self, session_id: usize) -> Result<(), HardwareError> { |
| // Restore session context from non-cacheable RAM to hardware |
| // Hardware-specific memory read and register write operations |
| } |
| } |
| |
| // Then use it with the streaming server |
| let server = ServerImpl::new(MyDigestDevice::new()); |
| ``` |
| |
| --- |
| |
| # Implementation Status and Development Notes |
| |
| ## Critical Findings and Resolutions |
| |
| ### Trait Lifetime Incompatibility with Session-Based Operations - RESOLVED |
| |
| During implementation, a fundamental incompatibility was discovered between the `openprot-hal-blocking` digest traits and the session-based streaming operations described in this design document. **This issue has been resolved through the implementation of a dual API structure with owned context variants.** |
| |
| #### The Original Problem |
| |
| The `openprot-hal-blocking` digest traits were originally designed for **scoped operations**, but the digest server API expected **persistent sessions**. These requirements were fundamentally incompatible due to lifetime constraints. |
| |
| #### Root Cause: Lifetime Constraints in Scoped API |
| |
| The original scoped trait definition created lifetime constraints: |
| |
| ```rust |
| pub trait DigestInit<T: DigestAlgorithm>: ErrorType { |
| type OpContext<'a>: DigestOp<Output = Self::Output> |
| where Self: 'a; |
| |
| fn init(&mut self, init_params: T) -> Result<Self::OpContext<'_>, Self::Error>; |
| } |
| ``` |
| |
| The `OpContext<'a>` had a lifetime tied to `&'a mut self`, meaning: |
| - Context could not outlive the function call that created it |
| - Context could not be stored in a separate struct |
| - Context could not persist across IPC boundaries |
| - Sessions could not maintain persistent state between operations |
| |
| #### The Solution: Dual API with Move-Based Resource Management |
| |
| The incompatibility has been **completely resolved** through implementation of a dual API structure: |
| |
| **1. Scoped API (Original)** - For simple, one-shot operations: |
| ```rust |
| pub mod scoped { |
| pub trait DigestInit<T: DigestAlgorithm>: ErrorType { |
| type OpContext<'a>: DigestOp<Output = Self::Output> |
| where Self: 'a; |
| |
| fn init<'a>(&'a mut self, init_params: T) -> Result<Self::OpContext<'a>, Self::Error>; |
| } |
| } |
| ``` |
| |
| **2. Owned API (New)** - For session-based, streaming operations: |
| ```rust |
| pub mod owned { |
| pub trait DigestInit<T: DigestAlgorithm>: ErrorType { |
| type OwnedContext: DigestOp<Output = Self::Output>; |
| |
| fn init_owned(&mut self, init_params: T) -> Result<Self::OwnedContext, Self::Error>; |
| } |
| |
| pub trait DigestOp { |
| type Output; |
| |
| fn update(&mut self, data: &[u8]) -> Result<(), Self::Error>; |
| fn finalize(self) -> Result<Self::Output, Self::Error>; |
| fn cancel(self) -> Self::Controller; |
| } |
| } |
| ``` |
| |
| #### How the Owned API Enables Sessions |
| |
| The owned API uses **move-based resource management** to solve the lifetime problem: |
| |
| ```rust |
| // ✅ NOW POSSIBLE: Digest server with owned contexts and controller |
| use openprot_hal_blocking::digest::owned::{DigestInit, DigestOp}; |
| |
| struct DigestServer<H, C> { |
| controller: Option<H>, // Hardware controller |
| active_session: Option<C>, // Single active session |
| } |
| |
| impl<H, C> DigestServer<H, C> |
| where |
| H: DigestInit<Sha2_256, Context = C>, |
| C: DigestOp<Controller = H>, |
| { |
| fn init_session(&mut self) -> Result<(), Error> { |
| let controller = self.controller.take().ok_or(Error::Busy)?; |
| let context = controller.init(Sha2_256)?; // ✅ Owned context |
| self.active_session = Some(context); // ✅ Store in server |
| Ok(()) |
| } |
| |
| fn update_session(&mut self, data: &[u8]) -> Result<(), Error> { |
| let context = self.active_session.take().ok_or(Error::NoSession)?; |
| let updated_context = context.update(data)?; // ✅ Move-based update |
| self.active_session = Some(updated_context); // ✅ Store updated state |
| Ok(()) |
| } |
| |
| fn finalize_session(&mut self) -> Result<Digest<8>, Error> { |
| let context = self.active_session.take().ok_or(Error::NoSession)?; |
| let (digest, controller) = context.finalize()?; |
| self.controller = Some(controller); // ✅ Controller recovery |
| Ok(digest) |
| } |
| } |
| ``` |
| |
| #### Key Benefits of the Move-Based Solution |
| |
| 1. **True Streaming Support**: Contexts can be stored and updated incrementally |
| 2. **Session Isolation**: Each session owns its context independently |
| 3. **Resource Recovery**: `cancel()` method allows controller recovery |
| 4. **Rust Ownership Safety**: Move semantics prevent use-after-finalize |
| 5. **Backward Compatibility**: Scoped API remains unchanged for simple use cases |
| |
| #### Implementation Examples |
| |
| **Session-Based Streaming (Now Possible)**: |
| ```rust |
| // SPDM certificate chain verification with streaming |
| let session_id = digest_server.init_sha256()?; |
| |
| for cert_chunk in certificate_chain.chunks(1024) { |
| digest_server.update(session_id, cert_chunk)?; |
| } |
| |
| let cert_digest = digest_server.finalize_sha256(session_id)?; |
| ``` |
| |
| **One-Shot Operations (Still Supported)**: |
| ```rust |
| // Simple hash computation using scoped API |
| let digest = digest_device.compute_sha256(complete_data)?; |
| ``` |
| |
| #### Current Implementation Status |
| |
| The dual API solution is **fully implemented and working**: |
| |
| - ✅ **Scoped API**: Original lifetime-constrained API for simple operations |
| - ✅ **Owned API**: New move-based API enabling persistent sessions |
| - ✅ **Mock Implementation**: Both APIs implemented in baremetal mock platform |
| - ✅ **Comprehensive Testing**: Session storage patterns validated |
| - ✅ **Documentation**: Complete analysis comparing both approaches |
| |
| #### Architectural Resolution |
| |
| The dual API approach resolves all original limitations: |
| |
| 1. ✅ **Session-based streaming is now possible** with the owned API |
| 2. ✅ **Both one-shot and streaming operations supported** via appropriate API choice |
| 3. ✅ **Design document architecture is now implementable** using owned contexts |
| 4. ✅ **Streaming large data sets fully supported** with persistent session state |
| |
| This demonstrates how API design evolution can solve fundamental architectural constraints while maintaining backward compatibility. The move-based resource management pattern provides the persistent contexts needed for server applications while preserving the simplicity of scoped operations for basic use cases. |