| // 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; |
| use openprot_mctp_server::{dispatch::dispatch_mctp_op, Server}; |
| |
| use common::{transfer, BufferSender}; |
| |
| |
| // --------------------------------------------------------------------------- |
| // 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_mctp_op(&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_mctp_op(&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_mctp_op(&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_mctp_op(&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_mctp_op(&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_mctp_op(&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_mctp_op(&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_mctp_op(&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_mctp_op(&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_mctp_op(&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 when no message is ready → TimedOut |
| // --------------------------------------------------------------------------- |
| |
| /// `Recv` dispatched when no message has arrived must return `TimedOut`. |
| #[test] |
| fn dispatch_recv_no_message_returns_timed_out() { |
| 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]; |
| |
| // Register a listener |
| let req_len = wire::encode_listener(&mut req, 1).unwrap(); |
| let resp_len = dispatch_mctp_op(&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; |
| |
| // Attempt Recv immediately — no inbound packet |
| let req_len = wire::encode_recv(&mut req, listener_handle, 0).unwrap(); |
| let resp_len = dispatch_mctp_op(&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.response_code(), ResponseCode::TimedOut); |
| } |
| |
| // --------------------------------------------------------------------------- |
| // 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_mctp_op(&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_mctp_op(&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_mctp_op(&req[..req_len], &mut resp, &mut server, &mut recv_buf); |
| let header = wire::decode_response_header(&resp[..resp_len]).unwrap(); |
| assert!(header.is_success()); |
| } |