mctp-api: add Stack facade and update mctp_echo to use high-level traits - Add services/mctp/api/src/stack.rs: Stack<C: MctpClient> facade that wraps any MctpClient and returns StackListener, StackReqChannel and StackRespChannel implementing the MctpListener / MctpReqChannel / MctpRespChannel traits. All channel types release server handles on Drop. - Update services/mctp/api/src/lib.rs: expose stack module and re-export Stack, StackListener, StackReqChannel, StackRespChannel. - Update target/ast1060-evb/mctp/mctp_echo.rs: replace direct MctpClient calls with Stack::new(IpcMctpClient) + MctpListener / MctpRespChannel traits, matching the Hubris mctp-echo task structure. - Update services/mctp/server/tests/integration.rs: add Stack facade tests exercising echo-via-Stack and req/resp roundtrip using DirectClient as the MctpClient strategy. - Update services/mctp/api/README.md: document the two-layer abstraction, Stack API, Strategy pattern, and wire protocol.
diff --git a/services/mctp/api/README.md b/services/mctp/api/README.md index b034290..de26023 100644 --- a/services/mctp/api/README.md +++ b/services/mctp/api/README.md
@@ -1,27 +1,99 @@ # openprot-mctp-api -Platform-independent MCTP types and traits crate. +Platform-independent MCTP types, traits, and stack facade. ## Overview -This crate defines the core API contract between MCTP clients and the MCTP server. It provides traits for client operations, listener management, and request/response channels, as well as the binary IPC wire protocol used for inter-process communication. +This crate defines the API contract between MCTP applications and the MCTP server. +It provides two layers: + +1. **`MctpClient` trait** — low-level interface mirroring the IPC wire operations + (req, listener, recv, send, drop_handle). Platform-specific crates such as + `openprot-mctp-client` implement this trait using the OS transport (e.g. Pigweed IPC). + +2. **`Stack` facade** (`stack` module) — high-level entry point that wraps any `MctpClient` + and returns typed channel objects (`StackListener`, `StackReqChannel`, `StackRespChannel`) + that implement the `MctpListener` / `MctpReqChannel` / `MctpRespChannel` traits. + +This two-layer design hides both the **concrete MCTP stack implementation** (which lives +inside the server process) and the **OS / IPC transport** from application code. +Applications depend only on the high-level traits; swapping the transport or stack +requires no application changes. + +```text +┌─────────────────────┐ +│ Application │ uses MctpListener / MctpReqChannel / MctpRespChannel traits +└─────────┬───────────┘ + │ + ▼ +┌─────────────────────┐ +│ Stack (this crate)│ wraps any MctpClient, returns typed channel handles +└─────────┬───────────┘ + │ MctpClient trait + ▼ +┌─────────────────────┐ +│ IpcMctpClient │ encodes wire protocol, calls OS IPC (e.g. Pigweed channel_transact) +│ (mctp-client crate)│ +└─────────┬───────────┘ + │ IPC + ▼ +┌─────────────────────┐ +│ MCTP Server │ owns the concrete MCTP stack (mctp-lib, etc.) +└─────────────────────┘ +``` ## Key Types -- `Handle` — opaque handle for listeners, requests, or response channels -- `RecvMetadata` — metadata from a successful receive (msg_type, tag, remote_eid, payload_size, etc.) -- `MctpError` / `ResponseCode` — error types (Success, InternalError, NoSpace, AddrInUse, TimedOut, BadArgument, ServerRestarted) +- `Handle` — opaque handle for listeners, request, or response channels +- `RecvMetadata` — metadata from a successful receive (msg_type, tag, remote_eid, payload_size) +- `MctpError` / `ResponseCode` — error types (InternalError, NoSpace, AddrInUse, TimedOut, BadArgument, ServerRestarted) -## Traits +## High-level API (`stack` module) -- `MctpClient` — main client interface (req, listener, get/set EID, recv, send, drop_handle) -- `MctpListener` — receiving incoming MCTP messages of a specific type -- `MctpReqChannel` — request/response channels -- `MctpRespChannel` — response channels +| Type | Trait | Obtained via | +|------|-------|--------------| +| `Stack<C>` | — | `Stack::new(client)` | +| `StackListener<'_, C>` | `MctpListener` | `stack.listener(msg_type, timeout)` | +| `StackReqChannel<'_, C>` | `MctpReqChannel` | `stack.req(eid, timeout)` | +| `StackRespChannel<'_, C>` | `MctpRespChannel` | returned by `StackListener::recv` | -## Wire Protocol +All channel types release their server-side handle automatically on `Drop`. -The `wire` module implements binary request/response encoding for IPC communication between userspace processes and the MCTP server. +## Low-level API (`MctpClient` trait) + +| Method | Description | +|--------|-------------| +| `req(eid)` | Allocate a request handle for a remote EID | +| `listener(msg_type)` | Register to receive messages of a given type | +| `get_eid() / set_eid(eid)` | Read/write the local endpoint ID | +| `recv(handle, timeout, buf)` | Receive a message on a handle | +| `send(handle, msg_type, eid, tag, ic, buf)` | Send a message (request or response) | +| `drop_handle(handle)` | Release a handle | + +## Design: Strategy Pattern + +`Stack<C: MctpClient>` applies the **Strategy pattern**: + +- **Context** → `Stack<C>` holds the strategy and exposes the high-level API +- **Strategy trait** → `MctpClient` defines the IPC operations (req, listener, recv, send, drop_handle) +- **Concrete strategies** → `IpcMctpClient` (Pigweed IPC), test `DirectClient`, future Linux socket client + +Applications code against `MctpListener` / `MctpReqChannel` / `MctpRespChannel` traits and never +see the strategy. The concrete `MctpClient` implementation is injected via `Stack::new(client)`. + +This gives two independent axes of variation: + +| Concern | How to swap | +|---------|-------------| +| MCTP stack implementation (mctp-lib, etc.) | Replace the server process — no API change | +| OS / IPC transport | Provide a different `MctpClient` impl to `Stack::new` | +| Application logic | Written against the high-level traits — unchanged across both | + + + +The `wire` module implements binary request/response encoding for IPC communication. +It is used internally by `openprot-mctp-client` and the server; applications do not +use it directly. ## Dependencies
diff --git a/services/mctp/api/STACK-DESIGN.md b/services/mctp/api/STACK-DESIGN.md new file mode 100644 index 0000000..2200e2c --- /dev/null +++ b/services/mctp/api/STACK-DESIGN.md
@@ -0,0 +1,179 @@ +# MCTP Stack Design + +## Pattern: Facade + Factory Method + Strategy + +`Stack<C>` is a **Facade** over any `MctpClient` implementation. It exposes three +factory methods (`req`, `listener`, via listener's `recv` for resp) that produce +typed channel handles. The `C: MctpClient` bound is a compile-time **Strategy**, +making the transport completely swappable without changing call-site code. + +--- + +## Type Hierarchy + +``` + «trait» + MctpClient + ┌────────────┐ + │ req() │ + │ listener() │ + │ send() │ + │ recv() │ + │ drop_handle│ + │ get/set_eid│ + └─────┬──────┘ + │ implemented by + ┌───────────┼───────────┐ + │ │ │ + IpcMctpClient LinuxClient (future) + (Hubris IPC) (sockets) +``` + +--- + +## Stack Facade + +``` + Application code + │ + │ only sees traits: + │ MctpReqChannel / MctpListener / MctpRespChannel + │ + ▼ +┌─────────────────────────────────────────────────┐ +│ Stack<C: MctpClient> │ +│ │ +│ ┌──────────────────────────────────────────┐ │ +│ │ + new(client: C) → Stack<C> │ │ +│ │ + get_eid() → u8 │ │ +│ │ + set_eid(eid) → Result │ │ +│ │ │ │ ◄── Facade +│ │ + req(eid, timeout) │ │ +│ │ → StackReqChannel<'_, C> │ │ +│ │ │ │ +│ │ + listener(msg_type, timeout) │ │ +│ │ → StackListener<'_, C> │ │ +│ └──────────────────────────────────────────┘ │ +│ │ +│ client: C ◄── Strategy (hidden from callers) │ +└─────────────────────────────────────────────────┘ +``` + +--- + +## Channel Products (Factory Method) + +``` +Stack::req() Stack::listener() + │ │ + ▼ ▼ +┌──────────────────────┐ ┌───────────────────────┐ +│ StackReqChannel<C> │ │ StackListener<C> │ +│ │ │ │ +│ handle: Handle │ │ handle: Handle │ +│ eid: u8 │ │ timeout: u32 │ +│ sent_tag: Option<u8>│ │ stack: &Stack<C> │ +│ timeout: u32 │ └────────────┬──────────┘ +│ stack: &Stack<C> │ │ +└──────────┬───────────┘ │ recv() returns + │ ▼ + │ implements ┌──────────────────────┐ + ▼ │ StackRespChannel<C> │ + «trait» │ │ + MctpReqChannel │ stack: &Stack<C> │ + ┌──────────────┐ │ eid: u8 │ + │ send() │ │ msg_type: u8 │ + │ recv() │ │ tag: u8 │ + │ remote_eid() │ └──────────┬───────────┘ + └──────────────┘ │ implements + ▼ + «trait» + MctpRespChannel + ┌─────────────┐ + │ send() │ + │ remote_eid()│ + └─────────────┘ +``` + +--- + +## Full Call-Flow: Server (Listener) Path + +``` + App Stack<C> StackListener<C> MctpClient (C) + │ │ │ │ + │ listener(type, t) │ │ │ + │─────────────────────►│ │ │ + │ │── listener(type) ──►│ │ + │ │ │─── client.listener() ►│ + │ │ │◄── Handle ────────────│ + │◄── StackListener ────│ │ │ + │ │ │ │ + │ recv(&mut buf) │ │ │ + │────────────────────────────────────────────►│ │ + │ │ │── client.recv() ─────►│ + │ │ │◄── RecvMetadata ──────│ + │ │ │ builds StackRespChannel + │◄── (meta, payload, StackRespChannel) ───────│ │ + │ │ │ │ + │ resp.send(&reply) │ │ │ + │─────────────────────────────────────────────────────────────────► │ + │ │ │ client.send(None,..) │ + │◄── Ok(()) ────────────────────────────────────────────────────────│ + │ │ │ │ + │ [drop listener] │ │ │ + │────────────────────────────────────────────►│ │ + │ │ │── client.drop_handle()►│ +``` + +--- + +## Full Call-Flow: Client (Request) Path + +``` + App Stack<C> StackReqChannel<C> MctpClient (C) + │ │ │ │ + │ req(eid, timeout) │ │ │ + │─────────────────────►│ │ │ + │ │── client.req(eid) ──────────────────────► │ + │ │◄── Handle ─────────────────────────────── │ + │◄── StackReqChannel ──│ │ │ + │ │ │ │ + │ send(msg_type, buf) │ │ │ + │────────────────────────────────────────── ►│ │ + │ │ │── client.send(..) ───►│ + │ │ │◄── tag ───────────────│ + │ │ │ sent_tag = Some(tag) │ + │◄── Ok(()) ───────────────────────────── ──│ │ + │ │ │ │ + │ recv(&mut buf) │ │ │ + │────────────────────────────────────────── ►│ │ + │ │ │── client.recv(..) ───►│ + │ │ │◄── RecvMetadata ──────│ + │◄── (meta, payload) ──────────────────── ──│ │ + │ │ │ │ + │ [drop channel] │ │ │ + │────────────────────────────────────────── ►│ │ + │ │ │── client.drop_handle()►│ +``` + +--- + +## Design Patterns Summary + +| Pattern | Where | Effect | +|---------|-------|--------| +| **Facade** | `Stack<C>` | Single entry point; hides `MctpClient` complexity and handle lifecycle | +| **Factory Method** | `Stack::req()`, `Stack::listener()` | Produces typed channel structs with lifetime-bound borrows | +| **Strategy** | `C: MctpClient` generic | Transport swapped at compile time — IPC, sockets, mock, etc. | +| **RAII / Handle Guard** | `Drop` on `StackReqChannel` & `StackListener` | Handles are released automatically; no explicit cleanup needed | + +### Why Facade fits perfectly here + +The classic Facade pattern calls for: + +1. A complex subsystem with many low-level operations — ✓ (`MctpClient`: `req`, `listener`, `send`, `recv`, `drop_handle`, `set_eid`) +2. A simplified, cohesive interface for clients — ✓ (`Stack`: `req()` / `listener()` / `get_eid()` / `set_eid()`) +3. The subsystem remaining accessible directly if needed — ✓ (callers can use `MctpClient` directly; `Stack` does not prevent it) + +The Strategy (generic `C`) is a natural complement: the Facade is stable, the strategy behind it changes per platform.
diff --git a/services/mctp/api/src/lib.rs b/services/mctp/api/src/lib.rs index ffe5b7c..9b1178c 100644 --- a/services/mctp/api/src/lib.rs +++ b/services/mctp/api/src/lib.rs
@@ -36,10 +36,12 @@ #![warn(missing_docs)] mod error; +pub mod stack; mod traits; pub mod wire; pub use error::{MctpError, ResponseCode}; +pub use stack::{Stack, StackListener, StackReqChannel, StackRespChannel}; pub use traits::{MctpClient, MctpListener, MctpReqChannel, MctpRespChannel}; /// An opaque handle for a listener, request, or response channel.
diff --git a/services/mctp/api/src/stack.rs b/services/mctp/api/src/stack.rs new file mode 100644 index 0000000..58ff418 --- /dev/null +++ b/services/mctp/api/src/stack.rs
@@ -0,0 +1,223 @@ +// Licensed under the Apache-2.0 license + +//! High-level MCTP stack facade +//! +//! Bridges any [`MctpClient`] implementation to the abstract +//! [`MctpListener`], [`MctpReqChannel`], and [`MctpRespChannel`] traits, +//! hiding both the concrete MCTP stack implementation and the underlying +//! OS / transport mechanism. +//! +//! ## Usage +//! +//! ```rust,ignore +//! use openprot_mctp_client::IpcMctpClient; +//! use openprot_mctp_api::stack::Stack; +//! use openprot_mctp_api::{MctpListener, MctpReqChannel, MctpRespChannel}; +//! +//! let stack = Stack::new(IpcMctpClient::new(handle::MCTP)); +//! stack.set_eid(8).unwrap(); +//! +//! // Server side: receive a request and reply +//! let mut listener = stack.listener(MSG_TYPE_SPDM, 0).unwrap(); +//! let (meta, payload, mut resp) = listener.recv(&mut buf).unwrap(); +//! resp.send(&reply).unwrap(); +//! +//! // Client side: send a request and receive the response +//! let mut req = stack.req(remote_eid, 0).unwrap(); +//! req.send(MSG_TYPE_SPDM, &msg).unwrap(); +//! let (meta, response) = req.recv(&mut buf).unwrap(); +//! ``` + +use crate::{Handle, MctpClient, MctpError, RecvMetadata, ResponseCode}; +use crate::traits::{MctpListener, MctpReqChannel, MctpRespChannel}; + +// ============================================================================ +// Stack +// ============================================================================ + +/// An MCTP stack facade backed by any [`MctpClient`] implementation. +/// +/// `Stack` is the entry point for application code. It wraps a concrete +/// `MctpClient` and returns typed channel handles whose methods implement +/// the standard MCTP traits. Applications only depend on those traits; +/// the underlying stack implementation and OS transport are invisible. +pub struct Stack<C: MctpClient> { + client: C, +} + +impl<C: MctpClient> Stack<C> { + /// Create a new stack backed by the given `MctpClient`. + pub fn new(client: C) -> Self { + Stack { client } + } + + /// Get the local endpoint ID. + pub fn get_eid(&self) -> u8 { + self.client.get_eid() + } + + /// Set the local endpoint ID. + pub fn set_eid(&self, eid: u8) -> Result<(), MctpError> { + self.client.set_eid(eid) + } + + /// Open an outbound request channel to `eid`. + /// + /// `timeout_millis` of 0 means no timeout (block indefinitely). + pub fn req( + &self, + eid: u8, + timeout_millis: u32, + ) -> Result<StackReqChannel<'_, C>, MctpError> { + let handle = self.client.req(eid)?; + Ok(StackReqChannel { + stack: self, + handle, + eid, + sent_tag: None, + timeout: timeout_millis, + }) + } + + /// Register a listener for incoming messages of the given MCTP type. + /// + /// `timeout_millis` of 0 means no timeout (block indefinitely). + pub fn listener( + &self, + msg_type: u8, + timeout_millis: u32, + ) -> Result<StackListener<'_, C>, MctpError> { + let handle = self.client.listener(msg_type)?; + Ok(StackListener { + stack: self, + handle, + timeout: timeout_millis, + }) + } +} + +// ============================================================================ +// Request channel +// ============================================================================ + +/// A request channel for sending MCTP requests and receiving responses. +/// +/// Obtained via [`Stack::req`]. Implements [`MctpReqChannel`]. +pub struct StackReqChannel<'s, C: MctpClient> { + stack: &'s Stack<C>, + handle: Handle, + eid: u8, + /// Tag captured after the first `send`; required before `recv` may be called. + sent_tag: Option<u8>, + timeout: u32, +} + +impl<C: MctpClient> MctpReqChannel for StackReqChannel<'_, C> { + fn send(&mut self, msg_type: u8, buf: &[u8]) -> Result<(), MctpError> { + if self.sent_tag.is_some() { + return Err(MctpError::from_code(ResponseCode::BadArgument)); + } + let tag = self.stack.client.send( + Some(self.handle), + msg_type, + None, + None, + false, + buf, + )?; + self.sent_tag = Some(tag); + Ok(()) + } + + fn recv<'f>( + &mut self, + buf: &'f mut [u8], + ) -> Result<(RecvMetadata, &'f mut [u8]), MctpError> { + if self.sent_tag.is_none() { + return Err(MctpError::from_code(ResponseCode::BadArgument)); + } + let meta = self.stack.client.recv(self.handle, self.timeout, buf)?; + let len = meta.payload_size; + Ok((meta, &mut buf[..len])) + } + + fn remote_eid(&self) -> u8 { + self.eid + } +} + +impl<C: MctpClient> Drop for StackReqChannel<'_, C> { + fn drop(&mut self) { + self.stack.client.drop_handle(self.handle); + } +} + +// ============================================================================ +// Listener +// ============================================================================ + +/// A listener that receives incoming MCTP messages of a specific type. +/// +/// Obtained via [`Stack::listener`]. Implements [`MctpListener`]. +pub struct StackListener<'s, C: MctpClient> { + stack: &'s Stack<C>, + handle: Handle, + timeout: u32, +} + +impl<'s, C: MctpClient> MctpListener for StackListener<'s, C> { + type RespChannel<'a> + = StackRespChannel<'s, C> + where + Self: 'a; + + fn recv<'f>( + &mut self, + buf: &'f mut [u8], + ) -> Result<(RecvMetadata, &'f mut [u8], Self::RespChannel<'_>), MctpError> { + let meta = self.stack.client.recv(self.handle, self.timeout, buf)?; + let len = meta.payload_size; + let resp = StackRespChannel { + stack: self.stack, + eid: meta.remote_eid, + msg_type: meta.msg_type, + tag: meta.msg_tag, + }; + Ok((meta, &mut buf[..len], resp)) + } +} + +impl<C: MctpClient> Drop for StackListener<'_, C> { + fn drop(&mut self) { + self.stack.client.drop_handle(self.handle); + } +} + +// ============================================================================ +// Response channel +// ============================================================================ + +/// A response channel for replying to an incoming MCTP request. +/// +/// Returned by [`StackListener::recv`]. Implements [`MctpRespChannel`]. +pub struct StackRespChannel<'s, C: MctpClient> { + stack: &'s Stack<C>, + eid: u8, + msg_type: u8, + tag: u8, +} + +impl<C: MctpClient> MctpRespChannel for StackRespChannel<'_, C> { + fn send(&mut self, buf: &[u8]) -> Result<(), MctpError> { + // Responses pass handle=None; the server distinguishes requests from + // responses by the presence or absence of a handle. + self.stack + .client + .send(None, self.msg_type, Some(self.eid), Some(self.tag), false, buf) + .map(|_| ()) + } + + fn remote_eid(&self) -> u8 { + self.eid + } +}
diff --git a/services/mctp/server/tests/integration.rs b/services/mctp/server/tests/integration.rs index 3f1b006..7c35e7a 100644 --- a/services/mctp/server/tests/integration.rs +++ b/services/mctp/server/tests/integration.rs
@@ -16,6 +16,7 @@ use std::cell::RefCell; use mctp::Eid; +use openprot_mctp_api::stack::Stack; use openprot_mctp_api::{MctpClient, MctpListener, MctpReqChannel, MctpRespChannel}; use openprot_mctp_server::Server; @@ -321,3 +322,162 @@ assert_eq!(resp.remote_eid, 8); assert_eq!(resp.msg_type, 5); } + +// --------------------------------------------------------------------------- +// Stack facade (openprot-mctp-api::stack) +// --------------------------------------------------------------------------- +// +// These tests exercise `Stack<DirectClient>` — the same code path used by the +// real application (`mctp_echo.rs` with `Stack<IpcMctpClient>`), but running +// entirely on the host with no IPC or embedded target. + +/// Echo via `Stack::listener` → `StackListener::recv` → `StackRespChannel::send`. +/// +/// This is the exact sequence used by `mctp_echo.rs`: +/// ```ignore +/// let mut listener = stack.listener(ECHO_MSG_TYPE, 0).unwrap(); +/// let (meta, msg, mut resp) = listener.recv(&mut buf).unwrap(); +/// resp.send(msg).unwrap(); +/// ``` +#[test] +fn stack_listener_echo() { + let buf_a = RefCell::new(Vec::new()); + let buf_b = RefCell::new(Vec::new()); + + let server_a: RefCell<Server<_, 16>> = + RefCell::new(Server::new(Eid(8), 0, BufferSender { packets: &buf_a })); + let server_b: RefCell<Server<_, 16>> = + RefCell::new(Server::new(Eid(42), 0, BufferSender { packets: &buf_b })); + + // Application A uses Stack — the same API as in production. + let stack_a = Stack::new(DirectClient::new(&server_a)); + // Application B uses a raw client to send the request and check the reply. + let client_b = DirectClient::new(&server_b); + + let mut listener = stack_a.listener(1, 0).expect("listener alloc"); + let req_handle = client_b.req(8).unwrap(); + + // B sends a request + client_b + .send(Some(req_handle), 1, None, None, false, b"hello from B") + .unwrap(); + transfer(&buf_b, &mut server_a.borrow_mut()); + + // A echoes back via Stack facade + let mut recv_buf = [0u8; 255]; + let (meta, payload, mut resp) = listener + .recv(&mut recv_buf) + .expect("stack listener should receive the message"); + + assert_eq!(payload, b"hello from B"); + assert_eq!(meta.remote_eid, 42); + + resp.send(payload).expect("stack resp send"); + + // Deliver A → B and verify + transfer(&buf_a, &mut server_b.borrow_mut()); + + let mut resp_buf = [0u8; 255]; + let resp_meta = client_b + .recv(req_handle, 0, &mut resp_buf) + .expect("B should receive the echo"); + + assert_eq!(&resp_buf[..resp_meta.payload_size], b"hello from B"); + assert_eq!(resp_meta.remote_eid, 8); + assert_eq!(resp_meta.msg_type, 1); +} + +/// Echo via `Stack::req` → `StackReqChannel::send` + `StackReqChannel::recv`. +#[test] +fn stack_req_channel_roundtrip() { + let buf_a = RefCell::new(Vec::new()); + let buf_b = RefCell::new(Vec::new()); + + let server_a: RefCell<Server<_, 16>> = + RefCell::new(Server::new(Eid(8), 0, BufferSender { packets: &buf_a })); + let server_b: RefCell<Server<_, 16>> = + RefCell::new(Server::new(Eid(42), 0, BufferSender { packets: &buf_b })); + + let client_a = DirectClient::new(&server_a); + // B uses Stack for the request side. + let stack_b = Stack::new(DirectClient::new(&server_b)); + + let listener_handle = client_a.listener(1).unwrap(); + + let mut req = stack_b.req(8, 0).expect("req channel alloc"); + req.send(1, b"stack req test").expect("req send"); + assert_eq!(req.remote_eid(), 8); + + transfer(&buf_b, &mut server_a.borrow_mut()); + + // A echoes back manually + let mut echo_buf = [0u8; 255]; + let meta = client_a.recv(listener_handle, 0, &mut echo_buf).unwrap(); + client_a + .send( + None, + meta.msg_type, + Some(meta.remote_eid), + Some(meta.msg_tag), + false, + &echo_buf[..meta.payload_size], + ) + .unwrap(); + + transfer(&buf_a, &mut server_b.borrow_mut()); + + let mut resp_buf = [0u8; 255]; + let (resp_meta, resp_payload) = req.recv(&mut resp_buf).expect("req channel recv"); + + assert_eq!(resp_payload, b"stack req test"); + assert_eq!(resp_meta.remote_eid, 8); + assert_eq!(resp_meta.msg_type, 1); +} + +/// Both sides use `Stack` — listener on A, request channel on B. +#[test] +fn stack_both_sides_echo() { + let buf_a = RefCell::new(Vec::new()); + let buf_b = RefCell::new(Vec::new()); + + let server_a: RefCell<Server<_, 16>> = + RefCell::new(Server::new(Eid(8), 0, BufferSender { packets: &buf_a })); + let server_b: RefCell<Server<_, 16>> = + RefCell::new(Server::new(Eid(42), 0, BufferSender { packets: &buf_b })); + + let stack_a = Stack::new(DirectClient::new(&server_a)); + let stack_b = Stack::new(DirectClient::new(&server_b)); + + let mut listener = stack_a.listener(1, 0).expect("listener alloc"); + let mut req = stack_b.req(8, 0).expect("req alloc"); + + // B sends + req.send(1, b"both sides").expect("req send"); + transfer(&buf_b, &mut server_a.borrow_mut()); + + // A receives and replies via Stack + let mut recv_buf = [0u8; 255]; + let (_, payload, mut resp) = listener.recv(&mut recv_buf).expect("listener recv"); + assert_eq!(payload, b"both sides"); + resp.send(payload).expect("resp send"); + transfer(&buf_a, &mut server_b.borrow_mut()); + + // B receives via Stack req channel + let mut resp_buf = [0u8; 255]; + let (meta, data) = req.recv(&mut resp_buf).expect("req recv"); + assert_eq!(data, b"both sides"); + assert_eq!(meta.remote_eid, 8); +} + +/// `Stack::get_eid` and `Stack::set_eid` delegate correctly. +#[test] +fn stack_eid_accessors() { + let server: RefCell<Server<_, 16>> = + RefCell::new(Server::new(Eid(8), 0, common::DroppingBufferSender)); + let stack = Stack::new(DirectClient::new(&server)); + + assert_eq!(stack.get_eid(), 8); + stack.set_eid(99).expect("set_eid should succeed"); + assert_eq!(stack.get_eid(), 99); +} +
diff --git a/target/ast1060-evb/mctp/mctp_echo.rs b/target/ast1060-evb/mctp/mctp_echo.rs index ff08843..4ca26be 100644 --- a/target/ast1060-evb/mctp/mctp_echo.rs +++ b/target/ast1060-evb/mctp/mctp_echo.rs
@@ -17,7 +17,8 @@ #![no_main] #![no_std] -use openprot_mctp_api::MctpClient; +use openprot_mctp_api::stack::Stack; +use openprot_mctp_api::{MctpListener, MctpRespChannel}; use openprot_mctp_client::IpcMctpClient; use pw_status::Result; @@ -32,19 +33,19 @@ fn mctp_echo_loop() -> Result<()> { pw_log::info!("MCTP echo starting"); - let client = IpcMctpClient::new(handle::MCTP); + let stack = Stack::new(IpcMctpClient::new(handle::MCTP)); - // Register a listener for type-1 messages - let listener = client - .listener(ECHO_MSG_TYPE) + let mut listener = stack + .listener(ECHO_MSG_TYPE, 0) .map_err(|_| pw_status::Error::Internal)?; let mut buf = [0u8; 1024]; loop { - // Block until a message arrives - let meta = client - .recv(listener, 0, &mut buf) + // Block until a message arrives; recv returns the payload slice and + // a response channel already bound to the sender's EID and tag. + let (meta, msg, mut resp) = listener + .recv(&mut buf) .map_err(|_| pw_status::Error::Internal)?; pw_log::info!( @@ -53,18 +54,10 @@ meta.remote_eid as u32, ); - // Echo the payload back - let payload = &buf[..meta.payload_size]; - client - .send( - None, // no request handle (this is a response) - meta.msg_type, // same message type - Some(meta.remote_eid), // back to sender - Some(meta.msg_tag), // same tag - meta.msg_ic, // preserve integrity check - payload, - ) - .map_err(|_| pw_status::Error::Internal)?; + // Echo the payload back through the response channel. + if let Err(_) = resp.send(msg) { + pw_log::error!("Echo: failed to send response to EID {}", meta.remote_eid as u32); + } } }