blob: 25cd1983c096a04e8da9b0420d0e5c1cbde9edc2 [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! Wire-protocol dispatch integration test.
//!
//! Exercises the full request path: encode request → `dispatch_mctp_op` → decode
//! response. This verifies that the wire protocol + dispatch layer + Server
//! work together correctly for a client/server integration boundary.
mod common;
use std::cell::RefCell;
use mctp::Eid;
use openprot_mctp_api::{wire, Handle};
use openprot_mctp_server::{
dispatch::{dispatch_mctp_op, drive_pending, DispatchOutcome},
Server,
};
use common::{transfer, BufferSender};
// Convenience wrapper: calls dispatch_mctp_op with now_millis=0 and panics on Pending.
fn dispatch_reply<S: openprot_mctp_server::Sender, const N: usize>(
request: &[u8],
response: &mut [u8],
server: &mut Server<S, N>,
recv_buf: &mut [u8],
) -> usize {
match dispatch_mctp_op(request, response, server, recv_buf, 0) {
DispatchOutcome::Reply(n) => n,
DispatchOutcome::Pending { handle } => {
panic!("unexpected Pending for handle {handle:?}")
}
}
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
/// Test SetEid + GetEid via dispatch.
#[test]
fn dispatch_set_get_eid() {
let buf = RefCell::new(Vec::new());
let sender = BufferSender { packets: &buf };
let mut server: Server<_, 16> = Server::new(Eid(0), 0, sender);
let mut req = [0u8; 64];
let mut resp = [0u8; 64];
let mut recv_buf = [0u8; 255];
// SetEid(42)
let req_len = wire::encode_set_eid(&mut req, 42).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server, &mut recv_buf);
let header = wire::decode_response_header(&resp[..resp_len]).unwrap();
assert!(header.is_success());
// GetEid → 42
let req_len = wire::encode_get_eid(&mut req).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server, &mut recv_buf);
let header = wire::decode_response_header(&resp[..resp_len]).unwrap();
assert!(header.is_success());
assert_eq!(header.eid, 42);
}
/// Test Listener + Req + Send + Recv via dispatch (full echo round-trip).
#[test]
fn dispatch_echo_roundtrip() {
// Server A (echo responder, EID 8)
let buf_a = RefCell::new(Vec::new());
let sender_a = BufferSender { packets: &buf_a };
let mut server_a: Server<_, 16> = Server::new(Eid(8), 0, sender_a);
// Server B (requester, EID 42)
let buf_b = RefCell::new(Vec::new());
let sender_b = BufferSender { packets: &buf_b };
let mut server_b: Server<_, 16> = Server::new(Eid(42), 0, sender_b);
let mut req = [0u8; 128];
let mut resp = [0u8; 128];
let mut recv_buf = [0u8; 255];
// Register listener on A for MsgType(1)
let req_len = wire::encode_listener(&mut req, 1).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server_a, &mut recv_buf);
let header = wire::decode_response_header(&resp[..resp_len]).unwrap();
assert!(header.is_success());
let listener_handle = header.handle;
// Register req on B targeting EID 8
let req_len = wire::encode_req(&mut req, 8).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server_b, &mut recv_buf);
let header = wire::decode_response_header(&resp[..resp_len]).unwrap();
assert!(header.is_success());
let req_handle = header.handle;
// B sends a message via dispatch
let payload = b"dispatch echo!";
let req_len =
wire::encode_send(&mut req, Some(req_handle), 1, None, None, false, payload).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server_b, &mut recv_buf);
let header = wire::decode_response_header(&resp[..resp_len]).unwrap();
assert!(header.is_success());
// Transfer B → A
transfer(&buf_b, &mut server_a);
// A receives via dispatch
let req_len = wire::encode_recv(&mut req, listener_handle, 0).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server_a, &mut recv_buf);
let header = wire::decode_response_header(&resp[..resp_len]).unwrap();
assert!(header.is_success());
assert_eq!(header.msg_type, 1);
assert_eq!(header.eid, 42); // remote EID
let recv_payload = wire::get_response_payload(&resp[..resp_len], &header).unwrap();
assert_eq!(recv_payload, payload);
// A echoes back via dispatch (response: no handle, set eid + tag)
let req_len = wire::encode_send(
&mut req,
None,
header.msg_type,
Some(header.eid),
Some(header.tag),
false,
recv_payload,
)
.unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server_a, &mut recv_buf);
let send_header = wire::decode_response_header(&resp[..resp_len]).unwrap();
assert!(send_header.is_success());
// Transfer A → B
transfer(&buf_a, &mut server_b);
// B receives the echo via dispatch
let req_len = wire::encode_recv(&mut req, req_handle, 0).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server_b, &mut recv_buf);
let header = wire::decode_response_header(&resp[..resp_len]).unwrap();
assert!(header.is_success());
assert_eq!(header.msg_type, 1);
assert_eq!(header.eid, 8); // from server A
let echo_payload = wire::get_response_payload(&resp[..resp_len], &header).unwrap();
assert_eq!(echo_payload, payload);
}
// ---------------------------------------------------------------------------
// Malformed request → BadArgument
// ---------------------------------------------------------------------------
/// A request buffer shorter than the header size must return `BadArgument`.
#[test]
fn dispatch_malformed_request_returns_bad_argument() {
use openprot_mctp_api::ResponseCode;
let buf = RefCell::new(Vec::new());
let sender = BufferSender { packets: &buf };
let mut server: Server<_, 16> = Server::new(Eid(8), 0, sender);
let mut resp = [0u8; 64];
let mut recv_buf = [0u8; 255];
// Two bytes — shorter than MctpRequestHeader::SIZE (12)
let bad_request = [0u8; 2];
let resp_len = dispatch_reply(&bad_request, &mut resp, &mut server, &mut recv_buf);
let header = wire::decode_response_header(&resp[..resp_len]).unwrap();
assert!(!header.is_success());
assert_eq!(header.response_code(), ResponseCode::BadArgument);
}
/// An opcode byte that is not a known `MctpOp` must return `BadArgument`.
#[test]
fn dispatch_unknown_opcode_returns_bad_argument() {
use openprot_mctp_api::ResponseCode;
let buf = RefCell::new(Vec::new());
let sender = BufferSender { packets: &buf };
let mut server: Server<_, 16> = Server::new(Eid(8), 0, sender);
let mut resp = [0u8; 64];
let mut recv_buf = [0u8; 255];
// 12-byte header with opcode 0xFF (unrecognised)
let mut bad_request = [0u8; 12];
bad_request[0] = 0xFF;
let resp_len = dispatch_reply(&bad_request, &mut resp, &mut server, &mut recv_buf);
let header = wire::decode_response_header(&resp[..resp_len]).unwrap();
assert!(!header.is_success());
assert_eq!(header.response_code(), ResponseCode::BadArgument);
}
// ---------------------------------------------------------------------------
// MctpOp::Recv — deferred path
// ---------------------------------------------------------------------------
/// `Recv` when no message is ready returns `Pending`, not an immediate error.
#[test]
fn dispatch_recv_returns_pending_when_no_message() {
let buf = RefCell::new(Vec::new());
let sender = BufferSender { packets: &buf };
let mut server: Server<_, 16> = Server::new(Eid(8), 0, sender);
let mut req = [0u8; 64];
let mut resp = [0u8; 64];
let mut recv_buf = [0u8; 255];
// Register a listener
let req_len = wire::encode_listener(&mut req, 1).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server, &mut recv_buf);
let h = wire::decode_response_header(&resp[..resp_len]).unwrap();
let listener_handle = Handle(h.handle);
// Attempt Recv immediately — no inbound packet → Pending
let req_len = wire::encode_recv(&mut req, listener_handle.0, 500).unwrap();
let outcome = dispatch_mctp_op(&req[..req_len], &mut resp, &mut server, &mut recv_buf, 0);
assert!(
matches!(outcome, DispatchOutcome::Pending { handle } if handle == listener_handle),
"expected Pending"
);
}
// ---------------------------------------------------------------------------
// MctpOp::Unbind
// ---------------------------------------------------------------------------
/// `Unbind` on a valid handle returns success.
#[test]
fn dispatch_unbind_valid_handle() {
let buf = RefCell::new(Vec::new());
let sender = BufferSender { packets: &buf };
let mut server: Server<_, 16> = Server::new(Eid(8), 0, sender);
let mut req = [0u8; 64];
let mut resp = [0u8; 64];
let mut recv_buf = [0u8; 255];
// Allocate a listener handle
let req_len = wire::encode_listener(&mut req, 1).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server, &mut recv_buf);
let h = wire::decode_response_header(&resp[..resp_len]).unwrap();
assert!(h.is_success());
let listener_handle = h.handle;
// Unbind it
let req_len = wire::encode_unbind(&mut req, listener_handle).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server, &mut recv_buf);
let header = wire::decode_response_header(&resp[..resp_len]).unwrap();
assert!(header.is_success());
}
/// `Unbind` on a handle that was never allocated still returns success.
/// (The server's `unbind` is idempotent — it ignores unknown handles.)
#[test]
fn dispatch_unbind_unknown_handle_is_idempotent() {
let buf = RefCell::new(Vec::new());
let sender = BufferSender { packets: &buf };
let mut server: Server<_, 16> = Server::new(Eid(8), 0, sender);
let mut req = [0u8; 64];
let mut resp = [0u8; 64];
let mut recv_buf = [0u8; 255];
let req_len = wire::encode_unbind(&mut req, 0xDEAD_BEEF).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server, &mut recv_buf);
let header = wire::decode_response_header(&resp[..resp_len]).unwrap();
assert!(header.is_success());
}
// ---------------------------------------------------------------------------
// drive_pending — deferred recv delivery
// ---------------------------------------------------------------------------
/// After a `Pending` recv, `drive_pending` fires `on_ready` once a packet arrives.
#[test]
fn dispatch_recv_resolved_by_drive_pending() {
let buf_a = RefCell::new(Vec::new());
let sender_a = BufferSender { packets: &buf_a };
let mut server_a: Server<_, 16> = Server::new(Eid(8), 0, sender_a);
let buf_b = RefCell::new(Vec::new());
let sender_b = BufferSender { packets: &buf_b };
let mut server_b: Server<_, 16> = Server::new(Eid(42), 0, sender_b);
let mut req = [0u8; 128];
let mut resp = [0u8; 128];
let mut recv_buf = [0u8; 255];
// Register listener on A
let req_len = wire::encode_listener(&mut req, 7).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server_a, &mut recv_buf);
let h = wire::decode_response_header(&resp[..resp_len]).unwrap();
let listener_handle = Handle(h.handle);
// A tries recv before any message arrives → Pending
let req_len = wire::encode_recv(&mut req, listener_handle.0, 1000).unwrap();
let outcome = dispatch_mctp_op(&req[..req_len], &mut resp, &mut server_a, &mut recv_buf, 0);
assert!(matches!(outcome, DispatchOutcome::Pending { .. }));
// B allocates req handle and sends a message to A
let req_len = wire::encode_req(&mut req, 8).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server_b, &mut recv_buf);
let h = wire::decode_response_header(&resp[..resp_len]).unwrap();
let req_handle = h.handle;
let payload = b"hello pending";
let req_len =
wire::encode_send(&mut req, Some(req_handle), 7, None, None, false, payload).unwrap();
dispatch_reply(&req[..req_len], &mut resp, &mut server_b, &mut recv_buf);
transfer(&buf_b, &mut server_a);
// drive_pending should deliver the message
let mut fired_handle: Option<Handle> = None;
let mut fired_len = 0usize;
drive_pending(&mut server_a, 0, &mut recv_buf, &mut resp, |h, n| {
fired_handle = Some(h);
fired_len = n;
});
assert_eq!(fired_handle, Some(listener_handle));
let header = wire::decode_response_header(&resp[..fired_len]).unwrap();
assert!(header.is_success());
assert_eq!(header.msg_type, 7);
assert_eq!(header.eid, 42);
let recv_payload = wire::get_response_payload(&resp[..fired_len], &header).unwrap();
assert_eq!(recv_payload, payload);
}
/// After a `Pending` recv with a timeout, `drive_pending` at `now > deadline`
/// fires `on_ready` with a `TimedOut` response.
#[test]
fn dispatch_recv_timeout() {
use openprot_mctp_api::ResponseCode;
let buf = RefCell::new(Vec::new());
let sender = BufferSender { packets: &buf };
let mut server: Server<_, 16> = Server::new(Eid(8), 0, sender);
let mut req = [0u8; 64];
let mut resp = [0u8; 64];
let mut recv_buf = [0u8; 255];
let req_len = wire::encode_listener(&mut req, 1).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server, &mut recv_buf);
let h = wire::decode_response_header(&resp[..resp_len]).unwrap();
let listener_handle = Handle(h.handle);
// Register recv with 100ms timeout at now=0
let req_len = wire::encode_recv(&mut req, listener_handle.0, 100).unwrap();
let outcome = dispatch_mctp_op(&req[..req_len], &mut resp, &mut server, &mut recv_buf, 0);
assert!(matches!(outcome, DispatchOutcome::Pending { .. }));
// Advance time past deadline — no message ever arrives
let mut fired_handle: Option<Handle> = None;
let mut fired_len = 0usize;
drive_pending(&mut server, 200, &mut recv_buf, &mut resp, |h, n| {
fired_handle = Some(h);
fired_len = n;
});
assert_eq!(fired_handle, Some(listener_handle));
let header = wire::decode_response_header(&resp[..fired_len]).unwrap();
assert!(!header.is_success());
assert_eq!(header.response_code(), ResponseCode::TimedOut);
}
/// If a message arrives *before* `Recv` is dispatched, `dispatch_mctp_op`
/// returns `Reply` immediately without registering a pending entry.
#[test]
fn dispatch_recv_immediate_if_message_waiting() {
let buf_a = RefCell::new(Vec::new());
let sender_a = BufferSender { packets: &buf_a };
let mut server_a: Server<_, 16> = Server::new(Eid(8), 0, sender_a);
let buf_b = RefCell::new(Vec::new());
let sender_b = BufferSender { packets: &buf_b };
let mut server_b: Server<_, 16> = Server::new(Eid(42), 0, sender_b);
let mut req = [0u8; 128];
let mut resp = [0u8; 128];
let mut recv_buf = [0u8; 255];
// Register listener on A
let req_len = wire::encode_listener(&mut req, 3).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server_a, &mut recv_buf);
let h = wire::decode_response_header(&resp[..resp_len]).unwrap();
let listener_handle = h.handle;
// B sends a message to A *before* Recv is dispatched
let req_len = wire::encode_req(&mut req, 8).unwrap();
let resp_len = dispatch_reply(&req[..req_len], &mut resp, &mut server_b, &mut recv_buf);
let h = wire::decode_response_header(&resp[..resp_len]).unwrap();
let req_handle = h.handle;
let payload = b"early";
let req_len =
wire::encode_send(&mut req, Some(req_handle), 3, None, None, false, payload).unwrap();
dispatch_reply(&req[..req_len], &mut resp, &mut server_b, &mut recv_buf);
transfer(&buf_b, &mut server_a);
// Recv dispatched after message is already in the router → Reply, not Pending
let req_len = wire::encode_recv(&mut req, listener_handle, 1000).unwrap();
let outcome = dispatch_mctp_op(&req[..req_len], &mut resp, &mut server_a, &mut recv_buf, 0);
let n = match outcome {
DispatchOutcome::Reply(n) => n,
DispatchOutcome::Pending { .. } => panic!("expected Reply, got Pending"),
};
let header = wire::decode_response_header(&resp[..n]).unwrap();
assert!(header.is_success());
assert_eq!(header.msg_type, 3);
assert_eq!(header.eid, 42);
let recv_payload = wire::get_response_payload(&resp[..n], &header).unwrap();
assert_eq!(recv_payload, payload);
}