| // Licensed under the Apache-2.0 license |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| //! MCTP echo integration test. |
| //! |
| //! This test exercises the full MCTP server stack with a mock transport, |
| //! replicating a production echo task behavior: |
| //! |
| //! 1. Server A (echo responder): listens for MCTP type-1 messages, echoes payload back |
| //! 2. Server B (requester): sends a request to A and verifies the echo response |
| //! |
| //! The test uses a **client/server partition**: the echo application logic |
| //! interacts exclusively through the `MctpClient` trait (client side), while |
| //! the `Server` + transport plumbing is the server side. |
| |
| mod common; |
| |
| use std::cell::RefCell; |
| |
| use mctp::Eid; |
| use openprot_mctp_api::{Handle, MctpClient}; |
| use openprot_mctp_server::Server; |
| |
| use common::{transfer, BufferSender, DirectClient}; |
| |
| |
| // --------------------------------------------------------------------------- |
| // Echo application logic (client side) |
| // --------------------------------------------------------------------------- |
| |
| /// Echo one message: receive on the listener, send the payload back. |
| /// |
| /// This is the same shape as production echo logic: |
| /// ```ignore |
| /// let (_, _, msg, mut resp) = listener.recv(&mut recv_buf).unwrap_lite(); |
| /// resp.send(msg).unwrap(); |
| /// ``` |
| /// but expressed through the `MctpClient` trait. |
| fn echo_once(client: &impl MctpClient, listener_handle: Handle) { |
| let mut recv_buf = [0u8; 255]; |
| let meta = client |
| .recv(listener_handle, 0, &mut recv_buf) |
| .expect("echo: should receive a message"); |
| |
| let payload = &recv_buf[..meta.payload_size]; |
| client |
| .send( |
| None, |
| meta.msg_type, |
| Some(meta.remote_eid), |
| Some(meta.msg_tag), |
| false, |
| payload, |
| ) |
| .expect("echo: should send response"); |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Tests |
| // --------------------------------------------------------------------------- |
| |
| /// MCTP echo: send a request, receive it on a listener, echo back, verify. |
| /// |
| /// This replicates a production echo task behavior: |
| /// - EID 8 listens for MsgType(1) and echoes the payload |
| /// - EID 42 sends a request and checks the response matches |
| #[test] |
| fn mctp_echo_roundtrip() { |
| // -- Server side: set up two MCTP server instances with mock transport -- |
| let buf_a = RefCell::new(Vec::new()); |
| let sender_a = BufferSender { packets: &buf_a }; |
| let server_a: RefCell<Server<_, 16>> = |
| RefCell::new(Server::new(Eid(8), 0, sender_a)); |
| |
| let buf_b = RefCell::new(Vec::new()); |
| let sender_b = BufferSender { packets: &buf_b }; |
| let server_b: RefCell<Server<_, 16>> = |
| RefCell::new(Server::new(Eid(42), 0, sender_b)); |
| |
| // -- Client side: wrap servers in DirectClient to use MctpClient trait -- |
| let client_a = DirectClient::new(&server_a); |
| let client_b = DirectClient::new(&server_b); |
| |
| // Client A: register listener for MsgType(1) — same as echo task |
| let listener_handle = client_a.listener(1).unwrap(); |
| |
| // Client B: get a request handle targeting EID 8 |
| let req_handle = client_b.req(8).unwrap(); |
| |
| // Client B: send a request with MsgType(1) |
| let payload = b"Hello MCTP echo!"; |
| let _tag = client_b |
| .send(Some(req_handle), 1, None, None, false, payload) |
| .unwrap(); |
| |
| // Server side: transfer B's outbound packets to A |
| transfer(&buf_b, &mut server_a.borrow_mut()); |
| |
| // Client A: echo the message back (uses MctpClient trait) |
| echo_once(&client_a, listener_handle); |
| |
| // Server side: transfer A's outbound packets to B |
| transfer(&buf_a, &mut server_b.borrow_mut()); |
| |
| // Client B: receive the echo response (uses MctpClient trait) |
| let mut resp_buf = [0u8; 255]; |
| let resp_meta = client_b |
| .recv(req_handle, 0, &mut resp_buf) |
| .expect("Client B should have received the echo response"); |
| |
| let response = &resp_buf[..resp_meta.payload_size]; |
| assert_eq!(response, payload, "Echo response should match original payload"); |
| assert_eq!(resp_meta.msg_type, 1); |
| assert_eq!(resp_meta.remote_eid, 8); |
| |
| // Clean up |
| client_a.drop_handle(listener_handle); |
| client_b.drop_handle(req_handle); |
| } |
| |
| /// Test that multiple messages can be echoed in sequence. |
| #[test] |
| fn mctp_echo_multiple() { |
| let buf_a = RefCell::new(Vec::new()); |
| let sender_a = BufferSender { packets: &buf_a }; |
| let server_a: RefCell<Server<_, 16>> = |
| RefCell::new(Server::new(Eid(8), 0, sender_a)); |
| |
| let buf_b = RefCell::new(Vec::new()); |
| let sender_b = BufferSender { packets: &buf_b }; |
| let server_b: RefCell<Server<_, 16>> = |
| RefCell::new(Server::new(Eid(42), 0, sender_b)); |
| |
| let client_a = DirectClient::new(&server_a); |
| let client_b = DirectClient::new(&server_b); |
| |
| let listener = client_a.listener(1).unwrap(); |
| let req = client_b.req(8).unwrap(); |
| |
| for i in 0..5u8 { |
| let msg = [i; 32]; |
| |
| // Client B: send request |
| client_b |
| .send(Some(req), 1, None, None, false, &msg) |
| .unwrap(); |
| transfer(&buf_b, &mut server_a.borrow_mut()); |
| buf_b.borrow_mut().clear(); |
| |
| // Client A: echo (uses MctpClient trait) |
| echo_once(&client_a, listener); |
| transfer(&buf_a, &mut server_b.borrow_mut()); |
| buf_a.borrow_mut().clear(); |
| |
| // Client B: verify echo response |
| let mut resp_buf = [0u8; 255]; |
| let resp = client_b |
| .recv(req, 0, &mut resp_buf) |
| .expect("echo response should be available"); |
| assert_eq!(&resp_buf[..resp.payload_size], &msg); |
| } |
| |
| client_a.drop_handle(listener); |
| client_b.drop_handle(req); |
| } |