Add transport-mctp for SPDM This imports transport-mctp from the OCP demo and updates it to use the stack facade now exported by MCTP.
diff --git a/services/spdm/transport-mctp/BUILD.bazel b/services/spdm/transport-mctp/BUILD.bazel new file mode 100644 index 0000000..eca6cbf --- /dev/null +++ b/services/spdm/transport-mctp/BUILD.bazel
@@ -0,0 +1,21 @@ +# 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", + "@oot_crates_no_std//:spdm-lib", + "@pigweed//pw_log/rust:pw_log", + ], +) + +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..0c74cc8 --- /dev/null +++ b/services/spdm/transport-mctp/src/lib.rs
@@ -0,0 +1,276 @@ +// 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 (via Stack channels) +//! - Message fragmentation (via MCTP layer) +//! - Request/response correlation (via MCTP tags) + +#![no_std] +#![warn(missing_docs)] + +use openprot_mctp_api::stack::{Stack, StackListener, StackReqChannel, StackRespChannel}; +use openprot_mctp_api::{MctpClient, MctpListener, MctpReqChannel, MctpRespChannel}; +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<'a, C: MctpClient> { + /// MCTP stack for transport operations + stack: &'a Stack<C>, + + /// Request channel for requester mode (outbound requests) + req_channel: Option<StackReqChannel<'a, C>>, + + /// Listener for responder mode (incoming requests) + listener: Option<StackListener<'a, C>>, + + /// Pending response channel (from last received request) + pending_resp: Option<StackRespChannel<'a, C>>, + + /// Remote endpoint ID (for requester mode) + remote_eid: Option<u8>, +} + +impl<'a, C: MctpClient> MctpSpdmTransport<'a, C> { + /// Create a new MCTP SPDM transport in requester mode. + /// + /// This will establish an MCTP request channel to the given remote EID. + pub fn new_requester(stack: &'a Stack<C>, remote_eid: u8) -> Self { + Self { + stack, + req_channel: None, + listener: None, + pending_resp: None, + remote_eid: Some(remote_eid), + } + } + + /// Create a new MCTP SPDM transport in responder mode. + /// + /// This will register an MCTP listener for SPDM message type. + pub fn new_responder(stack: &'a Stack<C>) -> Self { + Self { + stack, + req_channel: None, + listener: None, + pending_resp: None, + remote_eid: None, + } + } +} + +impl<C: MctpClient> SpdmTransport for MctpSpdmTransport<'_, C> { + /// Initialize the MCTP transport session. + /// + /// For **requester mode**: + /// - Establishes an MCTP request channel targeting the remote EID + /// + /// For **responder mode**: + /// - Registers an MCTP listener for SPDM message type (0x05) + /// + /// # Errors + /// + /// Returns `TransportError::DriverError` if channel allocation fails. + fn init_sequence(&mut self) -> TransportResult<()> { + if let Some(remote_eid) = self.remote_eid { + // Requester mode: get request channel for remote EID + pw_log::debug!("MctpSpdmTransport: req(eid={})", remote_eid as u32); + self.req_channel = Some( + self.stack + .req(remote_eid, 0) + .map_err(|e| { + pw_log::error!( + "MctpSpdmTransport: req(eid={}) failed: ResponseCode={}", + remote_eid as u32, + e.code as u8, + ); + TransportError::DriverError + })?, + ); + pw_log::debug!("MctpSpdmTransport: req channel allocated"); + } else { + // Responder mode: register listener for SPDM messages + pw_log::debug!( + "MctpSpdmTransport: listener(msg_type=0x{:02x})", + MCTP_MSG_TYPE_SPDM as u32 + ); + self.listener = Some( + self.stack + .listener(MCTP_MSG_TYPE_SPDM, 0) + .map_err(|e| { + pw_log::error!( + "MctpSpdmTransport: listener(msg_type=0x05) failed: ResponseCode={}", + e.code as u8, + ); + TransportError::DriverError + })?, + ); + pw_log::debug!("MctpSpdmTransport: listener allocated"); + } + + Ok(()) + } + + fn send_request<'a>(&mut self, dest_eid: u8, req: &mut MessageBuf<'a>) -> TransportResult<()> { + // Get the request channel (should be set by init_sequence) + let channel = self + .req_channel + .as_mut() + .ok_or(TransportError::NoRequestInFlight)?; + + // message_data() returns buffer[head..tail] — the full serialized SPDM message + // including header bytes that have been consumed by pull_data during encoding. + // data_len() only returns the uncommitted tail bytes and must NOT be used here. + let msg_data = req.message_data().map_err(|_| TransportError::SendError)?; + pw_log::debug!( + "send_request: eid={} len={} [0]={:#04x} [1]={:#04x}", + dest_eid as u32, + msg_data.len() as u32, + msg_data.first().copied().unwrap_or(0) as u32, + msg_data.get(1).copied().unwrap_or(0) as u32, + ); + + // Send via MCTP request channel + channel + .send(MCTP_MSG_TYPE_SPDM, msg_data) + .map_err(|_| TransportError::SendError)?; + + Ok(()) + } + + fn receive_response<'a>(&mut self, rsp: &mut MessageBuf<'a>) -> TransportResult<()> { + // Get the request channel + let channel = self + .req_channel + .as_mut() + .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, payload) = channel + .recv(&mut recv_buf) + .map_err(|_| TransportError::ReceiveError)?; + + // Verify message type + if meta.msg_type != MCTP_MSG_TYPE_SPDM { + return Err(TransportError::UnexpectedMessageType); + } + + pw_log::debug!( + "receive_response: len={} [0]={:#04x} [1]={:#04x}", + payload.len() as u32, + payload.first().copied().unwrap_or(0) as u32, + payload.get(1).copied().unwrap_or(0) as u32, + ); + + // Copy payload into MessageBuf + rsp.reserve(MCTP_HEADER_SIZE) + .map_err(|_| TransportError::BufferTooSmall)?; + rsp.put_data(payload.len()) + .map_err(|_| TransportError::BufferTooSmall)?; + + let rsp_buf = rsp + .data_mut(payload.len()) + .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 + let listener = self.listener.as_mut().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, payload, resp_channel) = listener + .recv(&mut recv_buf) + .map_err(|_| TransportError::ReceiveError)?; + + // Verify message type + if meta.msg_type != MCTP_MSG_TYPE_SPDM { + return Err(TransportError::UnexpectedMessageType); + } + + // Store response channel for send_response + self.pending_resp = Some(resp_channel); + + pw_log::debug!( + "receive_request: len={} [0]={:#04x} [1]={:#04x}", + payload.len() as u32, + payload.first().copied().unwrap_or(0) as u32, + payload.get(1).copied().unwrap_or(0) as u32, + ); + + // Copy payload into MessageBuf + req.reserve(MCTP_HEADER_SIZE) + .map_err(|_| TransportError::BufferTooSmall)?; + req.put_data(payload.len()) + .map_err(|_| TransportError::BufferTooSmall)?; + + let req_buf = req + .data_mut(payload.len()) + .map_err(|_| TransportError::BufferTooSmall)?; + req_buf.copy_from_slice(payload); + + Ok(()) + } + + fn send_response<'a>(&mut self, resp: &mut MessageBuf<'a>) -> TransportResult<()> { + // Get the pending response channel from last received request + let mut resp_channel = self + .pending_resp + .take() + .ok_or(TransportError::NoRequestInFlight)?; + + // message_data() returns buffer[head..tail] — the full serialized SPDM message. + let msg_data = resp.message_data().map_err(|_| TransportError::SendError)?; + pw_log::debug!( + "send_response: eid={} len={} [0]={:#04x} [1]={:#04x}", + resp_channel.remote_eid() as u32, + msg_data.len() as u32, + msg_data.first().copied().unwrap_or(0) as u32, + msg_data.get(1).copied().unwrap_or(0) as u32, + ); + + // Send response back to requester + resp_channel + .send(msg_data) + .map_err(|_| TransportError::SendError)?; + + Ok(()) + } + + fn max_message_size(&self) -> TransportResult<usize> { + Ok(MAX_SPDM_MESSAGE_SIZE) + } + + fn header_size(&self) -> usize { + MCTP_HEADER_SIZE + } +}