tree: 8a78e1bb586a34f8cbbc1d6d5a5ea46f8ac3e8e3
  1. examples/
  2. src/
  3. BUILD.bazel
  4. Cargo.toml
  5. README.md
services/mctp/transport-loopback/README.md

MCTP Transport Loopback

In-memory loopback transport for MCTP testing using fixed-size buffers (no dynamic allocation).

Overview

This crate provides a formalized loopback transport for MCTP that enables two endpoints to communicate entirely in-memory without any physical transport (I2C, PCIe, etc.).

Key feature: Uses fixed-size buffers to mirror the memory behavior of transport-i2c, making it compatible with no_std embedded environments without requiring a global allocator.

This is useful for:

  • Unit and integration testing of MCTP applications
  • Testing SPDM over MCTP without hardware
  • Developing and testing MCTP-based protocols in pure Rust
  • Running tests on bare-metal targets (AST1060, etc.)

Architecture

The loopback transport consists of:

  1. PacketBuffer - Fixed-size ring buffer for storing packets (max 16 packets of 255 bytes each)
  2. LoopbackPair - A pair of connected MCTP endpoints
  3. LoopbackClient - An MctpClient implementation for each endpoint
  4. BufferSender - Captures outbound packets into a PacketBuffer
  5. transfer() - Moves packets from one endpoint to another

Memory Behavior

Mirroring transport-i2c:

  • Fixed-size buffers: All packet storage uses stack-allocated arrays
  • No dynamic allocation: Compatible with no_std without requiring alloc
  • Compile-time bounds: Maximum 16 packets of 255 bytes each per endpoint
  • No Vec, no Box, no heap: Can run on bare-metal AST1060-EVB

Usage

use openprot_mctp_transport_loopback::{LoopbackPair, PacketBuffer};
use openprot_mctp_api::MctpClient;
use core::cell::RefCell;

// Create packet buffers (stack-allocated, no heap required)
let packets_a = RefCell::new(PacketBuffer::new());
let packets_b = RefCell::new(PacketBuffer::new());

// Create a loopback pair with EIDs 8 and 42
let pair = LoopbackPair::<16>::new(8, 42, &packets_a, &packets_b);

// Get client handles for each endpoint
let client_a = pair.client_a();
let client_b = pair.client_b();

// Use clients normally
let handle_a = client_a.req(42).unwrap();
client_a.send(Some(handle_a), 1, None, None, false, b"hello").unwrap();

// Transfer packets from A to B
pair.transfer_a_to_b();

// Receive on B
let handle_b = client_b.listener(1).unwrap();
let mut buf = [0u8; 256];
let meta = client_b.recv(handle_b, 0, &mut buf).unwrap();
assert_eq!(&buf[..meta.payload_size], b"hello");

Design

This transport uses the same memory pattern as transport-i2c:

transport-i2c sender:

let mut pkt = [0u8; mctp_lib::serial::MTU_MAX];  // Stack-allocated

transport-loopback:

pub struct PacketBuffer {
    packets: [[u8; 255]; 16],  // Stack-allocated
    lengths: [usize; 16],
    count: usize,
}

Both avoid dynamic allocation entirely, making them suitable for embedded environments without heap allocation.

Differences from Original Version

The original transport-loopback used Vec<Vec<u8>> which required:

  • extern crate alloc
  • #[global_allocator]
  • Heap memory

This version uses fixed-size arrays which:

  • ✅ Works in pure no_std without alloc
  • ✅ No global allocator needed
  • ✅ Can run on AST1060-EVB without custom allocators
  • ✅ Matches memory semantics of transport-i2c
  • ⚠️ Limited to 16 buffered packets per endpoint (configurable constant)

Constants

pub const MAX_PACKET_SIZE: usize = 255;        // MCTP MTU
pub const MAX_BUFFERED_PACKETS: usize = 16;    // Max packets per endpoint

To change packet buffer depth, modify MAX_BUFFERED_PACKETS in lib.rs.

Example with SPDM

See examples/spdm_usage.md for full SPDM-over-MCTP testing examples.

Quick example:

use openprot_mctp_transport_loopback::{LoopbackPair, PacketBuffer};
use openprot_spdm_transport_mctp::MctpSpdmTransport;
use core::cell::RefCell;

// Create loopback infrastructure (no heap required)
let packets_a = RefCell::new(PacketBuffer::new());
let packets_b = RefCell::new(PacketBuffer::new());
let pair = LoopbackPair::<16>::new(8, 42, &packets_a, &packets_b);

// Create SPDM transports
let mut spdm_requester = MctpSpdmTransport::new_requester(pair.client_a(), 42);
let mut spdm_responder = MctpSpdmTransport::new_responder(pair.client_b());

// Initialize
spdm_requester.init_sequence().unwrap();
spdm_responder.init_sequence().unwrap();

// Test SPDM protocol messages...

Testing

The implementation includes comprehensive unit tests:

# Run tests (uses std for test harness)
bazel test //services/mctp/transport-loopback:transport_loopback_test

Tests verify:

  • ✅ PacketBuffer operations (push, iter, clear)
  • ✅ Basic unidirectional message transfer
  • ✅ Bidirectional request/response flow
  • ✅ Roundtrip helper method

License

Licensed under the Apache-2.0 license.