pldm: use run_until to remove timeout-as-success ambiguity in host tests base_host, inventory_host, unexpected_eid_host, and unexpected_eid_fw_host all queue one command, run the FD, and check the response, then relied on run_terminus timing out to signal "done" -- indistinguishable from a real hang. Add a shared run_fd_step helper (common.rs) that uses run_until to bound each of these to exactly one step, so every error they can now hit is real. firmware_update_host's own multi-step flow (an unbounded number of RequestFirmwareData/TransferComplete/VerifyComplete/ApplyComplete round trips per call) doesn't fit a fixed step count and keeps using run_terminus directly; it already gained a direct run_until test in the prior commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
diff --git a/services/pldm/tests/base_host.rs b/services/pldm/tests/base_host.rs index 29e1fc9..fef3347 100644 --- a/services/pldm/tests/base_host.rs +++ b/services/pldm/tests/base_host.rs
@@ -9,8 +9,8 @@ use mctp::Eid; use openprot_mctp_server::Server; -use openprot_pldm_service::firmware_device::{FirmwareDevice, RunTerminusResult}; -use openprot_pldm_service::{MctpPldmTransport, PldmServiceError}; +use openprot_pldm_service::firmware_device::FirmwareDevice; +use openprot_pldm_service::MctpPldmTransport; use pldm_common::codec::PldmCodec; use pldm_common::message::control::{GetPldmVersionRequest, GetTidRequest, SetTidRequest}; use pldm_common::message::firmware_update::apply_complete::ApplyResult; @@ -24,7 +24,7 @@ use pldm_interface::firmware_device::fd_ops::{ComponentOperation, FdOps, FdOpsError}; mod common; -use common::{transfer, BufferSender, DirectClientWithPump, FD_EID, TIMEOUT_MILLIS, UA_EID}; +use common::{run_fd_step, transfer, BufferSender, DirectClientWithPump, FD_EID, UA_EID}; struct MockFdOps { component_accepted: Cell<bool>, @@ -202,16 +202,10 @@ ) .expect("send request_update payload"); - // Runs `FirmwareDevice::run_terminus` until its inbound queue is drained. - // `run_terminus` loops until its responder listener has nothing left, at - // which point it returns Mctp(TimedOut); that terminating timeout means - // "done", not a failure. - let mut run_fd_once = - || match fd.run_terminus(UA_EID, &mut fd_buf, TIMEOUT_MILLIS, TIMEOUT_MILLIS, &mut ()) { - RunTerminusResult::Completed => {} - RunTerminusResult::StoppedByError(PldmServiceError::Mctp(e)) if e.is_timeout() => {} - RunTerminusResult::StoppedByError(e) => panic!("firmware device failed: {e:?}"), - }; + // Each command below is answered within a single step: the responder + // transport's pre-recv pump delivers the queued UA->FD packet before + // `run_fd_step` even attempts to receive it. + let mut run_fd_once = || run_fd_step(&mut fd, UA_EID, &mut fd_buf, &mut ()); // The responder transport's pre-recv pump delivers the queued UA->FD // packets into fd_server *after* its listener is registered. Delivering
diff --git a/services/pldm/tests/common.rs b/services/pldm/tests/common.rs index 3311668..b52e915 100644 --- a/services/pldm/tests/common.rs +++ b/services/pldm/tests/common.rs
@@ -10,6 +10,8 @@ use mctp_lib::Sender; use openprot_mctp_api::{Handle, MctpClient, MctpError, RecvMetadata, ResponseCode}; use openprot_mctp_server::Server; +use openprot_pldm_service::firmware_device::{FdEventSink, FirmwareDevice, RunTerminusResult}; +use pldm_interface::firmware_device::fd_ops::FdOps; pub const FD_EID: u8 = 42; pub const UA_EID: u8 = 8; @@ -57,6 +59,47 @@ } } +/// Drives `fd` for exactly one step and panics on any error. +/// +/// These host tests queue one command, run the FD, and check the response — +/// never more than one step's worth of work, and never while the FD is in +/// initiator mode. `run_terminus` used to be driven until it timed out with +/// nothing left to receive, treating that timeout as "done"; the trouble is +/// a genuine timeout (something actually hanging) looks identical, so a real +/// bug there would have passed silently. Bounding the run to one step with +/// `run_until` removes the ambiguity: every error from here is real. +// `common.rs` is compiled once per test binary (via `mod common;`), and not +// every binary uses every helper here — `firmware_update_host.rs`'s +// multi-step flow needs `run_terminus` directly instead. +#[allow(dead_code)] +pub fn run_fd_step<O, Cr, Cq>( + fd: &mut FirmwareDevice<'_, O, Cr, Cq>, + remote_eid: u8, + buf: &mut [u8], + sink: &mut impl FdEventSink, +) where + O: FdOps, + Cr: MctpClient, + Cq: MctpClient, +{ + let mut done = false; + match fd.run_until( + remote_eid, + buf, + TIMEOUT_MILLIS, + TIMEOUT_MILLIS, + sink, + || { + let go = !done; + done = true; + go + }, + ) { + RunTerminusResult::Completed => {} + RunTerminusResult::StoppedByError(e) => panic!("firmware device failed: {e:?}"), + } +} + pub struct DirectClientWithPump<'a, S: Sender, const N: usize, F: FnMut()> { pub server: &'a RefCell<Server<S, N>>, pub pre_recv_pump: RefCell<F>,
diff --git a/services/pldm/tests/inventory_host.rs b/services/pldm/tests/inventory_host.rs index 0e35333..42ad067 100644 --- a/services/pldm/tests/inventory_host.rs +++ b/services/pldm/tests/inventory_host.rs
@@ -10,8 +10,8 @@ use mctp::Eid; use openprot_mctp_server::Server; -use openprot_pldm_service::firmware_device::{FirmwareDevice, RunTerminusResult}; -use openprot_pldm_service::{MctpPldmTransport, PldmServiceError}; +use openprot_pldm_service::firmware_device::FirmwareDevice; +use openprot_pldm_service::MctpPldmTransport; use pldm_common::codec::PldmCodec; use pldm_common::message::firmware_update::apply_complete::ApplyResult; use pldm_common::message::firmware_update::get_fw_params::{ @@ -33,7 +33,7 @@ use pldm_interface::firmware_device::fd_ops::{ComponentOperation, FdOps, FdOpsError}; mod common; -use common::{transfer, BufferSender, DirectClientWithPump, FD_EID, TIMEOUT_MILLIS, UA_EID}; +use common::{run_fd_step, transfer, BufferSender, DirectClientWithPump, FD_EID, UA_EID}; struct InventoryFdOps { descriptors: [Descriptor; 2], @@ -222,14 +222,7 @@ .send(Some(handle), 0x01, None, None, false, request) .expect("send inventory request"); - match fd.run_terminus(UA_EID, &mut fd_buf, TIMEOUT_MILLIS, TIMEOUT_MILLIS, &mut ()) { - RunTerminusResult::Completed => {} - RunTerminusResult::StoppedByError(PldmServiceError::Mctp(error)) - if error.is_timeout() => {} - RunTerminusResult::StoppedByError(error) => { - panic!("firmware device failed: {error:?}") - } - } + run_fd_step(&mut fd, UA_EID, &mut fd_buf, &mut ()); transfer(&fd_to_ua_packets, &mut ua_server.borrow_mut()); fd_to_ua_packets.borrow_mut().clear();
diff --git a/services/pldm/tests/unexpected_eid_fw_host.rs b/services/pldm/tests/unexpected_eid_fw_host.rs index 57b81c9..0341572 100644 --- a/services/pldm/tests/unexpected_eid_fw_host.rs +++ b/services/pldm/tests/unexpected_eid_fw_host.rs
@@ -9,8 +9,8 @@ use mctp::Eid; use openprot_mctp_server::Server; -use openprot_pldm_service::firmware_device::{FirmwareDevice, RunTerminusResult}; -use openprot_pldm_service::{MctpPldmTransport, PldmServiceError}; +use openprot_pldm_service::firmware_device::FirmwareDevice; +use openprot_pldm_service::MctpPldmTransport; use pldm_common::codec::PldmCodec; use pldm_common::message::firmware_update::apply_complete::ApplyResult; use pldm_common::message::firmware_update::get_fw_params::FirmwareParameters; @@ -31,7 +31,7 @@ use pldm_interface::firmware_device::fd_ops::{ComponentOperation, FdOps, FdOpsError}; mod common; -use common::{transfer, BufferSender, DirectClientWithPump, FD_EID, TIMEOUT_MILLIS, UA_EID}; +use common::{run_fd_step, transfer, BufferSender, DirectClientWithPump, FD_EID, UA_EID}; const PLDM_MSG_TYPE: u8 = 0x01; /// Total firmware image size (bytes) advertised in `UpdateComponent`. @@ -222,17 +222,10 @@ ); let mut fd_buf = [0u8; 1024]; - // Runs `FirmwareDevice::run_terminus` until its inbound queue is drained. - // `run_terminus` loops until its responder listener has nothing left, at - // which point it returns Mctp(TimedOut); that terminating timeout means - // "done", not a failure. `UA_EID` is the only EID `run_terminus` is told - // to serve, so commands from `ATTACKER_EID` must be ignored below. - let mut run_fd_once = - || match fd.run_terminus(UA_EID, &mut fd_buf, TIMEOUT_MILLIS, TIMEOUT_MILLIS, &mut ()) { - RunTerminusResult::Completed => {} - RunTerminusResult::StoppedByError(PldmServiceError::Mctp(e)) if e.is_timeout() => {} - RunTerminusResult::StoppedByError(e) => panic!("firmware device failed: {e:?}"), - }; + // Each command below is answered within a single step. `UA_EID` is the + // only EID `run_fd_step` is told to serve, so commands from + // `ATTACKER_EID` must be ignored below. + let mut run_fd_once = || run_fd_step(&mut fd, UA_EID, &mut fd_buf, &mut ()); let mut buf = [0u8; 1024]; // ---- Attacker (EID 99) sends RequestUpdate; the FD must ignore it and FD states must not move----
diff --git a/services/pldm/tests/unexpected_eid_host.rs b/services/pldm/tests/unexpected_eid_host.rs index c074f25..96179b0 100644 --- a/services/pldm/tests/unexpected_eid_host.rs +++ b/services/pldm/tests/unexpected_eid_host.rs
@@ -9,8 +9,8 @@ use mctp::Eid; use openprot_mctp_server::Server; -use openprot_pldm_service::firmware_device::{FirmwareDevice, RunTerminusResult}; -use openprot_pldm_service::{MctpPldmTransport, PldmServiceError}; +use openprot_pldm_service::firmware_device::FirmwareDevice; +use openprot_pldm_service::MctpPldmTransport; use pldm_common::codec::PldmCodec; use pldm_common::message::control::{GetTidRequest, SetTidRequest}; use pldm_common::message::firmware_update::apply_complete::ApplyResult; @@ -24,7 +24,7 @@ use pldm_interface::firmware_device::fd_ops::{ComponentOperation, FdOps, FdOpsError}; mod common; -use common::{transfer, BufferSender, DirectClientWithPump, FD_EID, TIMEOUT_MILLIS, UA_EID}; +use common::{run_fd_step, transfer, BufferSender, DirectClientWithPump, FD_EID, UA_EID}; struct MockFdOps { component_accepted: Cell<bool>, @@ -196,17 +196,10 @@ ); let mut fd_buf = [0u8; 1024]; - // Runs `FirmwareDevice::run_terminus` until its inbound queue is drained. - // `run_terminus` loops until its responder listener has nothing left, at - // which point it returns Mctp(TimedOut); that terminating timeout means - // "done", not a failure. `UA_EID` is the only EID `run_terminus` is told - // to serve, so commands from `ATTACKER_EID` must be ignored below. - let mut run_fd_once = - || match fd.run_terminus(UA_EID, &mut fd_buf, TIMEOUT_MILLIS, TIMEOUT_MILLIS, &mut ()) { - RunTerminusResult::Completed => {} - RunTerminusResult::StoppedByError(PldmServiceError::Mctp(e)) if e.is_timeout() => {} - RunTerminusResult::StoppedByError(e) => panic!("firmware device failed: {e:?}"), - }; + // Each command below is answered within a single step. `UA_EID` is the + // only EID `run_fd_step` is told to serve, so commands from + // `ATTACKER_EID` must be ignored below. + let mut run_fd_once = || run_fd_step(&mut fd, UA_EID, &mut fd_buf, &mut ()); let mut buf = [0u8; 1024]; // ---- Attacker (EID 99) sends SetTid(0x99); the FD must ignore it ----