Skip to content

[feat] SPDM MCTP Transport Integration #54

@rusty1968

Description

@rusty1968

Integrating the SPDM responder task (using spdm-lib) into Hubris revealed an API mismatch with the mctp-rs transport layer.

Let's examine the mctp-echo sample to understand this issue:

MCTP Echo: Simple, Direct Flow

fn main() -> ! {
    let stack = mctp_api::Stack::from(MCTP.get_task_id());
    stack.set_eid(Eid(8)).unwrap_lite();
    let mut listener = stack.listener(MsgType(1), None).unwrap_lite();
    let mut recv_buf = [0; 255];

    loop {
        // 1. Receive message with RespChannel
        let (_, _, msg, mut resp) = listener.recv(&mut recv_buf).unwrap_lite();

        // 2. Immediately send response using RespChannel
        match resp.send(msg) {
            Ok(_) => {}
            Err(_e) => { /* handle error */ }
        }
        // 3. RespChannel drops here - perfect lifecycle
    }
}

Key Characteristics of Echo Pattern

  1. Immediate Processing: Receive → Process → Respond in same scope
  2. Direct RespChannel Usage: Uses the channel returned by recv() directly
  3. No Storage Required: RespChannel used immediately, then dropped
  4. Perfect Tag Correlation: Automatic MCTP tag correlation via RespChannel
  5. Simple Lifetime Management: No lifetime conflicts

Why Echo Pattern Works Perfectly

let (msg_type, msg_ic, msg, mut resp_channel) = listener.recv(&mut recv_buf)?;
//                           ^^^^^^^^^^^^^^^^
// resp_channel used immediately in same scope

resp_channel.send(response_data)?;  // ← Uses original tag automatically
// resp_channel drops here - no lifetime issues

MCTP Flow:

  1. Client sends request with tag 0x42
  2. Echo receives with RespChannel containing tag 0x42
  3. Echo responds via RespChannel.send() with tag 0x42
  4. Client correlates perfectly - same tag!

SPDM Responder: Complex, Deferred Flow

Complex, Deferred Flow

impl SpdmTransport for MctpSpdmTransport {
    // 1. RECEIVE PHASE: Store metadata, can't store RespChannel
    fn receive_request(&mut self, req: &mut MessageBuf) -> TransportResult<()> {
        let (msg_type, _msg_ic, msg, resp_channel) = self.listener.recv(&mut self.buffer)?;

        // PROBLEM: Cannot store resp_channel due to lifetimes
        // self.pending_resp_channel = Some(resp_channel);  // ← COMPILATION ERROR

        // WORKAROUND: Extract metadata only
        self.pending_eid = Some(resp_channel.remote_eid());
        self.pending_msg_type = Some(msg_type);
        // resp_channel DROPS HERE - tag information lost!

        // Copy message for later processing
        // ... message buffer operations
    }

    // 2. PROCESSING PHASE: spdm-lib processes the message
    // (Happens outside transport, in main loop)

    // 3. RESPONSE PHASE: Create new channel, losing tag correlation
    fn send_response(&mut self, resp: &mut MessageBuf) -> TransportResult<()> {
        let eid = self.pending_eid.take().unwrap();
        let typ = self.pending_msg_type.unwrap();

        // PROBLEM: Create NEW channel with NEW tag
        let mut req_chan = self.stack.req(eid, None)?;
        req_chan.send(typ, response_data)?;  // ← Creates fresh tag!
    }
}

Why SPDM Pattern Breaks

The SPDM responder cannot use the echo pattern because:

1. Architectural Constraint: spdm-lib Interface

// spdm-lib expects this trait interface
trait SpdmTransport {
    fn receive_request(&mut self, req: &mut MessageBuf) -> Result<()>;  // Phase 1
    fn send_response(&mut self, resp: &mut MessageBuf) -> Result<()>;   // Phase 3
}
// Phase 2 (processing) happens BETWEEN these calls in spdm-lib

The problem: receive_request() and send_response() are separate function calls with different scopes, but RespChannel has a lifetime tied to the recv() call.

2. Temporal Separation

// MCTP Echo: Single scope
let (_, _, msg, mut resp) = listener.recv(&mut buf)?;
resp.send(process_immediately(msg))?;  // Same scope - works!

// SPDM Responder: Multi-scope
fn receive_request() {
    let (_, _, msg, resp) = listener.recv(&mut buf)?;
    // resp must be used HERE or lost forever
    // But spdm-lib wants to process message later!
}
// ... time passes, spdm-lib processes ...
fn send_response() {
    // resp is long gone - need new channel
}

RespChannel has a lifetime to the recv call.

// mctp-api types are inherently tied to stack lifetime
pub struct MctpListener<'r> {
    stack: &'r Stack,  // ← Lifetime tied to stack
    handle: ipc::GenericHandle,
    timeout: u32,
}

pub struct MctpRespChannel<'r> {
    stack: &'r Stack,  // ← Same constraint
    handle: ipc::GenericHandle,
    eid: Eid,
    typ: MsgType,
    tv: TagValue,
}

I have tried to workaround the impedance mismatch by creating a request channel, but if I do that ReqChannel: Allocates new tag → ❌ Appears as independent request, not response.

According to MCTP specification (DSP0236), request/response pairs must share the same tag:

  1. Client sends request with tag 0x42
  2. Server must respond with tag 0x42 (same tag)
  3. Client correlates response using tag matching

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions