Create SpdmTransport interface to the MCTP library This implements spdm-lib's SpdmTransport trait object for the OpenPRoT MCTP Client. The current architecture ties a Requester to a Responder.
diff --git a/Cargo.lock b/Cargo.lock index e3cfac7..d6fd3b4 100644 --- a/Cargo.lock +++ b/Cargo.lock
@@ -500,10 +500,7 @@ name = "openprot-mctp-client" version = "0.1.0" dependencies = [ - "mctp", - "mctp-lib", "openprot-mctp-api", - "openprot-mctp-server", ] [[package]] @@ -574,6 +571,7 @@ version = "0.1.0" dependencies = [ "heapless 0.9.1", + "openprot-spdm-transport-mctp", "spdm-lib", ] @@ -582,6 +580,15 @@ version = "0.1.0" dependencies = [ "heapless 0.9.1", + "openprot-spdm-transport-mctp", + "spdm-lib", +] + +[[package]] +name = "openprot-spdm-transport-mctp" +version = "0.1.0" +dependencies = [ + "openprot-mctp-api", "spdm-lib", ]
diff --git a/Cargo.toml b/Cargo.toml index 16f3fbd..5e05e37 100644 --- a/Cargo.toml +++ b/Cargo.toml
@@ -20,6 +20,7 @@ "services/mctp/client", "services/spdm/requester", "services/spdm/responder", + "services/spdm/transport-mctp", "services/telemetry", "services/storage", ]
diff --git a/services/spdm/requester/BUILD.bazel b/services/spdm/requester/BUILD.bazel index 21f9159..0362cd5 100644 --- a/services/spdm/requester/BUILD.bazel +++ b/services/spdm/requester/BUILD.bazel
@@ -9,6 +9,7 @@ edition = "2024", visibility = ["//visibility:public"], deps = [ + "//services/spdm/transport-mctp:spdm_transport_mctp", "@rust_crates//:heapless", "@rust_crates//:spdm-lib", ],
diff --git a/services/spdm/requester/Cargo.toml b/services/spdm/requester/Cargo.toml index 753b13d..e87282e 100644 --- a/services/spdm/requester/Cargo.toml +++ b/services/spdm/requester/Cargo.toml
@@ -8,5 +8,6 @@ license = "Apache-2.0" [dependencies] +openprot-spdm-transport-mctp = { path = "../transport-mctp" } spdm-lib = { git = "https://github.com/9elements/spdm-lib.git", branch = "buildup" } heapless = { workspace = true }
diff --git a/services/spdm/responder/BUILD.bazel b/services/spdm/responder/BUILD.bazel index 9dd4e83..46cc63f 100644 --- a/services/spdm/responder/BUILD.bazel +++ b/services/spdm/responder/BUILD.bazel
@@ -9,6 +9,7 @@ edition = "2024", visibility = ["//visibility:public"], deps = [ + "//services/spdm/transport-mctp:spdm_transport_mctp", "@rust_crates//:heapless", "@rust_crates//:spdm-lib", ],
diff --git a/services/spdm/responder/Cargo.toml b/services/spdm/responder/Cargo.toml index a06a73b..7a20ccd 100644 --- a/services/spdm/responder/Cargo.toml +++ b/services/spdm/responder/Cargo.toml
@@ -8,5 +8,6 @@ license = "Apache-2.0" [dependencies] +openprot-spdm-transport-mctp = { path = "../transport-mctp" } spdm-lib = { git = "https://github.com/9elements/spdm-lib.git", branch = "buildup" } heapless = { workspace = true }
diff --git a/services/spdm/transport-mctp/BUILD.bazel b/services/spdm/transport-mctp/BUILD.bazel new file mode 100644 index 0000000..d44ad13 --- /dev/null +++ b/services/spdm/transport-mctp/BUILD.bazel
@@ -0,0 +1,20 @@ +# Licensed under the Apache-2.0 license + +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "spdm_transport_mctp", + srcs = glob(["src/**/*.rs"]), + crate_name = "openprot_spdm_transport_mctp", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//services/mctp/api:mctp_api", + "@rust_crates//:spdm-lib", + ], +) + +rust_test( + name = "spdm_transport_mctp_test", + crate = ":spdm_transport_mctp", +)
diff --git a/services/spdm/transport-mctp/Cargo.toml b/services/spdm/transport-mctp/Cargo.toml new file mode 100644 index 0000000..36a77c0 --- /dev/null +++ b/services/spdm/transport-mctp/Cargo.toml
@@ -0,0 +1,12 @@ +# Licensed under the Apache-2.0 license + +[package] +name = "openprot-spdm-transport-mctp" +version = "0.1.0" +edition = "2021" +description = "MCTP transport implementation for SPDM" +license = "Apache-2.0" + +[dependencies] +openprot-mctp-api = { path = "../../mctp/api" } +spdm-lib = { git = "https://github.com/9elements/spdm-lib.git", branch = "buildup" }
diff --git a/services/spdm/transport-mctp/README.md b/services/spdm/transport-mctp/README.md new file mode 100644 index 0000000..7b19d9e --- /dev/null +++ b/services/spdm/transport-mctp/README.md
@@ -0,0 +1,51 @@ +# openprot-spdm-transport-mctp + +MCTP transport layer for SPDM protocol communication. + +## Overview + +This crate implements the `SpdmTransport` trait from `spdm-lib` using MCTP as the underlying transport mechanism. It bridges the SPDM protocol layer with the MCTP messaging infrastructure. + +## MCTP Binding + +- **Message Type**: 0x05 (SPDM, per DMTF DSP0236 §4.2.1) +- **Max Message Size**: 2048 bytes +- **Fragmentation**: Handled by MCTP layer +- **Tag Correlation**: MCTP tags used for request/response matching + +## Modes + +### Requester Mode +Used by SPDM requesters (clients) that initiate attestation operations: +```rust +let transport = MctpSpdmTransport::new_requester(mctp_client, remote_eid); +``` + +### Responder Mode +Used by SPDM responders (servers) that handle attestation requests: +```rust +let transport = MctpSpdmTransport::new_responder(mctp_client); +``` + +## Transport Lifecycle + +1. **Initialize**: `init_sequence()` establishes MCTP handles + - Requester: Creates request handle to remote EID + - Responder: Registers listener for SPDM messages + +2. **Communication**: + - Requester: `send_request()` → `receive_response()` + - Responder: `receive_request()` → `send_response()` + +3. **Cleanup**: Automatically releases MCTP handles on drop + +## Dependencies + +- `openprot-mctp-api` — MCTP client trait and types +- `spdm-lib` — SPDM protocol library with transport trait + +## Usage + +This crate is used by both: +- `openprot-spdm-requester` — SPDM client implementation +- `openprot-spdm-responder` — SPDM server implementation
diff --git a/services/spdm/transport-mctp/src/lib.rs b/services/spdm/transport-mctp/src/lib.rs new file mode 100644 index 0000000..d1a3d26 --- /dev/null +++ b/services/spdm/transport-mctp/src/lib.rs
@@ -0,0 +1,241 @@ +// Licensed under the Apache-2.0 license + +//! MCTP-based SPDM Transport +//! +//! This module provides an implementation of the `SpdmTransport` trait from +//! spdm-lib that uses MCTP as the underlying transport layer. +//! +//! ## MCTP Binding +//! +//! SPDM messages are carried over MCTP using message type 0x05 (SPDM). +//! The transport handles: +//! - MCTP session management (handles for req/listener) +//! - Message fragmentation (via MCTP layer) +//! - Request/response correlation (via MCTP tags) + +#![no_std] +#![warn(missing_docs)] + +use openprot_mctp_api::{Handle, MctpClient, RecvMetadata}; +use spdm_lib::codec::MessageBuf; +use spdm_lib::platform::transport::{SpdmTransport, TransportError, TransportResult}; + +/// MCTP message type for SPDM (DMTF DSP0236 §4.2.1) +const MCTP_MSG_TYPE_SPDM: u8 = 0x05; + +/// Maximum SPDM message size over MCTP +const MAX_SPDM_MESSAGE_SIZE: usize = 2048; + +/// MCTP transport layer header size (none for SPDM over MCTP) +const MCTP_HEADER_SIZE: usize = 0; + +/// SPDM transport implementation using MCTP as the underlying transport. +/// +/// This transport can operate in two modes: +/// - **Requester mode**: Sends requests to a remote EID and receives responses +/// - **Responder mode**: Listens for incoming requests and sends responses +pub struct MctpSpdmTransport<C: MctpClient> { + /// MCTP client for transport operations + client: C, + + /// MCTP handle for requester mode (outbound requests) + req_handle: Option<Handle>, + + /// MCTP handle for responder mode (incoming requests) + listener_handle: Option<Handle>, + + /// Remote endpoint ID (for requester mode) + remote_eid: Option<u8>, + + /// Last received message metadata (for response correlation) + last_request_meta: Option<RecvMetadata>, +} + +impl<C: MctpClient> MctpSpdmTransport<C> { + /// Create a new MCTP SPDM transport in requester mode. + /// + /// This will establish an MCTP request handle to the given remote EID. + pub fn new_requester(client: C, remote_eid: u8) -> Self { + Self { + client, + req_handle: None, + listener_handle: None, + remote_eid: Some(remote_eid), + last_request_meta: None, + } + } + + /// Create a new MCTP SPDM transport in responder mode. + /// + /// This will register an MCTP listener for SPDM message type. + pub fn new_responder(client: C) -> Self { + Self { + client, + req_handle: None, + listener_handle: None, + remote_eid: None, + last_request_meta: None, + } + } +} + +impl<C: MctpClient> SpdmTransport for MctpSpdmTransport<C> { + /// Initialize the MCTP transport session. + /// + /// For **requester mode**: + /// - Establishes an MCTP request handle targeting the remote EID + /// + /// For **responder mode**: + /// - Registers an MCTP listener for SPDM message type (0x05) + /// + /// # Errors + /// + /// Returns `TransportError::DriverError` if MCTP handle allocation fails. + fn init_sequence(&mut self) -> TransportResult<()> { + if let Some(remote_eid) = self.remote_eid { + // Requester mode: get request handle for remote EID + self.req_handle = Some( + self.client + .req(remote_eid) + .map_err(|_| TransportError::DriverError)?, + ); + } else { + // Responder mode: register listener for SPDM messages + self.listener_handle = Some( + self.client + .listener(MCTP_MSG_TYPE_SPDM) + .map_err(|_| TransportError::DriverError)?, + ); + } + + Ok(()) + } + + fn send_request<'a>(&mut self, dest_eid: u8, req: &mut MessageBuf<'a>) -> TransportResult<()> { + // Get the request handle (should be set by init_sequence) + let handle = self.req_handle.ok_or(TransportError::NoRequestInFlight)?; + + // Get the message data from MessageBuf + let msg_len = req.data_len(); + let msg_data = req.data(msg_len).map_err(|_| TransportError::SendError)?; + + // Send via MCTP + self.client + .send( + Some(handle), + MCTP_MSG_TYPE_SPDM, + Some(dest_eid), + None, // Let MCTP allocate tag + false, // No integrity check for SPDM + msg_data, + ) + .map_err(|_| TransportError::SendError)?; + + Ok(()) + } + + fn receive_response<'a>(&mut self, rsp: &mut MessageBuf<'a>) -> TransportResult<()> { + // Get the request handle + let handle = self.req_handle.ok_or(TransportError::ResponseNotExpected)?; + + // Allocate receive buffer + let mut recv_buf = [0u8; MAX_SPDM_MESSAGE_SIZE]; + + // Receive via MCTP (blocking with no timeout) + let meta = self.client + .recv(handle, 0, &mut recv_buf) + .map_err(|_| TransportError::ReceiveError)?; + + // Verify message type + if meta.msg_type != MCTP_MSG_TYPE_SPDM { + return Err(TransportError::UnexpectedMessageType); + } + + // Copy payload into MessageBuf + let payload = &recv_buf[..meta.payload_size]; + rsp.reserve(MCTP_HEADER_SIZE).map_err(|_| TransportError::BufferTooSmall)?; + rsp.put_data(meta.payload_size).map_err(|_| TransportError::BufferTooSmall)?; + + let rsp_buf = rsp.data_mut(meta.payload_size).map_err(|_| TransportError::BufferTooSmall)?; + rsp_buf.copy_from_slice(payload); + + Ok(()) + } + + fn receive_request<'a>(&mut self, req: &mut MessageBuf<'a>) -> TransportResult<()> { + // Get the listener handle + let handle = self.listener_handle.ok_or(TransportError::DriverError)?; + + // Allocate receive buffer + let mut recv_buf = [0u8; MAX_SPDM_MESSAGE_SIZE]; + + // Receive via MCTP (blocking with no timeout) + let meta = self.client + .recv(handle, 0, &mut recv_buf) + .map_err(|_| TransportError::ReceiveError)?; + + // Verify message type + if meta.msg_type != MCTP_MSG_TYPE_SPDM { + return Err(TransportError::UnexpectedMessageType); + } + + // Store metadata for response correlation + self.last_request_meta = Some(meta); + + // Copy payload into MessageBuf + let payload = &recv_buf[..meta.payload_size]; + req.reserve(MCTP_HEADER_SIZE).map_err(|_| TransportError::BufferTooSmall)?; + req.put_data(meta.payload_size).map_err(|_| TransportError::BufferTooSmall)?; + + let req_buf = req.data_mut(meta.payload_size).map_err(|_| TransportError::BufferTooSmall)?; + req_buf.copy_from_slice(payload); + + Ok(()) + } + + fn send_response<'a>(&mut self, resp: &mut MessageBuf<'a>) -> TransportResult<()> { + // Get metadata from last received request + let meta = self.last_request_meta.ok_or(TransportError::NoRequestInFlight)?; + + // Get the response data from MessageBuf + let msg_len = resp.data_len(); + let msg_data = resp.data(msg_len).map_err(|_| TransportError::SendError)?; + + // Send response back to requester + self.client + .send( + None, // No handle for responses + MCTP_MSG_TYPE_SPDM, + Some(meta.remote_eid), // Back to requester + Some(meta.msg_tag), // Use same tag for correlation + meta.msg_ic, // Match integrity check + msg_data, + ) + .map_err(|_| TransportError::SendError)?; + + // Clear request metadata + self.last_request_meta = None; + + Ok(()) + } + + fn max_message_size(&self) -> TransportResult<usize> { + Ok(MAX_SPDM_MESSAGE_SIZE) + } + + fn header_size(&self) -> usize { + MCTP_HEADER_SIZE + } +} + +impl<C: MctpClient> Drop for MctpSpdmTransport<C> { + fn drop(&mut self) { + // Clean up MCTP handles + if let Some(handle) = self.req_handle { + self.client.drop_handle(handle); + } + if let Some(handle) = self.listener_handle { + self.client.drop_handle(handle); + } + } +}