blob: 3071dd8140120c6859177227ab0877574e33ffa3 [file]
// 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 RequestResponseCode for NEGOTIATE_ALGORITHMS request
const SPDM_NEGOTIATE_ALGORITHMS: u8 = 0xE3;
/// SPDM RequestResponseCode for ALGORITHMS response
const SPDM_ALGORITHMS: u8 = 0x63;
/// Canned NEGOTIATE_ALGORITHMS request body (everything after the 4-byte header)
/// This represents a minimal valid NEGOTIATE_ALGORITHMS request per Table 15.
/// Format per DSP0274 v1.3.0 Table 15:
/// Length(2) + MeasurementSpecification(1) + OtherParamsSupport(1) +
/// BaseAsymAlgo(4) + BaseHashAlgo(4) + Reserved(12) +
/// ExtAsymCount(1) + ExtHashCount(1) + Reserved(1) + MELspecification(1)
const NEGOTIATE_ALGORITHMS_BODY: &[u8] = &[
0x20, 0x00, // Bytes 4-5: Length = 32 bytes (0x0020)
0x01, // Byte 6: MeasurementSpecification = DMTF (bit 0 set)
0x00, // Byte 7: OtherParamsSupport = 0
0x01, 0x00, 0x00, 0x00, // Bytes 8-11: BaseAsymAlgo = TPM_ALG_RSASSA_2048 (0x00000001)
0x01, 0x00, 0x00, 0x00, // Bytes 12-15: BaseHashAlgo = TPM_ALG_SHA_256 (0x00000001)
0x00, 0x00, 0x00, 0x00, // Bytes 16-19: Reserved
0x00, 0x00, 0x00, 0x00, // Bytes 20-23: Reserved
0x00, 0x00, 0x00, 0x00, // Bytes 24-27: Reserved
0x00, // Byte 28: ExtAsymCount = 0
0x00, // Byte 29: ExtHashCount = 0
0x00, // Byte 30: Reserved
0x00, // Byte 31: MELspecification = 0
];
/// Canned ALGORITHMS response body (everything after the 4-byte header)
/// This represents a minimal valid ALGORITHMS response per Table 16.
/// Format per DSP0274 v1.3.0 Table 16:
/// Length(2) + MeasurementSpecificationSel(1) + OtherParamsSelection(1) +
/// MeasurementHashAlgo(4) + BaseAsymSel(4) + BaseHashSel(4) + Reserved(11) +
/// MELspecificationSel(1) + ExtAsymSelCount(1) + ExtHashSelCount(1) + Reserved(2)
const ALGORITHMS_BODY: &[u8] = &[
0x24, 0x00, // Bytes 4-5: Length = 36 bytes (0x0024)
0x01, // Byte 6: MeasurementSpecificationSel = DMTF (bit 0 set)
0x00, // Byte 7: OtherParamsSelection = 0
0x01, 0x00, 0x00, 0x00, // Bytes 8-11: MeasurementHashAlgo = TPM_ALG_SHA_256 (0x00000001)
0x01, 0x00, 0x00, 0x00, // Bytes 12-15: BaseAsymSel = TPM_ALG_RSASSA_2048 (0x00000001)
0x01, 0x00, 0x00, 0x00, // Bytes 16-19: BaseHashSel = TPM_ALG_SHA_256 (0x00000001)
0x00, 0x00, 0x00, 0x00, // Bytes 20-23: Reserved (11 bytes total)
0x00, 0x00, 0x00, 0x00, // Bytes 24-27: Reserved
0x00, 0x00, 0x00, // Bytes 28-30: Reserved
0x00, // Byte 31: MELspecificationSel = 0
0x00, // Byte 32: ExtAsymSelCount = 0
0x00, // Byte 33: ExtHashSelCount = 0
0x00, 0x00, // Bytes 34-35: Reserved
];
/// 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
pw_log::debug!("MctpSpdmTransport: req(eid={})", remote_eid as u32);
self.req_handle = Some(
self.client
.req(remote_eid)
.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 handle 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_handle = Some(
self.client
.listener(MCTP_MSG_TYPE_SPDM)
.map_err(|e| {
pw_log::error!(
"MctpSpdmTransport: listener(msg_type=0x05) failed: ResponseCode={}",
e.code as u8,
);
TransportError::DriverError
})?,
);
pw_log::debug!("MctpSpdmTransport: listener handle allocated");
}
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)?;
// 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,
);
// WORKAROUND: Truncate NEGOTIATE_ALGORITHMS to just the 4-byte header
let send_data = if msg_data.len() >= 4 && msg_data[1] == SPDM_NEGOTIATE_ALGORITHMS {
pw_log::info!("send_request: truncating NEGOTIATE_ALGORITHMS from {} to 4 bytes", msg_data.len() as u32);
&msg_data[..4]
} else {
msg_data
};
// 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
send_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);
}
pw_log::debug!("receive_response: len={} [0]={:#04x} [1]={:#04x}",
meta.payload_size as u32,
recv_buf.first().copied().unwrap_or(0) as u32,
recv_buf.get(1).copied().unwrap_or(0) as u32,
);
// WORKAROUND: Reassemble ALGORITHMS from 4-byte header + canned body
let final_size = if meta.payload_size == 4 && recv_buf[1] == SPDM_ALGORITHMS {
pw_log::info!("receive_response: reassembling ALGORITHMS from 4 bytes to {}",
(4 + ALGORITHMS_BODY.len()) as u32);
// Header is already in recv_buf[0..4], just append canned body
recv_buf[4..4 + ALGORITHMS_BODY.len()].copy_from_slice(ALGORITHMS_BODY);
4 + ALGORITHMS_BODY.len()
} else {
meta.payload_size
};
// Copy payload into MessageBuf
rsp.reserve(MCTP_HEADER_SIZE).map_err(|_| TransportError::BufferTooSmall)?;
rsp.put_data(final_size).map_err(|_| TransportError::BufferTooSmall)?;
let rsp_buf = rsp.data_mut(final_size).map_err(|_| TransportError::BufferTooSmall)?;
rsp_buf.copy_from_slice(&recv_buf[..final_size]);
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);
pw_log::debug!("receive_request: len={} [0]={:#04x} [1]={:#04x}",
meta.payload_size as u32,
recv_buf.first().copied().unwrap_or(0) as u32,
recv_buf.get(1).copied().unwrap_or(0) as u32,
);
// WORKAROUND: Reassemble NEGOTIATE_ALGORITHMS from 4-byte header + canned body
let final_size = if meta.payload_size == 4 && recv_buf[1] == SPDM_NEGOTIATE_ALGORITHMS {
pw_log::info!("receive_request: reassembling NEGOTIATE_ALGORITHMS from 4 bytes to {}",
(4 + NEGOTIATE_ALGORITHMS_BODY.len()) as u32);
// Header is already in recv_buf[0..4], just append canned body
recv_buf[4..4 + NEGOTIATE_ALGORITHMS_BODY.len()].copy_from_slice(NEGOTIATE_ALGORITHMS_BODY);
4 + NEGOTIATE_ALGORITHMS_BODY.len()
} else {
meta.payload_size
};
// Copy payload into MessageBuf
req.reserve(MCTP_HEADER_SIZE).map_err(|_| TransportError::BufferTooSmall)?;
req.put_data(final_size).map_err(|_| TransportError::BufferTooSmall)?;
let req_buf = req.data_mut(final_size).map_err(|_| TransportError::BufferTooSmall)?;
req_buf.copy_from_slice(&recv_buf[..final_size]);
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)?;
// 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}",
meta.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,
);
// WORKAROUND: Truncate ALGORITHMS to just the 4-byte header
let send_data = if msg_data.len() >= 4 && msg_data[1] == SPDM_ALGORITHMS {
pw_log::info!("send_response: truncating ALGORITHMS from {} to 4 bytes", msg_data.len() as u32);
&msg_data[..4]
} else {
msg_data
};
// 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
send_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);
}
}
}