| // Licensed under the Apache-2.0 license | |
| // SPDX-License-Identifier: Apache-2.0 | |
| //! # Digest HAL Traits | |
| //! | |
| //! This module provides blocking/synchronous Hardware Abstraction Layer (HAL) traits | |
| //! for cryptographic digest operations. It defines a common interface for hash functions | |
| //! and message authentication codes that can be implemented by various hardware and | |
| //! software backends. | |
| //! | |
| //! ## API Evolution | |
| //! | |
| //! This module provides two complementary APIs: | |
| //! | |
| //! ### Scoped API (Current) | |
| //! - **Use case**: One-shot operations, simple baremetal applications | |
| //! - **Pattern**: Borrowed contexts with lifetime constraints | |
| //! - **Benefits**: Minimal overhead, direct hardware mapping | |
| //! - **Limitations**: Cannot store contexts, no persistent sessions | |
| //! | |
| //! ### Owned API (New - Move-based Resource Management) | |
| //! - **Use case**: Server applications, persistent sessions, IPC boundaries | |
| //! - **Pattern**: Owned contexts with resource recovery | |
| //! - **Benefits**: Persistent storage, multiple concurrent contexts, IPC-safe | |
| //! - **Limitations**: Slightly more complex ownership model | |
| //! | |
| //! ## Key Components | |
| //! | |
| //! - `Digest` - A generic container for digest output values | |
| //! - `DigestAlgorithm` - Trait defining digest algorithm properties | |
| //! | |
| //! ### Scoped API | |
| //! - `scoped::DigestInit` - Trait for initializing digest operations (borrowed contexts) | |
| //! - `scoped::DigestOp` - Trait for performing digest computations (borrowed contexts) | |
| //! - `scoped::DigestCtrlReset` - Trait for resetting digest contexts | |
| //! | |
| //! ### Owned API (Typestate) | |
| //! - `owned::DigestInit` - Trait for initializing digest operations (owned contexts) | |
| //! - `owned::DigestOp` - Trait for performing digest computations (owned contexts) | |
| //! | |
| //! ## Supported Algorithms | |
| //! | |
| //! This module includes support for: | |
| //! - SHA-2 family: SHA-256, SHA-384, SHA-512 | |
| //! - SHA-3 family: SHA3-224, SHA3-256, SHA3-384, SHA3-512 | |
| //! | |
| //! ## Example Usage | |
| //! | |
| //! ### Scoped API (Traditional) | |
| //! ```rust,no_run | |
| //! # use openprot_hal_blocking::digest::*; | |
| //! # use openprot_hal_blocking::digest::scoped::*; | |
| //! # struct MyDigestImpl; | |
| //! # impl ErrorType for MyDigestImpl { type Error = core::convert::Infallible; } | |
| //! # impl DigestInit<Sha2_256> for MyDigestImpl { | |
| //! # type OpContext<'a> = MyContext<'a> where Self: 'a; | |
| //! # fn init<'a>(&'a mut self, _: Sha2_256) -> Result<Self::OpContext<'a>, Self::Error> { todo!() } | |
| //! # } | |
| //! # struct MyContext<'a>(&'a mut MyDigestImpl); | |
| //! # impl ErrorType for MyContext<'_> { type Error = core::convert::Infallible; } | |
| //! # impl DigestOp for MyContext<'_> { | |
| //! # type Output = Digest<8>; | |
| //! # fn update(&mut self, _: &[u8]) -> Result<(), Self::Error> { Ok(()) } | |
| //! # fn finalize(self) -> Result<Self::Output, Self::Error> { | |
| //! # Ok(Digest { value: [0u32; 8] }) | |
| //! # } | |
| //! # } | |
| //! let mut hasher = MyDigestImpl; | |
| //! let mut ctx = hasher.init(Sha2_256)?; | |
| //! ctx.update(b"hello world")?; | |
| //! let digest = ctx.finalize()?; | |
| //! # Ok::<(), core::convert::Infallible>(()) | |
| //! ``` | |
| //! | |
| //! ### Owned API (Move-based - for servers/sessions) | |
| //! ```rust,no_run | |
| //! # use openprot_hal_blocking::digest::*; | |
| //! # use openprot_hal_blocking::digest::owned::{DigestInit, DigestOp}; | |
| //! # struct MyDigestController; | |
| //! # impl ErrorType for MyDigestController { type Error = core::convert::Infallible; } | |
| //! # impl DigestInit<Sha2_256> for MyDigestController { | |
| //! # type Context = MyOwnedContext; | |
| //! # fn init(self, _: Sha2_256) -> Result<Self::Context, Self::Error> { todo!() } | |
| //! # } | |
| //! # struct MyOwnedContext; | |
| //! # impl ErrorType for MyOwnedContext { type Error = core::convert::Infallible; } | |
| //! # impl DigestOp for MyOwnedContext { | |
| //! # type Output = Digest<8>; | |
| //! # type Controller = MyDigestController; | |
| //! # fn update(self, _: &[u8]) -> Result<Self, Self::Error> { todo!() } | |
| //! # fn finalize(self) -> Result<(Self::Output, Self::Controller), Self::Error> { todo!() } | |
| //! # fn cancel(self) -> Self::Controller { todo!() } | |
| //! # } | |
| //! let controller = MyDigestController; | |
| //! let context = controller.init(Sha2_256)?; | |
| //! let context = context.update(b"hello world")?; | |
| //! let (digest, recovered_controller) = context.finalize()?; | |
| //! // Controller can be reused for new operations | |
| //! # Ok::<(), core::convert::Infallible>(()) | |
| //! ``` | |
| use core::fmt::Debug; | |
| use core::result::Result; | |
| use zerocopy::{FromBytes, Immutable, IntoBytes}; | |
| /// A generic digest output container. | |
| /// | |
| /// This structure represents the output of a cryptographic digest operation. | |
| /// It uses a const generic parameter `N` to specify the number of 32-bit words | |
| /// in the digest output, allowing it to accommodate different digest sizes. | |
| /// | |
| /// The structure is marked with `#[repr(C)]` to ensure a predictable memory layout, | |
| /// making it suitable for zero-copy operations and hardware interfaces. | |
| /// | |
| /// ## Integration Benefits | |
| /// | |
| /// The `Digest<N>` type solves several critical integration challenges: | |
| /// | |
| /// ### 1. Concrete vs Opaque Types | |
| /// Unlike opaque associated types (`type Output: IntoBytes`), `Digest<N>` provides | |
| /// a **concrete type** that generic code can work with directly: | |
| /// | |
| /// ```rust | |
| /// # use openprot_hal_blocking::digest::Digest; | |
| /// // ✅ CONCRETE: We know exactly what this is | |
| /// fn process_digest(digest: Digest<8>) -> [u32; 8] { | |
| /// digest.into_array() // Safe, direct conversion | |
| /// } | |
| /// | |
| /// // ❌ OPAQUE: We don't know what D::Output actually is | |
| /// // fn process_generic<D>(output: D::Output) -> /* Unknown type */ { | |
| /// // // Cannot convert to [u32; 8] safely | |
| /// // } | |
| /// ``` | |
| /// | |
| /// ### 2. Safe Type Conversions | |
| /// Provides safe methods to access underlying data without unsafe code: | |
| /// | |
| /// ```rust | |
| /// # use openprot_hal_blocking::digest::Digest; | |
| /// let digest = Digest::<8> { value: [1, 2, 3, 4, 5, 6, 7, 8] }; | |
| /// | |
| /// // Safe conversions - no unsafe code needed | |
| /// let array: [u32; 8] = digest.into_array(); // Owned conversion | |
| /// let array_ref: &[u32; 8] = digest.as_array(); // Borrowed conversion | |
| /// let bytes: &[u8] = digest.as_bytes(); // Byte slice access | |
| /// ``` | |
| /// | |
| /// ### 3. IPC Integration | |
| /// Designed specifically for Hubris IPC leased memory operations: | |
| /// | |
| /// ```rust,no_run | |
| /// # use openprot_hal_blocking::digest::Digest; | |
| /// # struct Leased<T, U>(core::marker::PhantomData<(T, U)>); | |
| /// # impl<T, U> Leased<T, U> { fn write(&self, data: U) -> Result<(), ()> { Ok(()) } } | |
| /// # let digest_out: Leased<(), [u32; 8]> = Leased(core::marker::PhantomData); | |
| /// # let digest = Digest::<8> { value: [0; 8] }; | |
| /// // Direct write to IPC lease - no conversion needed | |
| /// digest_out.write(digest.into_array())?; | |
| /// # Ok::<(), ()>(()) | |
| /// ``` | |
| /// | |
| /// ### 4. Server Application Support | |
| /// Enables servers to store and manipulate digest results safely: | |
| /// | |
| /// ```rust | |
| /// # use openprot_hal_blocking::digest::Digest; | |
| /// struct DigestCache { | |
| /// sha256_results: Vec<Digest<8>>, // Can store concrete types | |
| /// sha384_results: Vec<Digest<12>>, // Different sizes supported | |
| /// } | |
| /// | |
| /// impl DigestCache { | |
| /// fn store_sha256(&mut self, digest: Digest<8>) { | |
| /// self.sha256_results.push(digest); // Direct storage | |
| /// } | |
| /// | |
| /// fn get_as_array(&self, index: usize) -> [u32; 8] { | |
| /// self.sha256_results[index].into_array() // Safe access | |
| /// } | |
| /// } | |
| /// ``` | |
| /// | |
| /// ### 5. Zero-Copy Operations | |
| /// Full zerocopy trait support enables efficient memory operations: | |
| /// | |
| /// ```rust | |
| /// # use openprot_hal_blocking::digest::Digest; | |
| /// let digest = Digest::<8> { value: [1, 2, 3, 4, 5, 6, 7, 8] }; | |
| /// | |
| /// // Zero-copy byte access via zerocopy traits | |
| /// let bytes: &[u8] = zerocopy::IntoBytes::as_bytes(&digest); | |
| /// | |
| /// // Safe transmutation between compatible layouts | |
| /// // (enabled by FromBytes + Immutable derives) | |
| /// ``` | |
| /// | |
| /// ## Comparison with Opaque Output Types | |
| /// | |
| /// | Feature | `Digest<N>` (Concrete) | `D::Output` (Opaque) | | |
| /// |---------|-------------------------|----------------------| | |
| /// | **Type Known at Compile Time** | ✅ Always `Digest<N>` | ❌ Unknown until runtime | | |
| /// | **Safe Array Access** | ✅ `into_array()`, `as_array()` | ❌ Requires unsafe casting | | |
| /// | **IPC Integration** | ✅ Direct `[u32; N]` conversion | ❌ Complex type bridging | | |
| /// | **Server Storage** | ✅ Can store in structs | ❌ Difficult generic storage | | |
| /// | **Zero-Copy Support** | ✅ Full zerocopy traits | ❌ Implementation dependent | | |
| /// | **Embedded Friendly** | ✅ Known size, no allocation | ❌ Unknown size, complex | | |
| /// | |
| /// # Type Parameters | |
| /// | |
| /// * `N` - The number of 32-bit words in the digest output | |
| /// | |
| /// # Examples | |
| /// | |
| /// ```rust | |
| /// # use openprot_hal_blocking::digest::Digest; | |
| /// // A 256-bit digest (8 words of 32 bits each) | |
| /// let sha256_digest = Digest::<8> { | |
| /// value: [0x12345678, 0x9abcdef0, 0x11111111, 0x22222222, | |
| /// 0x33333333, 0x44444444, 0x55555555, 0x66666666], | |
| /// }; | |
| /// | |
| /// // Safe conversion to array for IPC | |
| /// let array = sha256_digest.into_array(); | |
| /// | |
| /// // Access as bytes for serialization | |
| /// let bytes = sha256_digest.as_bytes(); | |
| /// assert_eq!(bytes.len(), 32); | |
| /// ``` | |
| #[derive(Copy, Clone, PartialEq, Eq, IntoBytes, FromBytes, Immutable)] | |
| #[repr(C)] | |
| pub struct Digest<const N: usize> { | |
| /// The digest value as an array of 32-bit words | |
| pub value: [u32; N], | |
| } | |
| impl<const N: usize> Digest<N> { | |
| /// Create a new digest from an array of words | |
| pub fn new(value: [u32; N]) -> Self { | |
| Self { value } | |
| } | |
| /// Get the digest as an array of words | |
| /// | |
| /// This provides safe access to the underlying array without any conversions. | |
| pub fn into_array(self) -> [u32; N] { | |
| self.value | |
| } | |
| /// Get a reference to the digest as an array of words | |
| pub fn as_array(&self) -> &[u32; N] { | |
| &self.value | |
| } | |
| /// Get the digest as a byte slice | |
| pub fn as_bytes(&self) -> &[u8] { | |
| zerocopy::IntoBytes::as_bytes(self) | |
| } | |
| } | |
| impl<const N: usize> AsRef<[u8]> for Digest<N> { | |
| fn as_ref(&self) -> &[u8] { | |
| zerocopy::IntoBytes::as_bytes(self) | |
| } | |
| } | |
| /// Trait defining the properties of a cryptographic digest algorithm. | |
| /// | |
| /// This trait provides compile-time information about digest algorithms, | |
| /// including their output size and associated digest type. It serves as | |
| /// a type-level specification that can be used with generic digest operations. | |
| /// | |
| /// # Requirements | |
| /// | |
| /// Implementing types must be `Copy` and `Debug` to support easy cloning | |
| /// and debugging capabilities. | |
| /// | |
| /// # Examples | |
| /// | |
| /// ```rust | |
| /// # use openprot_hal_blocking::digest::{DigestAlgorithm, Digest}; | |
| /// # use core::fmt::Debug; | |
| /// #[derive(Clone, Copy, Debug)] | |
| /// struct MyCustomAlgorithm; | |
| /// | |
| /// impl DigestAlgorithm for MyCustomAlgorithm { | |
| /// const OUTPUT_BITS: usize = 256; | |
| /// type Digest = Digest<8>; // 256 bits / 32 bits per word = 8 words | |
| /// } | |
| /// ``` | |
| pub trait DigestAlgorithm: Copy + Debug { | |
| /// The output size of the digest algorithm in bits. | |
| /// | |
| /// This constant defines the total number of bits in the digest output. | |
| /// For example, SHA-256 would have `OUTPUT_BITS = 256`. | |
| const OUTPUT_BITS: usize; | |
| /// The digest output type for this algorithm. | |
| /// | |
| /// This associated type specifies the concrete digest type that will be | |
| /// produced by this algorithm. Typically this will be a [`Digest<N>`] | |
| /// where `N` is calculated from `OUTPUT_BITS`. | |
| type Digest; | |
| } | |
| /// SHA-256 digest algorithm marker type. | |
| /// | |
| /// This zero-sized type represents the SHA-256 cryptographic hash algorithm, | |
| /// which produces a 256-bit (32-byte) digest output. | |
| /// | |
| /// SHA-256 is part of the SHA-2 family and is widely used for cryptographic | |
| /// applications requiring strong collision resistance. | |
| #[derive(Clone, Copy, Debug)] | |
| pub struct Sha2_256; | |
| impl DigestAlgorithm for Sha2_256 { | |
| const OUTPUT_BITS: usize = 256usize; | |
| type Digest = Digest<{ Self::OUTPUT_BITS / 32 }>; | |
| } | |
| /// SHA-384 digest algorithm marker type. | |
| /// | |
| /// This zero-sized type represents the SHA-384 cryptographic hash algorithm, | |
| /// which produces a 384-bit (48-byte) digest output. | |
| /// | |
| /// SHA-384 is part of the SHA-2 family and provides a larger output size | |
| /// than SHA-256 for applications requiring additional security margin. | |
| #[derive(Clone, Copy, Debug)] | |
| pub struct Sha2_384; | |
| impl DigestAlgorithm for Sha2_384 { | |
| const OUTPUT_BITS: usize = 384usize; | |
| type Digest = Digest<{ Self::OUTPUT_BITS / 32 }>; | |
| } | |
| /// SHA-512 digest algorithm marker type. | |
| /// | |
| /// This zero-sized type represents the SHA-512 cryptographic hash algorithm, | |
| /// which produces a 512-bit (64-byte) digest output. | |
| /// | |
| /// SHA-512 is part of the SHA-2 family and provides the largest standard | |
| /// output size, offering maximum collision resistance. | |
| #[derive(Clone, Copy, Debug)] | |
| pub struct Sha2_512; | |
| impl DigestAlgorithm for Sha2_512 { | |
| const OUTPUT_BITS: usize = 512; | |
| type Digest = Digest<{ Self::OUTPUT_BITS / 32 }>; | |
| } | |
| /// SHA3-224 digest algorithm marker type. | |
| /// | |
| /// This zero-sized type represents the SHA3-224 cryptographic hash algorithm, | |
| /// which produces a 224-bit (28-byte) digest output. | |
| /// | |
| /// SHA3-224 is part of the SHA-3 (Keccak) family and offers an alternative | |
| /// to SHA-2 with different underlying mathematical foundations. | |
| #[derive(Clone, Copy, Debug)] | |
| pub struct Sha3_224; | |
| impl DigestAlgorithm for Sha3_224 { | |
| const OUTPUT_BITS: usize = 224usize; | |
| type Digest = Digest<{ Self::OUTPUT_BITS / 32 }>; | |
| } | |
| /// SHA3-256 digest algorithm marker type. | |
| /// | |
| /// This zero-sized type represents the SHA3-256 cryptographic hash algorithm, | |
| /// which produces a 256-bit (32-byte) digest output. | |
| /// | |
| /// SHA3-256 is part of the SHA-3 (Keccak) family and provides equivalent | |
| /// security to SHA-256 with different algorithmic properties. | |
| #[derive(Clone, Copy, Debug)] | |
| pub struct Sha3_256; | |
| impl DigestAlgorithm for Sha3_256 { | |
| const OUTPUT_BITS: usize = 256usize; | |
| type Digest = Digest<{ Self::OUTPUT_BITS / 32 }>; | |
| } | |
| /// SHA3-384 digest algorithm marker type. | |
| /// | |
| /// This zero-sized type represents the SHA3-384 cryptographic hash algorithm, | |
| /// which produces a 384-bit (48-byte) digest output. | |
| /// | |
| /// SHA3-384 is part of the SHA-3 (Keccak) family and provides equivalent | |
| /// security to SHA-384 with different algorithmic properties. | |
| #[derive(Clone, Copy, Debug)] | |
| pub struct Sha3_384; | |
| impl DigestAlgorithm for Sha3_384 { | |
| const OUTPUT_BITS: usize = 384usize; | |
| type Digest = Digest<{ Self::OUTPUT_BITS / 32 }>; | |
| } | |
| /// SHA3-512 digest algorithm marker type. | |
| /// | |
| /// This zero-sized type represents the SHA3-512 cryptographic hash algorithm, | |
| /// which produces a 512-bit (64-byte) digest output. | |
| /// | |
| /// SHA3-512 is part of the SHA-3 (Keccak) family and provides equivalent | |
| /// security to SHA-512 with different algorithmic properties. | |
| #[derive(Clone, Copy, Debug)] | |
| pub struct Sha3_512; | |
| impl DigestAlgorithm for Sha3_512 { | |
| const OUTPUT_BITS: usize = 512; | |
| type Digest = Digest<{ Self::OUTPUT_BITS / 32 }>; | |
| } | |
| /// Error kind. | |
| /// | |
| /// This represents a common set of digest operation errors. Implementations are | |
| /// free to define more specific or additional error types. However, by providing | |
| /// a mapping to these common errors, generic code can still react to them. | |
| #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] | |
| #[non_exhaustive] | |
| pub enum ErrorKind { | |
| /// The input data length is not valid for the hash function. | |
| InvalidInputLength, | |
| /// The specified hash algorithm is not supported by the hardware or software implementation. | |
| UnsupportedAlgorithm, | |
| /// Failed to initialize the hash computation context. | |
| InitializationError, | |
| /// Error occurred while updating the hash computation with new data. | |
| UpdateError, | |
| /// Error occurred while finalizing the hash computation. | |
| FinalizationError, | |
| /// The hardware accelerator is busy and cannot process the hash computation. | |
| Busy, | |
| /// General hardware failure during hash computation. | |
| HardwareFailure, | |
| /// Insufficient permissions to access the hardware or perform the hash computation. | |
| PermissionDenied, | |
| /// The hash computation context has not been initialized. | |
| NotInitialized, | |
| } | |
| impl core::fmt::Display for ErrorKind { | |
| fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | |
| match self { | |
| Self::InvalidInputLength => write!(f, "invalid input data length"), | |
| Self::UnsupportedAlgorithm => write!(f, "unsupported hash algorithm"), | |
| Self::InitializationError => write!(f, "failed to initialize hash computation"), | |
| Self::UpdateError => write!(f, "error updating hash computation"), | |
| Self::FinalizationError => write!(f, "error finalizing hash computation"), | |
| Self::Busy => write!(f, "hardware accelerator is busy"), | |
| Self::HardwareFailure => write!(f, "hardware failure during hash computation"), | |
| Self::PermissionDenied => write!(f, "insufficient permissions to access hardware"), | |
| Self::NotInitialized => write!(f, "hash computation context not initialized"), | |
| } | |
| } | |
| } | |
| /// Trait for digest operation errors. | |
| /// | |
| /// This trait provides a common interface for all error types that can occur | |
| /// during digest operations. It allows for categorization of errors into | |
| /// common types while still permitting implementation-specific error details. | |
| /// | |
| /// All digest error types must implement `Debug` for debugging purposes and | |
| /// provide a method to convert to a generic [`ErrorKind`]. | |
| /// | |
| /// # Examples | |
| /// | |
| /// ```rust | |
| /// # use openprot_hal_blocking::digest::{Error, ErrorKind}; | |
| /// # use core::fmt::Debug; | |
| /// #[derive(Debug)] | |
| /// struct MyDigestError { | |
| /// message: &'static str, | |
| /// } | |
| /// | |
| /// impl Error for MyDigestError { | |
| /// fn kind(&self) -> ErrorKind { | |
| /// ErrorKind::HardwareFailure | |
| /// } | |
| /// } | |
| /// ``` | |
| pub trait Error: core::fmt::Debug { | |
| /// Convert error to a generic error kind | |
| /// | |
| /// By using this method, errors freely defined by HAL implementations | |
| /// can be converted to a set of generic errors upon which generic | |
| /// code can act. | |
| fn kind(&self) -> ErrorKind; | |
| } | |
| impl Error for core::convert::Infallible { | |
| fn kind(&self) -> ErrorKind { | |
| match *self {} | |
| } | |
| } | |
| /// Trait for types that have an associated error type. | |
| /// | |
| /// This trait provides a standard way for digest-related types to specify | |
| /// their error type. It's used throughout the digest HAL to maintain | |
| /// type safety while allowing different implementations to use their own | |
| /// specific error types. | |
| /// | |
| /// # Examples | |
| /// | |
| /// ```rust | |
| /// # use openprot_hal_blocking::digest::{ErrorType, Error, ErrorKind}; | |
| /// # use core::fmt::Debug; | |
| /// # #[derive(Debug)] | |
| /// # struct MyError; | |
| /// # impl Error for MyError { | |
| /// # fn kind(&self) -> ErrorKind { ErrorKind::HardwareFailure } | |
| /// # } | |
| /// struct MyDigestDevice; | |
| /// | |
| /// impl ErrorType for MyDigestDevice { | |
| /// type Error = MyError; | |
| /// } | |
| /// ``` | |
| pub trait ErrorType { | |
| /// Error type. | |
| type Error: Error; | |
| } | |
| /// Trait for initializing digest operations. | |
| /// | |
| /// This trait provides the interface for creating new digest computation contexts. | |
| /// It is parameterized by a [`DigestAlgorithm`] type to ensure type safety and | |
| /// allow different algorithms to have different initialization parameters. | |
| /// | |
| /// # Type Parameters | |
| /// | |
| /// * `T` - The digest algorithm type that implements [`DigestAlgorithm`] | |
| /// | |
| /// # Examples | |
| /// | |
| /// ```rust,no_run | |
| /// # use openprot_hal_blocking::digest::*; | |
| /// # struct MyDigestImpl; | |
| /// # impl ErrorType for MyDigestImpl { type Error = core::convert::Infallible; } | |
| /// # impl DigestInit<Sha2_256> for MyDigestImpl { | |
| /// # type OpContext<'a> = MyContext<'a> where Self: 'a; | |
| /// # fn init<'a>(&'a mut self, _: Sha2_256) -> Result<Self::OpContext<'a>, Self::Error> { todo!() } | |
| /// # } | |
| /// # struct MyContext<'a>(&'a mut MyDigestImpl); | |
| /// # impl ErrorType for MyContext<'_> { type Error = core::convert::Infallible; } | |
| /// # impl DigestOp for MyContext<'_> { | |
| /// # type Output = Digest<8>; | |
| /// # fn update(&mut self, _: &[u8]) -> Result<(), Self::Error> { Ok(()) } | |
| /// # fn finalize(self) -> Result<Self::Output, Self::Error> { | |
| /// # Ok(Digest { value: [0u32; 8] }) | |
| /// # } | |
| /// # } | |
| /// let mut device = MyDigestImpl; | |
| /// let context = device.init(Sha2_256)?; | |
| /// # Ok::<(), core::convert::Infallible>(()) | |
| /// ``` | |
| pub trait DigestInit<T: DigestAlgorithm>: ErrorType { | |
| /// The operation context type that will handle the digest computation. | |
| /// | |
| /// This associated type represents the stateful context returned by [`init`](Self::init) | |
| /// that can be used to perform the actual digest operations via [`DigestOp`]. | |
| /// The lifetime parameter ensures the context cannot outlive the device that created it. | |
| type OpContext<'a>: DigestOp<Output = T::Digest> | |
| where | |
| Self: 'a; | |
| /// Init instance of the crypto function with the given context. | |
| /// | |
| /// # Parameters | |
| /// | |
| /// - `algorithm`: The zero-sized algorithm marker type specifying which hash function to use. | |
| /// | |
| /// # Returns | |
| /// | |
| /// A new instance of the hash function. | |
| fn init(&mut self, algorithm: T) -> Result<Self::OpContext<'_>, Self::Error>; | |
| } | |
| /// Trait for resetting digest computation contexts. | |
| /// | |
| /// This trait provides the ability to reset a digest device or context back to | |
| /// its initial state, allowing it to be reused for new digest computations | |
| /// without needing to create a new instance. | |
| /// | |
| /// # Examples | |
| /// | |
| /// ```rust,no_run | |
| /// # use openprot_hal_blocking::digest::*; | |
| /// # struct MyDigestImpl; | |
| /// # impl ErrorType for MyDigestImpl { type Error = core::convert::Infallible; } | |
| /// # impl DigestCtrlReset for MyDigestImpl { | |
| /// # fn reset(&mut self) -> Result<(), Self::Error> { Ok(()) } | |
| /// # } | |
| /// let mut device = MyDigestImpl; | |
| /// device.reset()?; | |
| /// # Ok::<(), core::convert::Infallible>(()) | |
| /// ``` | |
| pub trait DigestCtrlReset: ErrorType { | |
| /// Reset instance to its initial state. | |
| /// | |
| /// # Returns | |
| /// | |
| /// A `Result` indicating success or failure. On success, returns `Ok(())`. On failure, returns a `CryptoError`. | |
| fn reset(&mut self) -> Result<(), Self::Error>; | |
| } | |
| /// Trait for performing digest operations. | |
| /// | |
| /// This trait provides the core interface for digest computation operations: | |
| /// updating the digest state with input data and finalizing the computation | |
| /// to produce the digest output. | |
| /// | |
| /// This trait is typically implemented by context types returned from | |
| /// [`DigestInit::init`] and represents an active digest computation. | |
| /// | |
| /// # State Machine | |
| /// | |
| /// Digest operations follow a simple state machine: | |
| /// 1. **Update**: Call [`update`](Self::update) zero or more times with input data | |
| /// 2. **Finalize**: Call [`finalize`](Self::finalize) once to produce the final digest | |
| /// | |
| /// After finalization, the context is consumed and cannot be reused. | |
| /// | |
| /// # Examples | |
| /// | |
| /// ```rust,no_run | |
| /// # use openprot_hal_blocking::digest::*; | |
| /// # struct MyContext; | |
| /// # impl ErrorType for MyContext { type Error = core::convert::Infallible; } | |
| /// # impl DigestOp for MyContext { | |
| /// # type Output = Digest<8>; | |
| /// # fn update(&mut self, _: &[u8]) -> Result<(), Self::Error> { Ok(()) } | |
| /// # fn finalize(self) -> Result<Self::Output, Self::Error> { | |
| /// # Ok(Digest { value: [0u32; 8] }) | |
| /// # } | |
| /// # } | |
| /// let mut context = MyContext; | |
| /// context.update(b"hello")?; | |
| /// context.update(b" world")?; | |
| /// let digest = context.finalize()?; | |
| /// # Ok::<(), core::convert::Infallible>(()) | |
| /// ``` | |
| pub trait DigestOp: ErrorType { | |
| /// The digest output type. | |
| /// | |
| /// This type represents the final digest value produced by [`finalize`](Self::finalize). | |
| /// It must implement [`IntoBytes`] to enable zero-copy conversion to byte arrays. | |
| type Output: IntoBytes; | |
| /// Update state using provided input data. | |
| /// | |
| /// # Parameters | |
| /// | |
| /// - `input`: The input data to be hashed. This can be any type that implements `AsRef<[u8]>`. | |
| /// | |
| /// # Returns | |
| /// | |
| /// A `Result` indicating success or failure. On success, returns `Ok(())`. On failure, returns a `CryptoError`. | |
| fn update(&mut self, input: &[u8]) -> Result<(), Self::Error>; | |
| /// Finalize the computation and produce the output. | |
| /// | |
| /// # Parameters | |
| /// | |
| /// - `out`: A mutable slice to store the hash output. The length of the slice must be at least `MAX_OUTPUT_SIZE`. | |
| /// | |
| /// # Returns | |
| /// | |
| /// A `Result` indicating success or failure. On success, returns `Ok(())`. On failure, returns a `CryptoError`. | |
| fn finalize(self) -> Result<Self::Output, Self::Error>; | |
| } | |
| pub mod scoped { | |
| //! Scoped digest API with borrowed contexts (current) | |
| //! | |
| //! This module contains the original OpenPRoT HAL digest traits that use | |
| //! borrowed contexts with lifetime constraints. These traits are ideal for: | |
| //! - One-shot digest operations | |
| //! - Simple embedded applications | |
| //! - Direct hardware mapping | |
| //! - Minimal memory overhead | |
| //! | |
| //! **Limitation**: Contexts cannot be stored or persist across function boundaries | |
| //! due to lifetime constraints. | |
| pub use super::{DigestAlgorithm, DigestCtrlReset, DigestInit, DigestOp, ErrorType}; | |
| } | |
| pub mod owned { | |
| //! Owned digest API with move-based resource management | |
| //! | |
| //! This module provides a move-based digest API where contexts are owned | |
| //! rather than borrowed. This enables: | |
| //! - Persistent session storage | |
| //! - Multiple concurrent contexts | |
| //! - IPC boundary crossing | |
| //! - Resource recovery patterns | |
| //! - Compile-time prevention of use-after-finalize | |
| //! | |
| //! This API is specifically designed for server applications like Hubris | |
| //! digest servers that need to maintain long-lived sessions. | |
| use super::{DigestAlgorithm, ErrorType, IntoBytes}; | |
| use core::result::Result; | |
| /// Trait for initializing digest operations with owned contexts. | |
| /// | |
| /// This trait takes ownership of the controller and returns an owned context | |
| /// that can be stored, moved, and persisted across function boundaries. | |
| /// Unlike the scoped API, there are no lifetime constraints. | |
| /// | |
| /// # Type Parameters | |
| /// | |
| /// * `T` - The digest algorithm type that implements [`DigestAlgorithm`] | |
| /// | |
| /// # Examples | |
| /// | |
| /// ```rust,no_run | |
| /// # use openprot_hal_blocking::digest::*; | |
| /// # use openprot_hal_blocking::digest::owned::{DigestInit, DigestOp}; | |
| /// # struct MyController; | |
| /// # impl ErrorType for MyController { type Error = core::convert::Infallible; } | |
| /// # struct MyContext; | |
| /// # impl ErrorType for MyContext { type Error = core::convert::Infallible; } | |
| /// # impl DigestOp for MyContext { | |
| /// # type Output = Digest<8>; | |
| /// # type Controller = MyController; | |
| /// # fn update(self, _: &[u8]) -> Result<Self, Self::Error> { Ok(self) } | |
| /// # fn finalize(self) -> Result<(Self::Output, Self::Controller), Self::Error> { todo!() } | |
| /// # fn cancel(self) -> Self::Controller { todo!() } | |
| /// # } | |
| /// # impl DigestInit<Sha2_256> for MyController { | |
| /// # type Context = MyContext; | |
| /// # fn init(self, _: Sha2_256) -> Result<Self::Context, Self::Error> { todo!() } | |
| /// # } | |
| /// let controller = MyController; | |
| /// let context = controller.init(Sha2_256)?; | |
| /// // Context can be stored in structs, moved across functions, etc. | |
| /// # Ok::<(), core::convert::Infallible>(()) | |
| /// ``` | |
| pub trait DigestInit<T: DigestAlgorithm>: ErrorType + Sized { | |
| /// The owned context type that will handle the digest computation. | |
| /// | |
| /// This context has no lifetime constraints and can be stored in structs, | |
| /// moved between functions, and persisted across IPC boundaries. | |
| type Context: DigestOp<Output = T::Digest, Controller = Self>; | |
| /// Initialize a new digest computation context. | |
| /// | |
| /// Takes ownership of the controller and returns an owned context. | |
| /// The controller will be returned when the context is finalized or cancelled. | |
| /// | |
| /// # Parameters | |
| /// | |
| /// - `algorithm`: The zero-sized algorithm marker type specifying which hash function to use. | |
| /// | |
| /// # Returns | |
| /// | |
| /// An owned context that can be used for digest operations. | |
| fn init(self, algorithm: T) -> Result<Self::Context, Self::Error>; | |
| } | |
| /// Trait for performing digest operations with owned contexts. | |
| /// | |
| /// This trait uses move semantics where each operation consumes the | |
| /// context and returns a new context (for `update`) or the final result | |
| /// with a recovered controller (for `finalize`/`cancel`). | |
| /// | |
| /// # Move-based Safety | |
| /// | |
| /// The move-based pattern provides compile-time guarantees: | |
| /// - Cannot use a context after finalization | |
| /// - Cannot finalize the same context twice | |
| /// - Controller is always recovered for reuse | |
| /// | |
| /// # Examples | |
| /// | |
| /// ```rust,no_run | |
| /// # use openprot_hal_blocking::digest::*; | |
| /// # use openprot_hal_blocking::digest::owned::{DigestInit, DigestOp}; | |
| /// # struct MyContext; | |
| /// # impl ErrorType for MyContext { type Error = core::convert::Infallible; } | |
| /// # struct MyController; | |
| /// # impl DigestOp for MyContext { | |
| /// # type Output = Digest<8>; | |
| /// # type Controller = MyController; | |
| /// # fn update(self, _: &[u8]) -> Result<Self, Self::Error> { Ok(self) } | |
| /// # fn finalize(self) -> Result<(Self::Output, Self::Controller), Self::Error> { todo!() } | |
| /// # fn cancel(self) -> Self::Controller { todo!() } | |
| /// # } | |
| /// # fn get_context() -> MyContext { todo!() } | |
| /// let context = get_context(); // MyContext | |
| /// let context = context.update(b"hello")?; | |
| /// let context = context.update(b" world")?; | |
| /// let (digest, controller) = context.finalize()?; | |
| /// // Controller recovered for reuse | |
| /// # Ok::<(), core::convert::Infallible>(()) | |
| /// ``` | |
| pub trait DigestOp: ErrorType + Sized { | |
| /// The digest output type. | |
| /// | |
| /// This type represents the final digest value produced by [`finalize`](Self::finalize). | |
| /// It must implement [`IntoBytes`] to enable zero-copy conversion to byte arrays. | |
| type Output: IntoBytes; | |
| /// The controller type that will be recovered after finalization or cancellation. | |
| /// | |
| /// This enables resource recovery and reuse patterns essential for server applications. | |
| type Controller; | |
| /// Update the digest state with input data. | |
| /// | |
| /// This method consumes the current context and returns a new context with | |
| /// the updated state. This prevents use-after-update bugs at compile time | |
| /// through move semantics. | |
| /// | |
| /// # Parameters | |
| /// | |
| /// - `data`: Input data to be processed by the digest algorithm | |
| /// | |
| /// # Returns | |
| /// | |
| /// A new context with updated state, or an error | |
| fn update(self, data: &[u8]) -> Result<Self, Self::Error>; | |
| /// Finalize the digest computation and recover the controller. | |
| /// | |
| /// This method consumes the context and returns both the final digest output | |
| /// and the original controller, enabling resource reuse. | |
| /// | |
| /// # Returns | |
| /// | |
| /// A tuple containing the digest output and the recovered controller | |
| fn finalize(self) -> Result<(Self::Output, Self::Controller), Self::Error>; | |
| /// Cancel the digest computation and recover the controller. | |
| /// | |
| /// This method cancels the current computation and returns the controller | |
| /// in a clean state, ready for reuse. Unlike `finalize`, this cannot fail. | |
| /// | |
| /// # Returns | |
| /// | |
| /// The recovered controller in a clean state | |
| fn cancel(self) -> Self::Controller; | |
| } | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use super::*; | |
| #[test] | |
| fn test_digest_output_conversions() { | |
| // Test safe conversion methods on Digest type | |
| let sha256_digest = Digest::<8> { | |
| value: [1, 2, 3, 4, 5, 6, 7, 8], | |
| }; | |
| // Test into_array() method | |
| let array = sha256_digest.into_array(); | |
| assert_eq!(array, [1, 2, 3, 4, 5, 6, 7, 8]); | |
| // Test as_array() method | |
| let sha256_digest = Digest::<8> { | |
| value: [1, 2, 3, 4, 5, 6, 7, 8], | |
| }; | |
| let array_ref = sha256_digest.as_array(); | |
| assert_eq!(array_ref, &[1, 2, 3, 4, 5, 6, 7, 8]); | |
| // Test as_bytes() method | |
| let bytes = sha256_digest.as_bytes(); | |
| assert_eq!(bytes.len(), 32); // 8 words * 4 bytes each | |
| // Verify the bytes match the expected layout (little endian) | |
| let expected_bytes = [ | |
| 1, 0, 0, 0, // word 1 | |
| 2, 0, 0, 0, // word 2 | |
| 3, 0, 0, 0, // word 3 | |
| 4, 0, 0, 0, // word 4 | |
| 5, 0, 0, 0, // word 5 | |
| 6, 0, 0, 0, // word 6 | |
| 7, 0, 0, 0, // word 7 | |
| 8, 0, 0, 0, // word 8 | |
| ]; | |
| assert_eq!(bytes, &expected_bytes); | |
| } | |
| #[test] | |
| fn test_output_type_sizes() { | |
| use core::mem; | |
| // Verify that digest output types have correct sizes for IPC | |
| assert_eq!(mem::size_of::<Digest<8>>(), 32); // SHA-256: 8 words * 4 bytes | |
| assert_eq!(mem::size_of::<Digest<12>>(), 48); // SHA-384: 12 words * 4 bytes | |
| assert_eq!(mem::size_of::<Digest<16>>(), 64); // SHA-512: 16 words * 4 bytes | |
| // Test alignment requirements | |
| assert_eq!(mem::align_of::<Digest<8>>(), 4); // Aligned to u32 | |
| } | |
| #[test] | |
| fn test_digest_new_constructor() { | |
| let array = [ | |
| 0x12345678, 0x9abcdef0, 0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555, | |
| 0x66666666, | |
| ]; | |
| let digest = Digest::new(array); | |
| assert_eq!(digest.value, array); | |
| assert_eq!(digest.into_array(), array); | |
| } | |
| } |