blob: e71c8b2b7d081fbf249ae66603d8bccc3b6ff666 [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! End-to-end host test wiring:
//! UA command -> FirmwareDevice (direct MCTP transports) -> UA
//! all via in-memory channels/transports.
use core::cell::{Cell, RefCell};
use mctp::Eid;
use openprot_mctp_server::Server;
use openprot_pldm_service::firmware_device::{FirmwareDevice, RunTerminusResult};
use openprot_pldm_service::{MctpPldmTransport, PldmServiceError};
use pldm_common::codec::PldmCodec;
use pldm_common::message::control::{GetPldmVersionRequest, GetTidRequest, SetTidRequest};
use pldm_common::message::firmware_update::apply_complete::ApplyResult;
use pldm_common::message::firmware_update::get_fw_params::FirmwareParameters;
use pldm_common::message::firmware_update::get_status::ProgressPercent;
use pldm_common::message::firmware_update::transfer_complete::TransferResult;
use pldm_common::message::firmware_update::verify_complete::VerifyResult;
use pldm_common::protocol::base::{PldmMsgType, PldmSupportedType, TransferOperationFlag};
use pldm_common::protocol::firmware_update::{ComponentResponseCode, Descriptor};
use pldm_common::util::fw_component::FirmwareComponent;
use pldm_interface::firmware_device::fd_ops::{ComponentOperation, FdOps, FdOpsError};
mod common;
use common::{transfer, BufferSender, DirectClientWithPump, FD_EID, TIMEOUT_MILLIS, UA_EID};
struct MockFdOps {
component_accepted: Cell<bool>,
download_bytes_received: Cell<usize>,
verified: Cell<bool>,
applied: Cell<bool>,
activated: Cell<bool>,
}
impl FdOps for MockFdOps {
fn get_device_identifiers(
&self,
_device_identifiers: &mut [Descriptor],
) -> Result<usize, FdOpsError> {
Ok(0)
}
fn get_firmware_parms(
&self,
firmware_params: &mut FirmwareParameters,
) -> Result<(), FdOpsError> {
*firmware_params = FirmwareParameters::default();
Ok(())
}
fn get_xfer_size(&self, ua_transfer_size: usize) -> Result<usize, FdOpsError> {
Ok(ua_transfer_size.min(512))
}
fn handle_component(
&self,
_component: &FirmwareComponent,
_fw_params: &FirmwareParameters,
_op: ComponentOperation,
) -> Result<ComponentResponseCode, FdOpsError> {
self.component_accepted.set(true);
Ok(ComponentResponseCode::CompCanBeUpdated)
}
fn query_download_offset_and_length(
&self,
_component: &FirmwareComponent,
) -> Result<(usize, usize), FdOpsError> {
Ok((0, 1024))
}
fn download_fw_data(
&self,
_offset: usize,
data: &[u8],
_component: &FirmwareComponent,
) -> Result<TransferResult, FdOpsError> {
self.download_bytes_received
.set(self.download_bytes_received.get() + data.len());
Ok(TransferResult::TransferSuccess)
}
fn is_download_complete(&self, _component: &FirmwareComponent) -> bool {
self.download_bytes_received.get() >= 1024
}
fn query_download_progress(
&self,
_component: &FirmwareComponent,
progress_percent: &mut ProgressPercent,
) -> Result<(), FdOpsError> {
let pct = (self.download_bytes_received.get() * 100 / 1024) as u8;
progress_percent
.set_value(pct)
.map_err(|_| FdOpsError::FwDownloadError)?;
Ok(())
}
fn verify(
&self,
_component: &FirmwareComponent,
_progress_percent: &mut ProgressPercent,
) -> Result<VerifyResult, FdOpsError> {
self.verified.set(true);
Ok(VerifyResult::VerifySuccess)
}
fn apply(
&self,
_component: &FirmwareComponent,
_progress_percent: &mut ProgressPercent,
) -> Result<ApplyResult, FdOpsError> {
self.applied.set(true);
Ok(ApplyResult::ApplySuccess)
}
fn activate(
&self,
_self_contained_activation: u8,
_estimated_time: &mut u16,
) -> Result<u8, FdOpsError> {
self.activated.set(true);
Ok(0)
}
fn cancel_update_component(&self, _component: &FirmwareComponent) -> Result<(), FdOpsError> {
Ok(())
}
}
#[test]
fn base_full_chain_via_firmware_device() {
let fd_ops = MockFdOps {
component_accepted: Cell::new(false),
download_bytes_received: Cell::new(0),
verified: Cell::new(false),
applied: Cell::new(false),
activated: Cell::new(false),
};
// In-memory MCTP endpoints: UA client side and FD responder side.
let ua_to_fd_packets = RefCell::new(Vec::new());
let ua_sender = BufferSender {
packets: &ua_to_fd_packets,
};
let ua_server: RefCell<Server<_, 16>> = RefCell::new(Server::new(Eid(UA_EID), 0, ua_sender));
let fd_to_ua_packets = RefCell::new(Vec::new());
let fd_sender = BufferSender {
packets: &fd_to_ua_packets,
};
let fd_server: RefCell<Server<_, 16>> = RefCell::new(Server::new(Eid(FD_EID), 0, fd_sender));
// Responder transport: receives UA->FD commands directly over MCTP. Its
// pre-recv pump delivers queued UA->FD packets into `fd_server` before
// each receive attempt.
let responder_client = DirectClientWithPump::new(&fd_server, || {
transfer(&ua_to_fd_packets, &mut fd_server.borrow_mut());
ua_to_fd_packets.borrow_mut().clear();
});
let responder_transport = MctpPldmTransport::new(responder_client);
// This base test only exercises control commands (SetTid/GetTid/
// GetPldmVersion), which never put the FD into update mode, so the
// requester transport is never actually exercised; a client with a
// no-op pump suffices.
let requester_client = DirectClientWithPump::new(&fd_server, || {});
let requester_transport = MctpPldmTransport::new(requester_client);
let mut fd = FirmwareDevice::init(
&fd_ops,
&pldm_interface::config::PLDM_PROTOCOL_CAPABILITIES,
responder_transport,
requester_transport,
);
let mut fd_buf = [0u8; 1024];
let mut ua_req_buf = [0u8; 1024];
// ---- SetTid: verify the FD reports the TID we just set (0x42) ----
let set_tid = SetTidRequest::new(0, PldmMsgType::Request, 0x42);
ua_req_buf[0] = 0x01;
let req_len = 1 + set_tid
.encode(&mut ua_req_buf[1..])
.expect("encode request_update");
// Run one full UA->FD->UA request/response roundtrip.
let req_handle = ua_server
.borrow_mut()
.req(FD_EID)
.expect("allocate request handle to FD");
ua_server
.borrow_mut()
.send(
Some(req_handle),
0x01,
None,
None,
false,
&ua_req_buf[1..req_len],
)
.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) {
RunTerminusResult::Completed => {}
RunTerminusResult::StoppedByError(PldmServiceError::Mctp(e)) if e.is_timeout() => {}
RunTerminusResult::StoppedByError(e) => panic!("firmware device failed: {e:?}"),
};
// The responder transport's pre-recv pump delivers the queued UA->FD
// packets into fd_server *after* its listener is registered. Delivering
// them here would route the request before any listener exists, causing
// it to be discarded.
run_fd_once();
transfer(&fd_to_ua_packets, &mut ua_server.borrow_mut());
fd_to_ua_packets.borrow_mut().clear();
let mut ua_resp_payload = [0u8; 1024];
let resp_meta = ua_server
.borrow_mut()
.try_recv(req_handle, &mut ua_resp_payload)
.expect("request_update response should be available");
assert!(
resp_meta.payload_size >= 4,
"response should include PLDM header and completion code"
);
assert_eq!(
ua_resp_payload[3], 0,
"request_update completion code should be success"
);
// ---- GetTid: verify the FD reports the TID we just set (0x42) ----
let get_tid = GetTidRequest::new(1, PldmMsgType::Request);
ua_req_buf[0] = 0x01;
let req_len = 1 + get_tid
.encode(&mut ua_req_buf[1..])
.expect("encode get_tid");
let req_handle = ua_server
.borrow_mut()
.req(FD_EID)
.expect("allocate get_tid request handle to FD");
ua_server
.borrow_mut()
.send(
Some(req_handle),
0x01,
None,
None,
false,
&ua_req_buf[1..req_len],
)
.expect("send get_tid payload");
run_fd_once();
transfer(&fd_to_ua_packets, &mut ua_server.borrow_mut());
fd_to_ua_packets.borrow_mut().clear();
let mut ua_resp_payload = [0u8; 1024];
let resp_meta = ua_server
.borrow_mut()
.try_recv(req_handle, &mut ua_resp_payload)
.expect("get_tid response should be available");
assert!(
resp_meta.payload_size >= 5,
"GetTid response should include header, completion code, and TID"
);
assert_eq!(
ua_resp_payload[3], 0,
"get_tid completion code should be success"
);
assert_eq!(
ua_resp_payload[4], 0x42,
"GetTid should return the TID set by SetTid"
);
// ---- GetPldmVersion: query the Base protocol version supported by the FD ----
let get_version = GetPldmVersionRequest::new(
2,
PldmMsgType::Request,
0,
TransferOperationFlag::GetFirstPart,
PldmSupportedType::Base,
);
ua_req_buf[0] = 0x01;
let req_len = 1 + get_version
.encode(&mut ua_req_buf[1..])
.expect("encode get_pldm_version");
let req_handle = ua_server
.borrow_mut()
.req(FD_EID)
.expect("allocate get_pldm_version request handle to FD");
ua_server
.borrow_mut()
.send(
Some(req_handle),
0x01,
None,
None,
false,
&ua_req_buf[1..req_len],
)
.expect("send get_pldm_version payload");
run_fd_once();
transfer(&fd_to_ua_packets, &mut ua_server.borrow_mut());
fd_to_ua_packets.borrow_mut().clear();
let mut ua_resp_payload = [0u8; 1024];
let resp_meta = ua_server
.borrow_mut()
.try_recv(req_handle, &mut ua_resp_payload)
.expect("get_pldm_version response should be available");
// hdr(3) + completion(1) + next_transfer_handle(4) + transfer_rsp_flag(1) + version(4) = 13
assert!(
resp_meta.payload_size >= 13,
"GetPldmVersion response should include header, completion code, and version data"
);
let resp_version: u32 = u32::from_le_bytes(
ua_resp_payload[9..13]
.try_into()
.expect("version data should be 4 bytes"),
);
assert!(
pldm_interface::config::PLDM_PROTOCOL_CAPABILITIES[0].protocol_version == resp_version,
"Returned Version is incorrect"
);
assert_eq!(
ua_resp_payload[3], 0,
"get_pldm_version completion code should be success"
);
}