Skip to content

Commit 73acff2

Browse files
committed
pldm-fw: ua: send actual component count in RequestUpdate
request_update() hardcoded the RequestUpdate NumberOfComponents field to 1. DSP0267 Table 27 defines this field as "the number of components that will be passed to the FD during the update", and the FD may use it to compare against the number of PassComponentTable/UpdateComponent commands it receives. When a package applies more than one component, the UA passes all of them (pass_component_table and update_components_progress iterate over update.components) while still announcing only 1, which a conformant FD can reject as a mismatch. Derive the count from update.components, the same list driven through the rest of the update flow, so the announced value matches what is actually sent. The value is fallibly converted to u16 to guard the (practically impossible) >65535 component case rather than truncating. Add a unit test (request_update_reports_actual_component_count) that drives request_update over a mock MCTP ReqChannel and asserts the RequestUpdate NumberOfComponents field reflects the actual component count, along with the cfg(test) package-builder and temp-file helpers it depends on. Signed-off-by: Brian Carr <brcarr@nvidia.com>
1 parent 574e3a9 commit 73acff2

4 files changed

Lines changed: 272 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pldm-fw/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ uuid = { workspace = true, features = ["v1"] }
2525
default = ["std"]
2626
alloc = ["pldm/alloc", "nom/alloc"]
2727
std = ["alloc", "pldm/std", "mctp/std", "nom/std", "chrono/clock", "uuid/std", "dep:thiserror"]
28+
29+
[dev-dependencies]
30+
tempfile = "3.27.0"

pldm-fw/src/pkg.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,132 @@ impl Package {
313313
Ok(self.file.read_at(buf, file_offset)?)
314314
}
315315
}
316+
317+
/// Write `bytes` to a fresh anonymous temporary file and return a handle to
318+
/// it, seeked back to the start ready for reading.
319+
#[cfg(test)]
320+
pub(crate) fn temp_file_with(bytes: &[u8]) -> std::fs::File {
321+
use std::io::{Seek, SeekFrom, Write};
322+
323+
let mut f = tempfile::tempfile().unwrap();
324+
f.write_all(bytes).unwrap();
325+
f.seek(SeekFrom::Start(0)).unwrap();
326+
f
327+
}
328+
329+
#[cfg(test)]
330+
pub(crate) mod tests {
331+
use super::*;
332+
333+
/// Build the bytes of a minimal, well-formed v1.1.x firmware update
334+
/// package.
335+
///
336+
/// The package describes a single device identified by one PCI Vendor
337+
/// ID descriptor (`vid`), whose component bitmap selects every supplied
338+
/// component. Each entry of `components` becomes a component image
339+
/// appended after the (CRC-protected) package header, with matching
340+
/// file offset/size fields.
341+
pub(crate) fn build_v11_package(vid: u16, components: &[&[u8]]) -> Vec<u8> {
342+
let ncomp = components.len();
343+
assert!(ncomp >= 1);
344+
345+
// --- single device record ---
346+
let bitmap_bytes = ncomp.div_ceil(8);
347+
let mut bitmap = vec![0u8; bitmap_bytes];
348+
for i in 0..ncomp {
349+
bitmap[i / 8] |= 1u8 << (i % 8);
350+
}
351+
let set_ver = b"0000";
352+
353+
let mut descs = Vec::new();
354+
descs.extend_from_slice(&0x0000u16.to_le_bytes()); // type: PCI Vendor ID
355+
descs.extend_from_slice(&2u16.to_le_bytes()); // length
356+
descs.extend_from_slice(&vid.to_le_bytes()); // data
357+
let desc_count = 1u8;
358+
let pkg_data_len = 0u16;
359+
360+
let mut rec_body = Vec::new();
361+
rec_body.extend_from_slice(&bitmap);
362+
rec_body.extend_from_slice(set_ver);
363+
rec_body.extend_from_slice(&descs);
364+
let rec_len = (11 + rec_body.len()) as u16;
365+
366+
let mut device = Vec::new();
367+
device.extend_from_slice(&rec_len.to_le_bytes());
368+
device.push(desc_count);
369+
device.extend_from_slice(&0u32.to_le_bytes()); // option flags
370+
device.push(1u8); // set version string type (utf-8)
371+
device.push(set_ver.len() as u8);
372+
device.extend_from_slice(&pkg_data_len.to_le_bytes());
373+
device.extend_from_slice(&rec_body);
374+
375+
// Header bytes following the 19-byte init region, excluding the
376+
// trailing 4-byte checksum. `offsets` carries each component's
377+
// absolute file offset.
378+
let build_pre = |offsets: &[usize]| -> Vec<u8> {
379+
let mut pre = Vec::new();
380+
pre.extend_from_slice(&[0u8; 13]); // release date/time
381+
pre.extend_from_slice(&(ncomp as u16).to_le_bytes()); // bitmap length (bits)
382+
383+
// package version string (type, length, data)
384+
pre.push(1u8);
385+
pre.push(4u8);
386+
pre.extend_from_slice(b"0000");
387+
// device id record area
388+
pre.push(1u8); // device count
389+
pre.extend_from_slice(&device);
390+
// downstream device id record area (1.1.x): none
391+
pre.push(0u8);
392+
// component image information area
393+
pre.extend_from_slice(&(ncomp as u16).to_le_bytes());
394+
for (i, c) in components.iter().enumerate() {
395+
pre.extend_from_slice(&0x000au16.to_le_bytes()); // classification: firmware
396+
pre.extend_from_slice(&(i as u16).to_le_bytes()); // identifier
397+
pre.extend_from_slice(&0u32.to_le_bytes()); // comparison stamp
398+
pre.extend_from_slice(&0u16.to_le_bytes()); // options
399+
pre.extend_from_slice(&0u16.to_le_bytes()); // activation method
400+
pre.extend_from_slice(&(offsets[i] as u32).to_le_bytes());
401+
pre.extend_from_slice(&(c.len() as u32).to_le_bytes());
402+
// component version string (type, length, data)
403+
pre.push(1u8);
404+
pre.push(4u8);
405+
pre.extend_from_slice(b"0000");
406+
}
407+
pre
408+
};
409+
410+
const HDR_INIT_SIZE: usize = 16 + 1 + 2;
411+
412+
// First pass with placeholder offsets to learn the header size,
413+
// which is independent of the (fixed-size) offset field values.
414+
let pre_len = build_pre(&vec![0usize; ncomp]).len();
415+
let hdr_size = HDR_INIT_SIZE + pre_len + 4;
416+
417+
// Second pass: real offsets point past the header into the payload
418+
// area.
419+
let mut offsets = vec![0usize; ncomp];
420+
let mut cum = hdr_size;
421+
for (i, c) in components.iter().enumerate() {
422+
offsets[i] = cum;
423+
cum += c.len();
424+
}
425+
let pre = build_pre(&offsets);
426+
427+
let mut header = Vec::new();
428+
header.extend_from_slice(PKG_UUID_1_1_X.as_bytes());
429+
header.push(1u8); // header format revision
430+
header.extend_from_slice(&(hdr_size as u16).to_le_bytes());
431+
header.extend_from_slice(&pre);
432+
433+
let crc32 = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC);
434+
let checksum = crc32.checksum(&header);
435+
header.extend_from_slice(&checksum.to_le_bytes());
436+
assert_eq!(header.len(), hdr_size);
437+
438+
let mut file = header;
439+
for c in components {
440+
file.extend_from_slice(c);
441+
}
442+
file
443+
}
444+
}

pldm-fw/src/ua.rs

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,13 @@ pub fn request_update(
180180
check_fd_state(comm, PldmFDState::Idle)?;
181181

182182
let sz = XFER_SIZE as u32;
183+
let num_components: u16 =
184+
update.components.len().try_into().map_err(|_| {
185+
PldmUpdateError::new_update("too many components".into())
186+
})?;
183187
let mut data = vec![];
184188
data.extend_from_slice(&sz.to_le_bytes());
185-
data.extend_from_slice(&1u16.to_le_bytes()); // NumberOfComponents
189+
data.extend_from_slice(&num_components.to_le_bytes()); // NumberOfComponents
186190
data.extend_from_slice(&1u8.to_le_bytes()); // MaximumOutstandingTransferRequests
187191
data.extend_from_slice(&0u16.to_le_bytes()); // PackageDataLength
188192
update.package.version.write_utf8_bytes(&mut data);
@@ -582,3 +586,137 @@ fn check_fd_state(
582586

583587
Ok(())
584588
}
589+
590+
#[cfg(test)]
591+
mod tests {
592+
use super::{request_update, Update};
593+
use crate::{
594+
Descriptor, DescriptorString, DeviceCapabilities, DeviceIdentifiers,
595+
FirmwareParameters, GetStatusResponse, PldmFDState, PLDM_TYPE_FW,
596+
};
597+
use mctp::{Eid, MsgIC, MsgType, ReqChannel, MCTP_TYPE_PLDM};
598+
use std::collections::VecDeque;
599+
600+
// --- Mock MCTP transport ---------------------------------------------
601+
//
602+
// request_update only drives the UA-initiated `ReqChannel` (`comm`):
603+
// a GetStatus precondition followed by RequestUpdate. The mock replays
604+
// scripted FD responses and records everything the UA sends so the test
605+
// can assert on the wire format.
606+
607+
#[derive(Default)]
608+
struct MockComm {
609+
responses: VecDeque<Vec<u8>>,
610+
sent: Vec<Vec<u8>>,
611+
}
612+
613+
impl MockComm {
614+
fn new(responses: Vec<Vec<u8>>) -> Self {
615+
Self {
616+
responses: responses.into(),
617+
sent: Vec::new(),
618+
}
619+
}
620+
621+
fn sent(&self) -> Vec<Vec<u8>> {
622+
self.sent.clone()
623+
}
624+
}
625+
626+
impl ReqChannel for MockComm {
627+
fn send_vectored(
628+
&mut self,
629+
_typ: MsgType,
630+
_ic: MsgIC,
631+
bufs: &[&[u8]],
632+
) -> mctp::Result<()> {
633+
let mut msg = Vec::new();
634+
for b in bufs {
635+
msg.extend_from_slice(b);
636+
}
637+
self.sent.push(msg);
638+
Ok(())
639+
}
640+
641+
fn recv<'f>(
642+
&mut self,
643+
buf: &'f mut [u8],
644+
) -> mctp::Result<(MsgType, MsgIC, &'f mut [u8])> {
645+
let resp =
646+
self.responses.pop_front().ok_or(mctp::Error::RxFailure)?;
647+
buf[..resp.len()].copy_from_slice(&resp);
648+
Ok((MCTP_TYPE_PLDM, MsgIC(false), &mut buf[..resp.len()]))
649+
}
650+
651+
fn remote_eid(&self) -> Eid {
652+
Eid(8)
653+
}
654+
}
655+
656+
// --- frame helpers ---------------------------------------------------
657+
658+
/// A PLDM response frame as returned by the FD over `comm`.
659+
fn resp_frame(cmd: u8, cc: u8, data: &[u8]) -> Vec<u8> {
660+
let mut v = vec![0u8, PLDM_TYPE_FW, cmd, cc];
661+
v.extend_from_slice(data);
662+
v
663+
}
664+
665+
fn status_data(state: PldmFDState) -> Vec<u8> {
666+
let s = GetStatusResponse {
667+
current_state: state,
668+
previous_state: PldmFDState::Idle,
669+
aux_state: 0,
670+
aux_state_status: 0,
671+
progress_percent: 0,
672+
reason_code: 0,
673+
update_option_flags_enabled: 0,
674+
};
675+
let mut b = [0u8; 16];
676+
let l = s.write_buf(&mut b).unwrap();
677+
b[..l].to_vec()
678+
}
679+
680+
fn make_update(vid: u16, components: &[&[u8]]) -> Update {
681+
let bytes = crate::pkg::tests::build_v11_package(vid, components);
682+
let pkg =
683+
crate::pkg::Package::parse(crate::pkg::temp_file_with(&bytes))
684+
.unwrap();
685+
let dev = DeviceIdentifiers {
686+
ids: vec![Descriptor::PciVid(vid)],
687+
};
688+
let fwp = FirmwareParameters {
689+
caps: DeviceCapabilities::from_u32(0),
690+
components: Vec::new().into(),
691+
active: DescriptorString::empty(),
692+
pending: DescriptorString::empty(),
693+
};
694+
Update::new(&dev, &fwp, pkg, None, None, vec![]).unwrap()
695+
}
696+
697+
// --- RequestUpdate ---------------------------------------------------
698+
699+
#[test]
700+
fn request_update_reports_actual_component_count() {
701+
let img: &[u8] = &[0u8; 64];
702+
let update = make_update(0xabcd, &[img, img]);
703+
704+
let mut comm = MockComm::new(vec![
705+
resp_frame(0x1b, 0, &status_data(PldmFDState::Idle)),
706+
// RequestUpdateResponse: FirmwareDeviceMetaDataLength + flag
707+
resp_frame(0x10, 0, &[0, 0, 0]),
708+
]);
709+
710+
request_update(&mut comm, &update).unwrap();
711+
712+
let sent = comm.sent();
713+
assert_eq!(sent.len(), 2);
714+
// sent[1] is RequestUpdate: [0x80, type, 0x10, <payload>]
715+
let ru = &sent[1];
716+
assert_eq!(ru[2], 0x10);
717+
let data = &ru[3..];
718+
// payload: MaxTransferSize(4), NumberOfComponents(2), ...
719+
let num_components = u16::from_le_bytes([data[4], data[5]]);
720+
assert_eq!(num_components, 2);
721+
}
722+
}

0 commit comments

Comments
 (0)