diff --git a/Cargo.toml b/Cargo.toml index d798a4b..23d0f60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,5 @@ authors = ["Caliptra contributors", "OpenPRoT contributors"] edition = "2024" [dependencies] -zerocopy = {version = "0.8.17", features = ["derive"]} +zerocopy = { version = "0.8.17", features = ["derive"] } bitfield = "0.14.0" diff --git a/src/codec.rs b/src/codec.rs index 15df19f..bbe35e7 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -6,6 +6,7 @@ use zerocopy::{FromBytes, Immutable, IntoBytes}; pub enum PldmCodecError { BufferTooShort, Unsupported, + InvalidData, } /// A trait for encoding and decoding PLDM (Platform Level Data Model) messages. @@ -37,7 +38,39 @@ pub trait PldmCodec: core::fmt::Debug + Sized { fn decode(buffer: &[u8]) -> Result; } +/// A trait for encoding and decoding PLDM messages with explicit lifetime requirements. +/// +/// This trait is similar to `PldmCodec` but supports types that borrow data from the buffer +/// during decoding, requiring an explicit lifetime parameter. +pub trait PldmCodecWithLifetime<'a>: core::fmt::Debug + Sized { + /// Encodes the PLDM message into the provided byte buffer. + /// + /// # Arguments + /// + /// * `buffer` - A mutable reference to a byte slice where the encoded message will be stored. + /// + /// # Returns + /// + /// A `Result` containing the size of the encoded message on success, or a `PldmCodecError` on failure. + fn encode(&self, buffer: &mut [u8]) -> Result; + + /// Decodes a PLDM message from the provided byte buffer. + /// + /// # Arguments + /// + /// * `buffer` - A reference to a byte slice containing the encoded message. The decoded + /// type may hold references to this buffer. + /// + /// # Returns + /// + /// A `Result` containing the decoded message on success, or a `PldmCodecError` on failure. + fn decode(buffer: &'a [u8]) -> Result; +} + // Default implementation of PldmCodec for types that can leverage zerocopy. +// TODO: can we generalize this to use sub-struct encodes when possible? +// There are structs like PldmFirmwareString that contain variable-length data +// that would need special handling. impl PldmCodec for T where T: core::fmt::Debug + Sized + FromBytes + IntoBytes + Immutable, diff --git a/src/message/firmware_update/get_fw_params.rs b/src/message/firmware_update/get_fw_params.rs index c18d92d..021b3b7 100644 --- a/src/message/firmware_update/get_fw_params.rs +++ b/src/message/firmware_update/get_fw_params.rs @@ -257,8 +257,7 @@ impl PldmCodec for GetFirmwareParametersResponse { offset += core::mem::size_of::(); let bytes = self.parms.encode(&mut buffer[offset..])?; - offset += bytes; - Ok(offset) + Ok(offset + bytes) } fn decode(buffer: &[u8]) -> Result { @@ -331,32 +330,71 @@ mod test { } #[test] - fn test_get_firmware_parameters_request() { + fn test_get_firmware_parameters_request_codec() { let request = GetFirmwareParametersRequest::new(0, PldmMsgType::Request); let mut buffer = [0u8; PLDM_MSG_HEADER_LEN]; request.encode(&mut buffer).unwrap(); + let decoded_request = GetFirmwareParametersRequest::decode(&buffer).unwrap(); assert_eq!(request, decoded_request); + + // Test buffer too short error + let mut buffer = [0u8; PLDM_MSG_HEADER_LEN - 1]; + assert_eq!( + request.encode(&mut buffer).unwrap_err(), + PldmCodecError::BufferTooShort + ); + + assert_eq!( + GetFirmwareParametersRequest::decode(&buffer).unwrap_err(), + PldmCodecError::BufferTooShort + ); } #[test] - fn test_get_firmware_parameters() { + fn test_get_firmware_parameters_codec() { let firmware_parameters = construct_firmware_params(); let mut buffer = [0u8; 1024]; let size = firmware_parameters.encode(&mut buffer).unwrap(); assert_eq!(size, firmware_parameters.codec_size_in_bytes()); + let decoded_firmware_parameters = FirmwareParameters::decode(&buffer[..size]).unwrap(); assert_eq!(firmware_parameters, decoded_firmware_parameters); + + // Test buffer too short error + let mut short_buffer = [0u8; 1]; + assert_eq!( + firmware_parameters.encode(&mut short_buffer).unwrap_err(), + PldmCodecError::BufferTooShort + ); + + assert_eq!( + FirmwareParameters::decode(&short_buffer).unwrap_err(), + PldmCodecError::BufferTooShort + ); } #[test] - fn test_get_firmware_parameters_response() { + fn test_get_firmware_parameters_response_codec() { let firmware_parameters = construct_firmware_params(); let response = GetFirmwareParametersResponse::new(0, 0, &firmware_parameters); let mut buffer = [0u8; 1024]; let size = response.encode(&mut buffer).unwrap(); assert_eq!(size, response.codec_size_in_bytes()); + let decoded_response = GetFirmwareParametersResponse::decode(&buffer[..size]).unwrap(); assert_eq!(response, decoded_response); + + // Test buffer too short error + let mut short_buffer = [0u8; 1]; + assert_eq!( + response.encode(&mut short_buffer).unwrap_err(), + PldmCodecError::BufferTooShort + ); + + assert_eq!( + GetFirmwareParametersResponse::decode(&short_buffer).unwrap_err(), + PldmCodecError::BufferTooShort + ); } } diff --git a/src/message/firmware_update/get_package_data.rs b/src/message/firmware_update/get_package_data.rs new file mode 100644 index 0000000..27f230c --- /dev/null +++ b/src/message/firmware_update/get_package_data.rs @@ -0,0 +1,622 @@ +// Licensed under the Apache-2.0 license + +use crate::protocol::base::{ + InstanceId, PldmBaseCompletionCode, PldmMsgHeader, PldmMsgType, PldmSupportedType, + TransferOperationFlag, PLDM_MSG_HEADER_LEN, +}; + +use crate::pldm_completion_code; + +use crate::codec::{PldmCodec, PldmCodecError, PldmCodecWithLifetime}; +use crate::protocol::firmware_update::{FwUpdateCmd, FwUpdateCompletionCode}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub const GET_PACKAGE_DATA_PORTION_SIZE: usize = 1024; + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +/// The FD sends this command to transfer optional data that shall be received prior to transferring +/// components during the firmware update process. This command is only used if the firmware update +/// package contained content within the FirmwareDevicePackageData field, the UA provided the length of +/// the package data in the RequestUpdate command, and the FD indicated that it would use this command +/// in the FDWillSendGetPackageDataCommand field. +pub struct GetPackageDataRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub data_transfer_handle: u32, + pub transfer_operation_flag: u8, +} + +impl GetPackageDataRequest { + pub fn new( + instance_id: InstanceId, + data_transfer_handle: u32, + transfer_operation_flag: TransferOperationFlag, + ) -> Self { + GetPackageDataRequest { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Request, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetPackageData as u8, + ), + data_transfer_handle, + transfer_operation_flag: transfer_operation_flag as u8, + } + } +} + +pldm_completion_code! { + GetPackageDataCode { + CommandNotExpected, + NoPackageData, + InvalidTransferHandle, + InvalidTransferOperationFlag + } +} + +const MAX_PORTION_DATA_SIZE: usize = 0xff; +#[derive(Debug, Clone, PartialEq, FromBytes)] +#[repr(C)] +/// GetPackageDataResponse is parameterized over the portion package data size. +pub struct GetPackageDataResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + + /// PLDM_BASE_CODES, COMMAND_NOT_EXPECTED, NO_PACKAGE_DATA, + /// INVALID_TRANSFER_HANDLE, INVALID_TRANSFER_OPERATION_FLAG + /// + /// See [GetPackageDataCode] + pub completion_code: u8, + pub next_data_transfer_handle: u32, + pub transfer_flag: u8, + + /// If the FD provided a value in the GetPackageDataMaximumTransferSize field, then the UA should + /// select the amount of data to return such that the byte length for this field, except when TransferFlag + /// = End or StartAndEnd, is equal to or less than that value. + pub portion_of_package_data: [u8; MAX_PORTION_DATA_SIZE], + + // Non-spec field. Since the spec tells us that this can vary in unknown size, + // we save the max and keep track of the actual size in this var. + // When encoding this into byte form, we have to omit this field and only + // encode as many bytes of [portion_of_package] as [portion_of_package_data_len] + // tells us to. + portion_of_package_data_len: usize, +} + +impl GetPackageDataResponse { + pub fn new( + instance_id: InstanceId, + completion_code: GetPackageDataCode, + next_data_transfer_handle: u32, + transfer_flag: TransferOperationFlag, + portion_of_package_data: &[u8], + ) -> Self { + let mut pdata: [u8; MAX_PORTION_DATA_SIZE] = [0x00; MAX_PORTION_DATA_SIZE]; + pdata[0..portion_of_package_data.len()].copy_from_slice(portion_of_package_data); + GetPackageDataResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetPackageData as u8, + ), + completion_code: completion_code.into(), + next_data_transfer_handle, + transfer_flag: transfer_flag as u8, + portion_of_package_data: pdata, + portion_of_package_data_len: portion_of_package_data.len(), + } + } +} + +// See: src/message/firmware_update/get_fw_params.rs for manual decode etc +impl PldmCodec for GetPackageDataResponse { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() + < core::mem::size_of::() + - MAX_PORTION_DATA_SIZE + - core::mem::size_of_val(&self.portion_of_package_data_len) + + self.portion_of_package_data_len + { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + buffer[offset..offset + size_of_val(&self.hdr.0)].copy_from_slice(&self.hdr.0); + offset += size_of_val(&self.hdr.0); + + buffer[offset] = self.completion_code; + offset += 1; + + buffer[offset..offset + size_of_val(&self.next_data_transfer_handle)] + .copy_from_slice(self.next_data_transfer_handle.as_bytes()); + offset += size_of_val(&self.next_data_transfer_handle); + + buffer[offset] = self.transfer_flag; + offset += 1; + + buffer[offset..offset + self.portion_of_package_data_len].copy_from_slice( + self.portion_of_package_data[0..self.portion_of_package_data_len].as_bytes(), + ); + + Ok(offset + self.portion_of_package_data_len) + } + + fn decode(buffer: &[u8]) -> Result { + const MIN_LEN: usize = core::mem::size_of::() + - core::mem::size_of::() + - core::mem::size_of::() * MAX_PORTION_DATA_SIZE; + + if buffer.len() < MIN_LEN { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + + let mut hdr_bytes = [0u8; PLDM_MSG_HEADER_LEN]; + hdr_bytes.copy_from_slice(&buffer[offset..offset + PLDM_MSG_HEADER_LEN]); + + let hdr = PldmMsgHeader(hdr_bytes); + offset += PLDM_MSG_HEADER_LEN; + + let completion_code = GetPackageDataCode::try_from(buffer[offset]) + .map_err(|_| PldmCodecError::Unsupported)?; + offset += 1; + + let next_data_transfer_handle = u32::from_le_bytes([ + buffer[offset], + buffer[offset + 1], + buffer[offset + 2], + buffer[offset + 3], + ]); + offset += 4; + + let transfer_flag = TransferOperationFlag::try_from(buffer[offset]) + .map_err(|_| PldmCodecError::Unsupported)?; + offset += 1; + + let portion_len = buffer.len() - offset; + if portion_len > MAX_PORTION_DATA_SIZE { + return Err(PldmCodecError::BufferTooShort); + } + + let portion_data = &buffer[offset..]; + Ok(Self::new( + hdr.instance_id(), + completion_code, + next_data_transfer_handle, + transfer_flag, + portion_data, + )) + } +} + +/// The UA sends this command to acquire optional data that the FD shall transfer to the UA prior to +/// beginning the transfer of component images. This command is only used if the FD has indicated in the +/// RequestUpdate command response that it has data that shall be retrieved and restored by the UA. The +/// firmware device metadata retrieved by this command will be sent back to the FD through the +/// GetMetaData command after all component images have been transferred. +/// +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct GetDeviceMetaDataRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub data_transfer_handle: u32, + pub transfer_operation_flag: u8, +} + +impl GetDeviceMetaDataRequest { + pub fn new( + instance_id: InstanceId, + data_transfer_handle: u32, + transfer_operation_flag: TransferOperationFlag, + ) -> Self { + GetDeviceMetaDataRequest { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Request, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetDeviceMetaData as u8, + ), + data_transfer_handle, + transfer_operation_flag: transfer_operation_flag as u8, + } + } +} + +pldm_completion_code! { + GetDeviceMetaDataCodes { + InvalidStateForCommand, + NoDeviceMetadata, + InvalidTransferHandle, + InvalidTransferOperationFlag, + PackageDataError, +}} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct GetDeviceMetaDataResponse<'a> { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + + /// PLDM_BASE_CODES, INVALID_STATE_FOR_COMMAND, NO_DEVICE_METADATA, + /// INVALID_TRANSFER_HANDLE, INVALID_TRANSFER_OPERATION_FLAG, PACKAGE_DATA_ERROR + /// + /// See [GetDeviceMetaDataCodes] + pub completion_code: u8, + pub next_data_transfer_handle: u32, + pub transfer_flag: u8, + + /// The FD should select the amount of data to return such that the byte length for this field, except + /// when TransferFlag = End or StartAndEnd, is equal to or between the values of the firmware update + /// baseline transfer size and MaximumTransferSize from the RequestUpdate or + /// RequestDownstreamDeviceUpdate command. When TransferFlag = End or StartAndEnd, the + /// variable size of this field can also be less than the firmware update baseline transfer size. + pub portion_of_device_metadata: &'a [u8], +} + +impl<'a> GetDeviceMetaDataResponse<'a> { + pub fn new( + instance_id: InstanceId, + completion_code: GetDeviceMetaDataCodes, + next_data_transfer_handle: u32, + transfer_flag: TransferOperationFlag, + portion_of_device_metadata: &'a [u8], + ) -> Self { + GetDeviceMetaDataResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetDeviceMetaData as u8, + ), + completion_code: completion_code.into(), + next_data_transfer_handle, + transfer_flag: transfer_flag as u8, + portion_of_device_metadata, + } + } +} + +impl<'a> PldmCodecWithLifetime<'a> for GetDeviceMetaDataResponse<'a> { + fn encode(&self, buffer: &mut [u8]) -> Result { + let size = core::mem::size_of::() - core::mem::size_of::<&'a [u8]>(); + if buffer.len() < size + self.portion_of_device_metadata.len() { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + self.hdr + .write_to_prefix(&mut buffer[offset..]) + .map_err(|_| PldmCodecError::BufferTooShort)?; + offset += PLDM_MSG_HEADER_LEN; + + buffer[offset] = self.completion_code; + offset += 1; + + buffer[offset..offset + size_of::()] + .copy_from_slice(&self.next_data_transfer_handle.to_le_bytes()); + offset += size_of::(); + + buffer[offset] = self.transfer_flag; + offset += size_of::(); + + buffer[offset..offset + self.portion_of_device_metadata.len()] + .copy_from_slice(self.portion_of_device_metadata); + + Ok(offset + self.portion_of_device_metadata.len()) + } + + fn decode(buffer: &'a [u8]) -> Result { + let size = core::mem::size_of::() - core::mem::size_of::<&'a [u8]>(); + if buffer.len() < size { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + let hdr = PldmMsgHeader::read_from_prefix(&buffer[offset..]) + .map_err(|_| PldmCodecError::BufferTooShort)? + .0; + offset += PLDM_MSG_HEADER_LEN; + + let completion_code = buffer[offset]; + offset += size_of::(); + + let next_data_transfer_handle = u32::from_le_bytes( + buffer[offset..offset + 4] + .try_into() + .map_err(|_| PldmCodecError::BufferTooShort)?, + ); + offset += size_of::(); + + let transfer_flag = buffer[offset]; + offset += size_of::(); + + let portion_of_device_metadata = &buffer[offset..]; + + Ok(Self { + hdr, + completion_code, + next_data_transfer_handle, + transfer_flag, + portion_of_device_metadata, + }) + } +} + +/// The FD sends this command to transfer the data that was originally obtained by the UA through the +/// [GetDeviceMetaDataRequest] command. This command shall only be used if the FD indicated in the +/// RequestUpdate response that it had device metadata that needed to be obtained by the UA. The FD can +/// send this command when it is in any state, except the IDLE and LEARN COMPONENTS state. +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct GetMetaDataRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub data_transfer_handle: u32, + pub transfer_operation_flag: u8, +} + +impl GetMetaDataRequest { + pub fn new( + instance_id: InstanceId, + data_transfer_handle: u32, + transfer_operation_flag: TransferOperationFlag, + ) -> Self { + GetMetaDataRequest { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Request, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetMetaData as u8, + ), + data_transfer_handle, + transfer_operation_flag: transfer_operation_flag as u8, + } + } +} + +pldm_completion_code! { + GetMetaDataCode { + CommandNotExpected, + InvalidTransferHandle, + InvalidTransferOperationFlag, + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct GetMetaDataResponse<'a> { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + + /// PLDM_BASE_CODES, COMMAND_NOT_EXPECTED, INVALID_TRANSFER_HANDLE, + /// INVALID_TRANSFER_OPERATION_FLAG + /// + /// See [GetMetaDataCode] + pub completion_code: u8, + pub next_data_transfer_handle: u32, + pub transfer_flag: u8, + + /// The UA should select the amount of data to return such that the byte length for this field, except + /// when TransferFlag = End or StartAndEnd, is equal to or between the values of the firmware update + /// baseline transfer size and MaximumTransferSize from the RequestUpdate or + /// [crate::message::firmware_update::query_downstream::RequestDownstreamDeviceUpdateRequest] command. + /// When TransferFlag = End or StartAndEnd, the variable size of this field can also be less than + /// the firmware update baseline transfer size. + pub portion_of_device_metadata: &'a [u8], +} + +impl<'a> GetMetaDataResponse<'a> { + pub fn new( + instance_id: InstanceId, + completion_code: GetMetaDataCode, + next_data_transfer_handle: u32, + transfer_flag: TransferOperationFlag, + portion_of_device_metadata: &'a [u8], + ) -> Self { + GetMetaDataResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetMetaData as u8, + ), + completion_code: completion_code.into(), + next_data_transfer_handle, + transfer_flag: transfer_flag as u8, + portion_of_device_metadata, + } + } +} + +impl<'a> PldmCodecWithLifetime<'a> for GetMetaDataResponse<'a> { + fn encode(&self, buffer: &mut [u8]) -> Result { + let size = core::mem::size_of::() - core::mem::size_of::<&'a [u8]>(); + if buffer.len() < size + self.portion_of_device_metadata.len() { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + self.hdr + .write_to_prefix(&mut buffer[offset..]) + .map_err(|_| PldmCodecError::BufferTooShort)?; + offset += PLDM_MSG_HEADER_LEN; + + buffer[offset] = self.completion_code; + offset += 1; + + buffer[offset..offset + 4].copy_from_slice(&self.next_data_transfer_handle.to_le_bytes()); + offset += 4; + + buffer[offset] = self.transfer_flag; + offset += 1; + + buffer[offset..offset + self.portion_of_device_metadata.len()] + .copy_from_slice(self.portion_of_device_metadata); + + Ok(offset + self.portion_of_device_metadata.len()) + } + + fn decode(buffer: &'a [u8]) -> Result { + let size = core::mem::size_of::() - core::mem::size_of::<&'a [u8]>(); + if buffer.len() < size { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + + let hdr = PldmMsgHeader::read_from_prefix(&buffer[offset..]) + .map_err(|_| PldmCodecError::BufferTooShort)? + .0; + offset += PLDM_MSG_HEADER_LEN; + + let completion_code = buffer[offset]; + offset += 1; + + let next_data_transfer_handle = u32::from_le_bytes( + buffer[offset..offset + 4] + .try_into() + .map_err(|_| PldmCodecError::BufferTooShort)?, + ); + offset += 4; + + let transfer_flag = buffer[offset]; + offset += 1; + + let portion_of_device_metadata = &buffer[offset..]; + + Ok(Self { + hdr, + completion_code, + next_data_transfer_handle, + transfer_flag, + portion_of_device_metadata, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_get_package_data_request_codec() { + let instance_id: InstanceId = 0x01; + let data_transfer_handle: u32 = 0x12345678; + let transfer_operation_flag = TransferOperationFlag::GetFirstPart; + + let request = + GetPackageDataRequest::new(instance_id, data_transfer_handle, transfer_operation_flag); + + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded = GetPackageDataRequest::decode(&buffer).unwrap(); + + assert_eq!(request, decoded); + } + + #[test] + fn test_get_package_data_response_codec() { + const PORTION_LEN: usize = 10; + + let instance_id: InstanceId = 0x01; + let next_data_transfer_handle: u32 = 0x12345678; + let transfer_operation_flag = TransferOperationFlag::GetFirstPart; + let portion = [22u8; PORTION_LEN]; + + let resp = GetPackageDataResponse::new( + instance_id, + GetPackageDataCode::BaseCodes(PldmBaseCompletionCode::Success), + next_data_transfer_handle, + transfer_operation_flag, + &portion, + ); + + let mut buffer_fitted = [0u8; core::mem::size_of::() + - MAX_PORTION_DATA_SIZE + - core::mem::size_of::() + + PORTION_LEN]; + + resp.encode(&mut buffer_fitted).unwrap(); + let decoded = GetPackageDataResponse::decode(&buffer_fitted).unwrap(); + assert_eq!(resp, decoded); + } + + #[test] + fn test_get_metadata_request_codec() { + let instance_id: InstanceId = 0x01; + let data_transfer_handle = 0x12345678; + let req = GetMetaDataRequest::new( + instance_id, + data_transfer_handle, + TransferOperationFlag::GetFirstPart, + ); + + let mut buffer = [0u8; core::mem::size_of::()]; + req.encode(&mut buffer).unwrap(); + + let decoded = GetMetaDataRequest::decode(&buffer).unwrap(); + assert_eq!(req, decoded); + } + + #[test] + fn test_get_meta_data_response_codec() { + let instance_id: InstanceId = 0x01; + let data_transfer_handle = 0x12345678; + let payload = [11u8; 20]; + + let resp = GetMetaDataResponse::new( + instance_id, + GetMetaDataCode::BaseCodes(PldmBaseCompletionCode::Success), + data_transfer_handle, + TransferOperationFlag::GetFirstPart, + &payload, + ); + + let mut buffer = + [0u8; core::mem::size_of::() - core::mem::size_of::<&[u8]>() + 20]; + resp.encode(&mut buffer).unwrap(); + + let decoded = GetMetaDataResponse::decode(&mut buffer).unwrap(); + assert_eq!(resp, decoded); + } + + #[test] + fn test_get_device_meta_data_request_codec() { + let instance_id: InstanceId = 0x01; + let data_transfer_handle = 0x12345678; + let req = GetDeviceMetaDataRequest::new( + instance_id, + data_transfer_handle, + TransferOperationFlag::GetFirstPart, + ); + + let mut buffer = [0u8; core::mem::size_of::()]; + req.encode(&mut buffer).unwrap(); + + let decoded = GetDeviceMetaDataRequest::decode(&buffer).unwrap(); + assert_eq!(req, decoded); + } + + #[test] + fn test_get_device_meta_data_response_codec() { + let instance_id: InstanceId = 0x01; + let data_transfer_handle = 0x12345678; + const TEST_PAYLOAD_LEN: usize = 20; + let payload = [11u8; TEST_PAYLOAD_LEN]; + + let resp = GetDeviceMetaDataResponse::new( + instance_id, + GetDeviceMetaDataCodes::BaseCodes(PldmBaseCompletionCode::Success), + data_transfer_handle, + TransferOperationFlag::GetFirstPart, + &payload, + ); + + let mut buffer = [0u8; core::mem::size_of::() + - core::mem::size_of::<&[u8]>() + + TEST_PAYLOAD_LEN]; + resp.encode(&mut buffer).unwrap(); + + let decoded = GetDeviceMetaDataResponse::decode(&mut buffer).unwrap(); + assert_eq!(resp, decoded); + } +} diff --git a/src/message/firmware_update/get_status.rs b/src/message/firmware_update/get_status.rs index 42c5baa..95df1fb 100644 --- a/src/message/firmware_update/get_status.rs +++ b/src/message/firmware_update/get_status.rs @@ -189,7 +189,7 @@ mod test { use crate::codec::PldmCodec; #[test] - fn test_get_status_request() { + fn test_get_status_request_codec() { let instance_id = 1; let msg_type = PldmMsgType::Request; let request = GetStatusRequest::new(instance_id, msg_type); @@ -202,7 +202,7 @@ mod test { } #[test] - fn test_get_status_response() { + fn test_get_status_response_codec() { let response = GetStatusResponse::new( 1, 0, diff --git a/src/message/firmware_update/mod.rs b/src/message/firmware_update/mod.rs index 859a174..e5cf7c6 100644 --- a/src/message/firmware_update/mod.rs +++ b/src/message/firmware_update/mod.rs @@ -3,9 +3,11 @@ pub mod activate_fw; pub mod apply_complete; pub mod get_fw_params; +pub mod get_package_data; pub mod get_status; pub mod pass_component; pub mod query_devid; +pub mod query_downstream; pub mod request_cancel; pub mod request_fw_data; pub mod request_update; diff --git a/src/message/firmware_update/pass_component.rs b/src/message/firmware_update/pass_component.rs index a8d2b7c..62797ff 100644 --- a/src/message/firmware_update/pass_component.rs +++ b/src/message/firmware_update/pass_component.rs @@ -154,7 +154,7 @@ mod tests { use super::*; #[test] - fn test_pass_component_table_request() { + fn test_pass_component_table_request_codec() { let request = PassComponentTableRequest::new( 1, PldmMsgType::Request, @@ -173,7 +173,7 @@ mod tests { } #[test] - fn test_pass_component_table_response() { + fn test_pass_component_table_response_codec() { let response = PassComponentTableResponse::new( 0, 0, diff --git a/src/message/firmware_update/query_devid.rs b/src/message/firmware_update/query_devid.rs index d17a980..6d33d4a 100644 --- a/src/message/firmware_update/query_devid.rs +++ b/src/message/firmware_update/query_devid.rs @@ -214,7 +214,7 @@ mod test { use crate::protocol::firmware_update::{Descriptor, DescriptorType}; #[test] - fn test_query_device_identifiers_resp() { + fn test_query_device_identifiers_resp_codec() { let instance_id = 0; let completion_code = 0; diff --git a/src/message/firmware_update/query_downstream.rs b/src/message/firmware_update/query_downstream.rs new file mode 100644 index 0000000..87a5664 --- /dev/null +++ b/src/message/firmware_update/query_downstream.rs @@ -0,0 +1,1532 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError, PldmCodecWithLifetime}; +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmBaseCompletionCode, PldmMsgHeader, PldmMsgType, PldmSupportedType, + TransferOperationFlag, PLDM_MSG_HEADER_LEN, +}; + +use crate::pldm_completion_code; + +use crate::protocol::firmware_update::{ + ComponentActivationMethods, Descriptor, FirmwareDeviceCapability, FwUpdateCmd, + FwUpdateCompletionCode, PldmFirmwareString, +}; +use bitfield::bitfield; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, TryFromBytes}; + +/// QueryDownstreamDevices is used by the UA to obtain the firmware identifiers +/// for the downstream devices supported by the FDP. The entire list of all +/// attached downstream devices is provided by the response to +/// [QueryDownstreamIdentifiers] command. The FDP shall provide a response +/// message to this command in all states, including IDLE. +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct QueryDownstreamDevicesRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl QueryDownstreamDevicesRequest { + pub fn new(instance_id: InstanceId) -> Self { + QueryDownstreamDevicesRequest { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Request, + PldmSupportedType::FwUpdate, + FwUpdateCmd::QueryDownstreamDevices as u8, + ), + } + } +} + +bitfield! { + #[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, PartialEq, Eq)] + pub struct QueryDownstreamDevicesCapability(u32); + impl Debug; + pub u32, reserved, _: 31, 3; + pub u32, update_simultaneous, set_update_simultaneous: 2; + pub u32, dynamic_remove, set_dynamic_remove: 1; + pub u32, dynamic_attach, set_dynamic_attach: 0; +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct QueryDownstreamDeviceResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + + /// PLDM_BASE_CODES + pub completion_code: u8, + pub downstream_device_update_supported: u8, + pub number_of_downstream_devices: u16, + pub max_number_of_downstream_devices: u16, + pub capabilities: QueryDownstreamDevicesCapability, +} + +impl QueryDownstreamDeviceResponse { + pub fn new( + instance_id: InstanceId, + completion_code: PldmBaseCompletionCode, + downstream_device_update_supported: u8, + number_of_downstream_devices: u16, + maximum_number_of_downstream_devices: u16, + ) -> Self { + QueryDownstreamDeviceResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::QueryDownstreamDevices as u8, + ), + completion_code: completion_code as u8, + downstream_device_update_supported, + number_of_downstream_devices, + max_number_of_downstream_devices: maximum_number_of_downstream_devices, + capabilities: QueryDownstreamDevicesCapability(0), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct QueryDownstreamIdentifiersRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub downstream_data_device_handle: u32, + pub transfer_op_flag: u8, +} + +impl QueryDownstreamIdentifiersRequest { + pub fn new( + instance_id: InstanceId, + downstream_data_device_handle: u32, + transfer_op_flag: TransferOperationFlag, + ) -> Self { + QueryDownstreamIdentifiersRequest { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Request, + PldmSupportedType::FwUpdate, + FwUpdateCmd::QueryDownstreamIdentifiers as u8, + ), + downstream_data_device_handle, + transfer_op_flag: transfer_op_flag as u8, + } + } +} + +// instead of using heapless::Vec, let's just allocate a fixed-size slices for now +pub const DOWNSTREAM_DEVICE_PORTION_COUNT: usize = 4; +pub const DOWNSTREAM_DEVICE_COUNT: usize = 8; +pub const DOWNSTREAM_DESCRIPTOR_COUNT: usize = 4; + +pldm_completion_code! { + QueryDownstreamIdentifiersResponseCode { + InvalidTransferHandle, + InvalidTransferOperationFlag, + DownstreamDeviceListChanged, + } +} + +#[derive(Debug, Clone, PartialEq)] +#[repr(C)] +/// The total structure for QueryDownstreamIdentifiersResponse looks as follows: +/// ```text +/// QueryDownstreamIdentifiersResponse +/// completion_code (u8) +/// next_data_transfer_handle (u32) +/// transfer_flag (u8) +/// --- portion (variable-length) +/// downstream_devices_length_i (u32) +/// number_of_downstream_devices_i (u16) +/// downstream_devices_index_i (u16) +/// --- +/// downstream_device_index_ij (u16) +/// downstream_descriptor_count_ij (u8) +/// --- +/// descriptor_type_ijk (u16) +/// descriptor_length_ijk (u16) +/// descriptor_data_ijk (variable-length L_ijk) +/// ... +/// descriptor_type_ij(k+1) (u16) +/// descriptor_length_ij(k+1) (u16) +/// descriptor_data_ij(k+1) (variable-length L_ij(k+1)) +/// ... +/// ... +/// downstream_devices_index_i(j+1) (u16) +/// downstream_device_count_i(j+1) (u8) +/// --- +/// descriptor_type_(i(j+1)k) (u16) +/// descriptor_length_(i(j+1)k) (u16) +/// descriptor_data_(i(j+1)k) (variable-length L_(i(j+1)k)) +/// ... +/// ... +/// downstream_devices_length_(i+1) (u32) +/// number_of_downstream_devices_(i+1) (u16) +/// downstream_devices_index_(i+1) (u16) +/// ... +/// ... +/// ``` +pub struct QueryDownstreamIdentifiersResponse<'a> { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + + /// PLDM_BASE_CODES, INVALID_TRANSFER_HANDLE, INVALID_TRANSFER_OPERATION_FLAG, + /// DOWNSTREAM_DEVICE_LIST_CHANGED + /// + /// See [QueryDownstreamIdentifiersResponseCode]. + pub completion_code: u8, + pub next_data_transfer_handle: u32, + pub transfer_flag: u8, + + /// QueryDownstreamIdentifiersResponsePortion + /// + /// If the FDP has negotiated a PartSize as defined by DSP0240 and its NegotiateTransferParameters + /// command, then the maximum size for this field shall be equal to or less than that negotiated value. + /// Otherwise the FDP can determine the size for this field. + // TODO: check for PartSize and make this dynamic, for now make it static + // pub portions: &'a QueryDownstreamIdentifiersPortion<'a>, + pub portion: &'a [u8], + portion_iter_current: usize, + portion_offset_next: usize, +} + +impl<'a> QueryDownstreamIdentifiersResponse<'a> { + pub fn new( + instance_id: InstanceId, + completion_code: QueryDownstreamIdentifiersResponseCode, + next_data_transfer_handle: u32, + transfer_flag: u8, + portion: &'a [u8], + ) -> Self { + QueryDownstreamIdentifiersResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::QueryDownstreamIdentifiers as u8, + ), + completion_code: completion_code.into(), + next_data_transfer_handle, + transfer_flag, + portion, + portion_iter_current: 0, + portion_offset_next: 0, + } + } +} + +#[derive(Debug, TryFromBytes, IntoBytes, Immutable, KnownLayout)] +#[repr(C, packed)] +struct PortionHeader { + pub downstream_devices_length: u32, + pub number_of_downstream_devices: u16, +} + +#[derive(Debug, TryFromBytes, IntoBytes, Immutable, KnownLayout, PartialEq, Eq)] +#[repr(C, packed)] +struct DownstreamDevicesHeader { + pub downstream_devices_index: u16, + pub downstream_descriptor_count: u8, +} + +// Idea: use get, at(i), try_at(i) and iterator to get elements dynamically from portions buffer +impl QueryDownstreamIdentifiersResponse<'_> { + /// Parse the portions slice and get the header information. + /// + /// Returns an error of the slice is too small and the parsing failed. + fn try_get_portion_header(&mut self) -> Result { + Ok(PortionHeader::try_read_from_prefix(self.portion) + .map_err(|_| PldmError::InvalidData)? + .0) + } + + /// Try to get a [DownstreamDevicesHeader] from the given slice index. + /// + /// Returns an error if the slice is too small or the parsing failed. + fn try_get_downstream_device_header( + &self, + buf: &[u8], + ) -> Result { + Ok(DownstreamDevicesHeader::try_read_from_prefix(buf) + .map_err(|_| PldmError::InvalidData)? + .0) + } + + /// Try to get a [DownstreamDevice] at a given index in the portion. + /// + /// If the index is out of range, return an [PldmError]. + pub fn try_at(&self, index: usize) -> Result, PldmError> { + for (device_index, device) in self.clone().enumerate() { + if device_index == index { + return Ok(device); + } + } + Err(PldmError::InvalidData) + } +} + +impl<'a> Iterator for QueryDownstreamIdentifiersResponse<'a> { + type Item = DownstreamDevice<'a>; + + /// Iterate over all available [DownstreamDevice] in the response portion. + fn next(&mut self) -> Option { + let portion_hdr = self.try_get_portion_header().ok()?; + if self.portion_iter_current >= portion_hdr.number_of_downstream_devices as usize { + // at the end, let's reset for next iteration + self.portion_iter_current = 0; + self.portion_offset_next = 0; + return None; + } + + let offset = if self.portion_iter_current == 0 { + size_of::() + } else { + self.portion_offset_next + }; + if offset >= self.portion.len() { + return None; + } + + let hdr: DownstreamDevicesHeader = self + .try_get_downstream_device_header(&self.portion[offset..]) + .ok()?; + let device_header_size = size_of::(); + + let desc_size = Descriptor::try_get_descriptor_length_from_blob( + &self.portion[offset + device_header_size..], + hdr.downstream_descriptor_count as usize, + ) + .ok()?; + + let next_offset = offset + device_header_size + desc_size; + if next_offset > self.portion.len() { + self.portion_iter_current = 0; + self.portion_offset_next = 0; + return None; + } + + let descriptor_start = offset + device_header_size; + let descriptor_end = next_offset; + + self.portion_offset_next = next_offset; + self.portion_iter_current += 1; + + let device = DownstreamDevice { + downstream_device_index: hdr.downstream_devices_index, + downstream_descriptor_count: hdr.downstream_descriptor_count, + downstream_descriptors: &self.portion[descriptor_start..descriptor_end], + _iter_dev_count: 0, + _iter_offset: 0, + }; + Some(device) + } +} + +impl<'a> PldmCodecWithLifetime<'a> for QueryDownstreamIdentifiersResponse<'a> { + fn encode(&self, buffer: &mut [u8]) -> Result { + let size = size_of::>() + + size_of::() + + size_of::() + + size_of::() + + self.portion.len(); + + if buffer.len() < size { + return Err(crate::codec::PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + self.hdr + .write_to(&mut buffer[offset..offset + PLDM_MSG_HEADER_LEN]) + .map_err(|_| crate::codec::PldmCodecError::BufferTooShort)?; + offset += PLDM_MSG_HEADER_LEN; + + buffer[offset] = self.completion_code; + offset += size_of::(); + buffer[offset..offset + size_of::()] + .copy_from_slice(&self.next_data_transfer_handle.to_le_bytes()); + + offset += size_of::(); + buffer[offset] = self.transfer_flag; + + offset += size_of::(); + buffer[offset..offset + self.portion.len()].copy_from_slice(self.portion); + + Ok(offset + self.portion.len()) + } + + fn decode(buffer: &'a [u8]) -> Result { + let min_size = size_of::>() + + size_of::() + + size_of::() + + size_of::(); + + if buffer.len() < min_size { + return Err(crate::codec::PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + let hdr = PldmMsgHeader::<[u8; PLDM_MSG_HEADER_LEN]>::read_from_bytes( + &buffer[offset..offset + PLDM_MSG_HEADER_LEN], + ) + .map_err(|_| crate::codec::PldmCodecError::BufferTooShort)?; + offset += PLDM_MSG_HEADER_LEN; + + let completion_code = buffer[offset]; + offset += size_of::(); + + let next_data_transfer_handle = u32::from_le_bytes( + buffer[offset..offset + size_of::()] + .try_into() + .map_err(|_| crate::codec::PldmCodecError::BufferTooShort)?, + ); + offset += size_of::(); + + let transfer_flag = buffer[offset]; + offset += size_of::(); + + let portion = &buffer[offset..]; + + Ok(QueryDownstreamIdentifiersResponse { + hdr, + completion_code, + next_data_transfer_handle, + transfer_flag, + portion, + portion_offset_next: 0, + portion_iter_current: 0, + }) + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +pub struct DownstreamDeviceIndex(u16); + +#[allow(unused)] +impl DownstreamDeviceIndex { + /// 0x0000 – 0x0FFF = Downstream index number + const FIRST_RESERVED: DownstreamDeviceIndex = DownstreamDeviceIndex(0x1000); + + /// 0x1000 – 0xFFFF = Reserved + const LAST_RESERVED: DownstreamDeviceIndex = DownstreamDeviceIndex(0xFFFF); +} + +impl TryFrom for DownstreamDeviceIndex { + type Error = (); + + /// Create a valid DownstreamDeviceIndex from a u16 value. + /// See [DownstreamDeviceIndex] with FIRST_RESERVED and LAST_RESERVED. + fn try_from(value: u16) -> Result { + if value < Self::FIRST_RESERVED.0 { + Ok(DownstreamDeviceIndex(value)) + } else { + Err(()) + } + } +} + +#[derive(Debug, Clone, PartialEq, Default, FromBytes)] +#[repr(C)] +pub struct DownstreamDevice<'a> { + pub downstream_device_index: u16, + pub downstream_descriptor_count: u8, + pub downstream_descriptors: &'a [u8], + + // Iterator state, ignore for parsing!! + _iter_dev_count: usize, + _iter_offset: usize, +} + +impl<'a> DownstreamDevice<'a> { + pub fn new( + downstream_device_index: DownstreamDeviceIndex, + downstream_descriptors: &'a [u8], + ) -> Self { + DownstreamDevice { + downstream_device_index: downstream_device_index.0, + downstream_descriptor_count: downstream_descriptors.len() as u8, + downstream_descriptors, + _iter_dev_count: 0, + _iter_offset: 0, + } + } +} + +impl Iterator for DownstreamDevice<'_> { + type Item = Descriptor; + + fn next(&mut self) -> Option { + if self.downstream_descriptor_count == 0 { + return None; + } + + if self._iter_dev_count >= self.downstream_descriptor_count as usize { + self._iter_dev_count = 0; + self._iter_offset = 0; + return None; + } + + // Read the descriptor length while skipping the type field + let descriptor_length = u16::read_from_bytes( + &self.downstream_descriptors + [self._iter_offset + size_of::()..self._iter_offset + size_of::() * 2], + ) + .ok()? as usize; + + // check bounds + if self._iter_offset + size_of::() * 2 + descriptor_length + > self.downstream_descriptors.len() + { + return None; + } + + let descriptor = Descriptor::decode( + &self.downstream_descriptors + [self._iter_offset..self._iter_offset + 2 * size_of::() + descriptor_length], + ) + .ok()?; + + self._iter_offset += size_of::() * 2 + descriptor_length; + self._iter_dev_count += 1; + + Some(descriptor) + } +} + +impl<'a> PldmCodecWithLifetime<'a> for DownstreamDevice<'a> { + fn encode(&self, buffer: &mut [u8]) -> Result { + let mut offset = 0; + let size = size_of::() + + size_of::() + + self + .downstream_descriptors + .iter() + .map(|d| d.encode(&mut []).unwrap_or(0)) + .sum::(); + + if buffer.len() < size { + return Err(crate::codec::PldmCodecError::BufferTooShort); + } + + buffer[offset..offset + size_of::()] + .copy_from_slice(&self.downstream_device_index.to_le_bytes()); + offset += size_of::(); + + buffer[offset] = self.downstream_descriptor_count; + offset += 1; + + for descriptor in self.downstream_descriptors.iter() { + let bytes_written = descriptor.encode(&mut buffer[offset..])?; + offset += bytes_written; + } + Ok(offset) + } + + fn decode(buffer: &'a [u8]) -> Result { + // min size: DownstreamDeviceIndex + descriptor count(0) + let min_size = size_of::() + size_of::(); + let mut offset = 0; + + if buffer.len() < min_size { + return Err(crate::codec::PldmCodecError::BufferTooShort); + } + + let downstream_device_index = u16::from_le_bytes( + buffer[offset..offset + size_of::()] + .try_into() + .map_err(|_| crate::codec::PldmCodecError::BufferTooShort)?, + ); + offset += size_of::(); + + let downstream_descriptor_count = buffer[offset]; + offset += 1; + + Ok(Self { + downstream_device_index, + downstream_descriptor_count, + downstream_descriptors: &buffer[offset..], + _iter_dev_count: 0, + _iter_offset: 0, + }) + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct GetDownstreamFirmwareParametersRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub data_transfer_handle: u32, + pub transfer_op_flag: u8, +} + +impl GetDownstreamFirmwareParametersRequest { + pub fn new( + instance_id: InstanceId, + data_transfer_handle: u32, + transfer_op_flag: TransferOperationFlag, + ) -> Self { + GetDownstreamFirmwareParametersRequest { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Request, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetDownstreamFirmwareParameters as u8, + ), + data_transfer_handle, + transfer_op_flag: transfer_op_flag as u8, + } + } +} + +pldm_completion_code! { + GetDownstreamFirmwareParametersResponseCode { + InvalidTransferHandle, + InvalidTransferOperationFlag, + DownstreamDeviceListChanged + } +} + +#[derive(Debug, Clone, PartialEq, FromBytes)] +#[repr(C)] +pub struct GetDownstreamFirmwareParametersResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + + /// PLDM_BASE_CODES, INVALID_TRANSFER_HANDLE, INVALID_TRANSFER_OPERATION_FLAG, + /// DOWNSTREAM_DEVICE_LIST_CHANGED + /// + /// See [GetDownstreamFirmwareParametersResponseCode]. + pub completion_code: u8, + pub next_data_transfer_handle: u32, + pub transfer_flag: u8, + + /// GetDownstreamFirmwareParametersPortion + /// + /// If the FDP has negotiated a PartSize as defined by DSP0240 and its NegotiateTransferParameters + /// command, then the maximum size for this field shall be equal to or less than that negotiated value. + /// Otherwise the FDP can determine the size for this field. + // TODO: check for PartSize and make this dynamic + pub portion: GetDownstreamFirmwareParametersPortion, +} + +// #[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[derive(Debug, Clone, PartialEq, FromBytes)] +#[repr(C)] +pub struct GetDownstreamFirmwareParametersPortion { + pub get_downstream_firmware_parameters_capability: FirmwareDeviceCapability, + pub downstream_device_count: u16, + pub downstream_device_parameter_table: DownstreamDeviceParameterTable, +} + +impl GetDownstreamFirmwareParametersPortion { + pub fn new( + capability: FirmwareDeviceCapability, + downstream_device_count: u16, + downstream_device_parameter_table: DownstreamDeviceParameterTable, + ) -> Self { + GetDownstreamFirmwareParametersPortion { + get_downstream_firmware_parameters_capability: capability, + downstream_device_count, + downstream_device_parameter_table, + } + } + + pub fn size(&self) -> usize { + size_of::() + + size_of::() + + self.downstream_device_parameter_table.size() + } +} + +impl PldmCodec for GetDownstreamFirmwareParametersPortion { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.size() { + return Err(crate::codec::PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + buffer[offset..offset + size_of::()].copy_from_slice( + &self + .get_downstream_firmware_parameters_capability + .0 + .to_le_bytes(), + ); + offset += size_of::(); + + buffer[offset..offset + size_of::()] + .copy_from_slice(&self.downstream_device_count.to_le_bytes()); + offset += size_of::(); + + let bytes_written = self + .downstream_device_parameter_table + .encode(&mut buffer[offset..])?; + + Ok(offset + bytes_written) + } + + fn decode(buffer: &[u8]) -> Result { + let min_size = size_of::() + size_of::(); + let mut offset = 0; + + if buffer.len() < min_size { + return Err(crate::codec::PldmCodecError::BufferTooShort); + } + + let capability = u32::from_le_bytes( + buffer[offset..offset + size_of::()] + .try_into() + .map_err(|_| crate::codec::PldmCodecError::InvalidData)?, + ); + offset += size_of::(); + + let downstream_device_count = u16::from_le_bytes( + buffer[offset..offset + size_of::()] + .try_into() + .map_err(|_| crate::codec::PldmCodecError::InvalidData)?, + ); + offset += size_of::(); + + let downstream_device_parameter_table = + DownstreamDeviceParameterTable::decode(&buffer[offset..])?; + + Ok(GetDownstreamFirmwareParametersPortion { + get_downstream_firmware_parameters_capability: FirmwareDeviceCapability(capability), + downstream_device_count, + downstream_device_parameter_table, + }) + } +} + +/// Wrapper struct for PLDM timestamp representation +#[derive(Debug, Clone, Copy, PartialEq, FromBytes, IntoBytes)] +#[repr(C, packed)] +pub struct PldmTimeStamp([u8; 8]); + +impl PldmTimeStamp { + /// Ensure that only valid timestamps are created + /// The timestamp string must be in the format "YYYYMMDD", where Y is year, M is month, D is day. + /// These are ASCII characters in range 0x30 to 0x39, which are the ascii digits '0' to '9'. + pub fn new(timestamp: &str) -> Result { + if timestamp.len() != 8 { + return Err(PldmError::InvalidData); + } + + let ts_bytes: [u8; 8] = timestamp + .as_bytes() + .try_into() + .map_err(|_| PldmError::InvalidData)?; + + ts_bytes.iter().try_for_each(|&b| { + if !(0x30..=0x39).contains(&b) { + Err(PldmError::InvalidData) + } else { + Ok(()) + } + })?; + Ok(PldmTimeStamp(ts_bytes)) + } +} + +#[derive(Debug, Clone, PartialEq, FromBytes, IntoBytes)] +#[repr(C, packed)] +/// # Warning +/// This struct is not a 1:1 representation of the PLDM spec, +/// since for simplicity we want to use PldmFirmwareString for the version strings. +/// This decision was made to prioritize usability over strict adherence to the spec. +/// For the original, see [DSP0267](https://www.dmtf.org/sites/default/files/standards/documents/DSP0267_1.2.0WIP99.pdf), Table 21. +/// +/// It is up for discussion whether this is the right approach. For now we proceed with this. +pub struct DownstreamDeviceParameterTable { + pub downstream_device_index: u16, + pub active_component_comparison_stamp: u32, + // pub active_component_version_string_type: u8, // See VersionStringType + // pub active_component_version_string_length: u8, + pub active_component_release_date: PldmTimeStamp, + pub pending_component_comparison_stamp: u32, + // pub pending_component_version_string_type: u8, // See VersionStringType + // pub pending_component_version_string_length: u8, + pub pending_component_release_date: PldmTimeStamp, + pub component_activation_methods: ComponentActivationMethods, + pub capabilities_during_update: CapabilitiesDuringUpdate, + + /// WARNING: when encoding/decoding this is variable in size. + pub active_component_version_string: PldmFirmwareString, + + /// "If no pending firmware component exists, this field is zero bytes in length" + /// + /// **WARNING**: when encoding/decoding this is variable in size. + pub pending_component_version_string: PldmFirmwareString, +} + +#[allow(clippy::too_many_arguments)] +impl DownstreamDeviceParameterTable { + pub fn new( + downstream_device_index: DownstreamDeviceIndex, + active_component_comparison_stamp: u32, + active_component_version_string: PldmFirmwareString, + pending_component_version_string: PldmFirmwareString, + active_component_release_date: &str, + pending_component_comparison_stamp: u32, + component_activation_methods: ComponentActivationMethods, + capabilities_during_update: CapabilitiesDuringUpdate, + pending_component_release_date: &str, + ) -> Result { + Ok(DownstreamDeviceParameterTable { + downstream_device_index: downstream_device_index.0, + active_component_comparison_stamp, + active_component_release_date: PldmTimeStamp::new(active_component_release_date)?, + pending_component_comparison_stamp, + pending_component_release_date: PldmTimeStamp::new(pending_component_release_date)?, + component_activation_methods, + capabilities_during_update, + active_component_version_string, + pending_component_version_string, + }) + } + + pub fn size(&self) -> usize { + size_of::() // downstream_device_index + + size_of::() // active_component_comparison_stamp + + size_of::() // ActiveComponentVersionStringType + + size_of::() // ActiveComponentVersionStringLength + + size_of::() // active_component_release_date + + size_of::() // pending_component_comparison_stamp + + size_of::() // PendingComponentVersionStringType + + size_of::() // PendingComponentVersionStringLength + + size_of::() // pending_component_release_date + + size_of::() // component_activation_methods + + size_of::() // capabilities_during_update + // this is 6 although it should be 4 + + self.active_component_version_string.str_len as usize + + self.pending_component_version_string.str_len as usize + } +} + +/// This is a custom implementation, since the struct contains variable-length fields. +/// [PldmFirmwareString] needs a custom codec as well, so we cannot derive it here. +impl PldmCodec for DownstreamDeviceParameterTable { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.size() { + return Err(crate::codec::PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + buffer[offset..offset + size_of::()] + .copy_from_slice(&self.downstream_device_index.to_le_bytes()); + offset += size_of::(); + + buffer[offset..offset + size_of::()] + .copy_from_slice(&self.active_component_comparison_stamp.to_le_bytes()); + offset += size_of::(); + + buffer[offset] = self.active_component_version_string.str_type; + offset += 1; + + buffer[offset] = self.active_component_version_string.str_len; + offset += 1; + + buffer[offset..offset + size_of::()] + .copy_from_slice(&self.active_component_release_date.0); + offset += size_of::(); + + buffer[offset..offset + size_of::()] + .copy_from_slice(&self.pending_component_comparison_stamp.to_le_bytes()); + offset += size_of::(); + + buffer[offset] = self.pending_component_version_string.str_type; + offset += 1; + + buffer[offset] = self.pending_component_version_string.str_len; + offset += 1; + + buffer[offset..offset + size_of::()] + .copy_from_slice(&self.pending_component_release_date.0); + offset += size_of::(); + + buffer[offset..offset + size_of::()] + .copy_from_slice(&self.component_activation_methods.0.to_le_bytes()); + offset += size_of::(); + + buffer[offset..offset + size_of::()] + .copy_from_slice(&self.capabilities_during_update.0.to_le_bytes()); + offset += size_of::(); + + buffer[offset..offset + self.active_component_version_string.str_len as usize] + .copy_from_slice( + &self.active_component_version_string.str_data + [..self.active_component_version_string.str_len as usize], + ); + offset += self.active_component_version_string.str_len as usize; + + buffer[offset..offset + self.pending_component_version_string.str_len as usize] + .copy_from_slice( + &self.pending_component_version_string.str_data + [..self.pending_component_version_string.str_len as usize], + ); + Ok(offset + self.pending_component_version_string.str_len as usize) + } + + fn decode(buffer: &[u8]) -> Result { + // min size, assumed both version strings are 0 length + let min_size = size_of::() + + size_of::() + + size_of::() + + size_of::() + + size_of::() + + size_of::() + + size_of::(); + + if buffer.len() < min_size { + return Err(crate::codec::PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + let downstream_device_index = u16::from_le_bytes( + buffer[offset..offset + size_of::()] + .try_into() + .map_err(|_| crate::codec::PldmCodecError::InvalidData)?, + ); + offset += size_of::(); + + let active_component_comparison_stamp = u32::from_le_bytes( + buffer[offset..offset + size_of::()] + .try_into() + .map_err(|_| crate::codec::PldmCodecError::InvalidData)?, + ); + offset += size_of::(); + + // in the spec we now have to decode the string information one after another + // See [DownstreamDeviceParameterTable] warning + let active_component_version_string_type = buffer[offset]; + offset += size_of::(); + + let active_component_version_string_length = buffer[offset]; + offset += size_of::(); + + let active_component_release_date = PldmTimeStamp::try_read_from_prefix( + &buffer[offset..offset + size_of::()], + ) + .map_err(|_| PldmCodecError::InvalidData)? + .0; + offset += size_of::(); + + let pending_component_comparison_stamp = u32::from_le_bytes( + buffer[offset..offset + size_of::()] + .try_into() + .map_err(|_| crate::codec::PldmCodecError::InvalidData)?, + ); + offset += size_of::(); + + let pending_component_version_string_type = buffer[offset]; + offset += size_of::(); + + let pending_component_version_string_length = buffer[offset]; + offset += size_of::(); + + let pending_component_release_date = PldmTimeStamp::try_read_from_prefix( + &buffer[offset..offset + size_of::()], + ) + .map_err(|_| PldmCodecError::InvalidData)? + .0; + offset += size_of::(); + + let component_activation_methods = ComponentActivationMethods::decode(&buffer[offset..])?; + offset += size_of::(); + + let capabilities_during_update = CapabilitiesDuringUpdate::decode(&buffer[offset..])?; + offset += size_of::(); + + let mut active_component_version_string = PldmFirmwareString { + str_type: active_component_version_string_type, + str_len: active_component_version_string_length, + str_data: [0u8; 32], + }; + active_component_version_string.str_data[..active_component_version_string_length as usize] + .copy_from_slice( + &buffer[offset..offset + active_component_version_string_length as usize], + ); + offset += active_component_version_string_length as usize; + + let mut pending_component_version_string = PldmFirmwareString { + str_type: pending_component_version_string_type, + str_len: pending_component_version_string_length, + str_data: [0u8; 32], + }; + pending_component_version_string.str_data + [..pending_component_version_string_length as usize] + .copy_from_slice( + &buffer[offset..offset + pending_component_version_string_length as usize], + ); + + Ok(DownstreamDeviceParameterTable { + downstream_device_index, + active_component_comparison_stamp, + active_component_release_date, + pending_component_comparison_stamp, + pending_component_release_date, + component_activation_methods, + capabilities_during_update, + active_component_version_string, + pending_component_version_string, + }) + } +} + +impl GetDownstreamFirmwareParametersResponse { + pub fn new( + instance_id: InstanceId, + completion_code: GetDownstreamFirmwareParametersResponseCode, + next_data_transfer_handle: u32, + transfer_flag: u8, + portion: GetDownstreamFirmwareParametersPortion, + ) -> Self { + GetDownstreamFirmwareParametersResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetDownstreamFirmwareParameters as u8, + ), + completion_code: completion_code.into(), + next_data_transfer_handle, + transfer_flag, + portion, + } + } +} + +impl PldmCodec for GetDownstreamFirmwareParametersResponse { + fn encode(&self, buffer: &mut [u8]) -> Result { + let size = size_of::>() + + size_of::() + + size_of::() + + size_of::() + + self.portion.size(); + + if buffer.len() < size { + return Err(crate::codec::PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + self.hdr + .write_to(&mut buffer[offset..offset + PLDM_MSG_HEADER_LEN]) + .map_err(|_| crate::codec::PldmCodecError::BufferTooShort)?; + offset += PLDM_MSG_HEADER_LEN; + + buffer[offset] = self.completion_code; + offset += size_of::(); + + buffer[offset..offset + size_of::()] + .copy_from_slice(&self.next_data_transfer_handle.to_le_bytes()); + offset += size_of::(); + + buffer[offset] = self.transfer_flag; + offset += size_of::(); + + let bytes_written = self.portion.encode(&mut buffer[offset..])?; + + Ok(offset + bytes_written) + } + + fn decode(buffer: &[u8]) -> Result { + let min_size = size_of::>() + + size_of::() + + size_of::() + + size_of::() + + size_of::() + + size_of::(); + + if buffer.len() < min_size { + return Err(crate::codec::PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + let hdr = PldmMsgHeader::<[u8; PLDM_MSG_HEADER_LEN]>::read_from_bytes( + &buffer[offset..offset + PLDM_MSG_HEADER_LEN], + ) + .map_err(|_| crate::codec::PldmCodecError::InvalidData)?; + offset += PLDM_MSG_HEADER_LEN; + + let completion_code = buffer[offset]; + offset += size_of::(); + + let next_data_transfer_handle = u32::from_le_bytes( + buffer[offset..offset + size_of::()] + .try_into() + .map_err(|_| crate::codec::PldmCodecError::InvalidData)?, + ); + offset += size_of::(); + + let transfer_flag = buffer[offset]; + offset += size_of::(); + + let portion = GetDownstreamFirmwareParametersPortion::decode(&buffer[offset..])?; + Ok(GetDownstreamFirmwareParametersResponse { + hdr, + completion_code, + next_data_transfer_handle, + transfer_flag, + portion, + }) + } +} + +bitfield! { + #[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, PartialEq, Eq)] + pub struct CapabilitiesDuringUpdate(u32); + impl Debug; + pub u32, reserved, _: 31, 5; + pub u32, component_security_level_latest, set_component_security_level_latest: 4; + pub u32, security_revision_number_updateable, set_security_revision_number_updateable: 3; + pub u32, component_downgrade_capability, set_component_downgrade_capability: 2; + pub u32, downstream_updateable, set_downstream_updateable: 1; + pub u32, downstream_apply_state, set_downstream_apply_state: 0; +} +// DMTF0267 12.17 +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct RequestDownstreamDeviceUpdateRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + // "This value shall be equal to or greater than firmware update baseline transfer size" + // See section 7.8 + pub max_downstream_device_transfer_size: u32, + pub max_outstanding_transfer_requests: u8, + pub downstream_device_pkg_data_length: u16, +} + +impl RequestDownstreamDeviceUpdateRequest { + pub fn new( + instance_id: InstanceId, + max_downstream_device_transfer_size: u32, + max_outstanding_transfer_requests: u8, + downstream_device_pkg_data_length: u16, + ) -> Self { + RequestDownstreamDeviceUpdateRequest { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Request, + PldmSupportedType::FwUpdate, + FwUpdateCmd::RequestDownstreamDeviceUpdate as u8, + ), + max_downstream_device_transfer_size, + max_outstanding_transfer_requests, + downstream_device_pkg_data_length, + } + } +} + +#[derive(Debug, Clone, Immutable, PartialEq)] +pub enum DDWillSendGetPackageDataCommand { + FDPShouldObtainUALimited = 0x02, + FDPShouldObtainLearn = 0x01, + FDPNoSupport = 0x00, +} + +impl TryFrom for DDWillSendGetPackageDataCommand { + type Error = (); + fn try_from(value: u8) -> Result { + match value { + 0x02 => Ok(DDWillSendGetPackageDataCommand::FDPShouldObtainUALimited), + 0x01 => Ok(DDWillSendGetPackageDataCommand::FDPShouldObtainLearn), + 0x00 => Ok(DDWillSendGetPackageDataCommand::FDPNoSupport), + _ => Err(()), + } + } +} + +impl From for u8 { + fn from(cmd: DDWillSendGetPackageDataCommand) -> Self { + match cmd { + DDWillSendGetPackageDataCommand::FDPShouldObtainUALimited => 0x02, + DDWillSendGetPackageDataCommand::FDPShouldObtainLearn => 0x01, + DDWillSendGetPackageDataCommand::FDPNoSupport => 0x00, + } + } +} + +pldm_completion_code! { + RequestDownstreamDeviceUpdateCode { + AlreadyInUpdateMode, + UnableToInitiateUpdate, + RetryRequestUpdate + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct RequestDownstreamDeviceUpdateResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + + /// PLDM_BASE_CODES, ALREADY_IN_UPDATE_MODE, UNABLE_TO_INITIATE_UPDATE, + /// RETRY_REQUEST_UPDATE + /// + /// See [RequestDownstreamDeviceUpdateCode]. + pub completion_code: u8, + pub downstream_device_metadata_length: u16, + pub pkg_data_command: u8, + pub get_pkg_data_max_transfer_size: u16, +} + +impl RequestDownstreamDeviceUpdateResponse { + pub fn new( + instance_id: InstanceId, + completion_code: RequestDownstreamDeviceUpdateCode, + downstream_device_metadata_length: u16, + pkg_data_command: DDWillSendGetPackageDataCommand, + get_pkg_data_max_transfer_size: u16, + ) -> Self { + RequestDownstreamDeviceUpdateResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::RequestDownstreamDeviceUpdate as u8, + ), + completion_code: completion_code.into(), + downstream_device_metadata_length, + pkg_data_command: pkg_data_command.into(), + get_pkg_data_max_transfer_size, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{codec::PldmCodec, protocol::firmware_update::DescriptorType}; + + #[test] + fn test_query_downstream_devices_request_codec() { + let instance_id: InstanceId = 0x01; + let req = QueryDownstreamDevicesRequest::new(instance_id); + + let mut buffer_encode = [0u8; core::mem::size_of::()]; + req.encode(&mut buffer_encode).unwrap(); + + let decoded = QueryDownstreamDevicesRequest::decode(&buffer_encode).unwrap(); + assert_eq!(req, decoded); + } + + #[test] + fn test_query_downstream_devices_response_codec() { + let instance_id: InstanceId = 0x01; + let resp = QueryDownstreamDeviceResponse::new( + instance_id, + PldmBaseCompletionCode::Success, + 1u8, + 1u16, + 1u16, + ); + + let mut buffer_encode = [0u8; core::mem::size_of::()]; + resp.encode(&mut buffer_encode).unwrap(); + + let decoded = QueryDownstreamDeviceResponse::decode(&buffer_encode).unwrap(); + assert_eq!(resp, decoded); + } + + #[test] + fn test_query_downstream_identifiers_request_codec() { + let instance_id: InstanceId = 0x01; + let downstream_data_device_handle = 0x12345678; + let transfer_op_flag = TransferOperationFlag::GetFirstPart; + + let req = QueryDownstreamIdentifiersRequest::new( + instance_id, + downstream_data_device_handle, + transfer_op_flag, + ); + + let mut buffer_encode = [0u8; core::mem::size_of::()]; + req.encode(&mut buffer_encode).unwrap(); + + let decoded = QueryDownstreamIdentifiersRequest::decode(&buffer_encode).unwrap(); + assert_eq!(req, decoded); + } + + #[test] + fn test_query_downstream_identifiers_response_codec() { + let instance_id: InstanceId = 0x01; + let resp = QueryDownstreamIdentifiersResponse::new( + instance_id, + QueryDownstreamIdentifiersResponseCode::BaseCodes(PldmBaseCompletionCode::Success), + 0x12345678, + TransferOperationFlag::GetFirstPart as u8, + &[0u8; 16], + ); + + let mut buffer_encode = [0u8; 128]; + let bytes_written = resp.encode(&mut buffer_encode).unwrap(); + let decoded = + QueryDownstreamIdentifiersResponse::decode(&buffer_encode[..bytes_written]).unwrap(); + assert_eq!(resp, decoded); + } + + #[test] + fn test_get_downstream_firmware_parameters_request_codec() { + let instance_id: InstanceId = 0x01; + let req = GetDownstreamFirmwareParametersRequest::new( + instance_id, + 0x12345678, + TransferOperationFlag::GetFirstPart, + ); + + let mut buffer_encode = + [0u8; core::mem::size_of::()]; + req.encode(&mut buffer_encode).unwrap(); + + let decoded = GetDownstreamFirmwareParametersRequest::decode(&buffer_encode).unwrap(); + assert_eq!(req, decoded); + } + + #[test] + fn test_device_parameter_table_codec() { + let downstream_device_index = DownstreamDeviceIndex::try_from(1).unwrap(); + let acvs = PldmFirmwareString::new("ascii", "test").unwrap(); + let pcvs = PldmFirmwareString::new("ascii", "test").unwrap(); + + let table = DownstreamDeviceParameterTable::new( + downstream_device_index, + 12345, + acvs, + pcvs, + "20250101", + 12346, + ComponentActivationMethods(0), + CapabilitiesDuringUpdate(0), + "20250202", + ) + .unwrap(); + + const STR_DATA_OFFSET: usize = size_of::() + + size_of::() + + 2 * size_of:: ()// string meta data + + size_of::() + + size_of::() + + 2 * size_of::()// string meta data + + size_of::() + + size_of::() + + size_of::(); + + let mut buffer = [0u8; STR_DATA_OFFSET + 4 + 4]; + let bytes_written = table.encode(&mut buffer).unwrap(); + + // check if the strings were encoded correctly + let mut offset = STR_DATA_OFFSET; + assert_eq!(&buffer[offset..offset + 4], b"test"); + offset += 4; + + assert_eq!(&buffer[offset..offset + 4], b"test"); + + let decoded = DownstreamDeviceParameterTable::decode(&buffer[..bytes_written]).unwrap(); + assert_eq!(table, decoded); + } + + #[test] + fn test_get_downstream_firmware_parameters_portion_codec() { + let downstream_device_index = DownstreamDeviceIndex::try_from(1).unwrap(); + let acvs = PldmFirmwareString::new("ascii", "test").unwrap(); + let pcvs = PldmFirmwareString::new("ascii", "test").unwrap(); + + let downstream_device_parameter_table = DownstreamDeviceParameterTable::new( + downstream_device_index, + 12345, + acvs, + pcvs, + "20250101", + 12346, + ComponentActivationMethods(0), + CapabilitiesDuringUpdate(0), + "20250202", + ) + .unwrap(); + + let portion = GetDownstreamFirmwareParametersPortion { + get_downstream_firmware_parameters_capability: FirmwareDeviceCapability(0u32), + downstream_device_count: 1, + downstream_device_parameter_table, + }; + + let mut buffer = [0u8; 0xff0]; + let bytes_written = portion.encode(&mut buffer).unwrap(); + + let decoded = + GetDownstreamFirmwareParametersPortion::decode(&buffer[..bytes_written]).unwrap(); + assert_eq!(portion, decoded); + } + + #[test] + fn test_get_downstream_firmware_parameters_response_codec() { + let instance_id: InstanceId = 0x01; + let downstream_device_index = DownstreamDeviceIndex::try_from(1).unwrap(); + let cap: FirmwareDeviceCapability = FirmwareDeviceCapability(0u32); + + let acvs = PldmFirmwareString::new("ascii", "test").unwrap(); + let pcvs = PldmFirmwareString::new("ascii", "test").unwrap(); + + let downstream_device_parameter_table = DownstreamDeviceParameterTable::new( + downstream_device_index, + 12345, + acvs, + pcvs, + "20250101", + 12346, + ComponentActivationMethods(0), + CapabilitiesDuringUpdate(0), + "20250202", + ) + .unwrap(); + + let portion = GetDownstreamFirmwareParametersPortion { + get_downstream_firmware_parameters_capability: cap, + downstream_device_count: 1, + downstream_device_parameter_table, + }; + + let resp = GetDownstreamFirmwareParametersResponse::new( + instance_id, + GetDownstreamFirmwareParametersResponseCode::BaseCodes(PldmBaseCompletionCode::Success), + 0x12345678, + TransferOperationFlag::GetFirstPart as u8, + portion, + ); + + let mut buffer_encode = [0u8; 0xff0]; + let bytes_written = resp.encode(&mut buffer_encode).unwrap(); + + let decoded = + GetDownstreamFirmwareParametersResponse::decode(&buffer_encode[..bytes_written]) + .unwrap(); + assert_eq!(resp, decoded); + } + + #[test] + fn test_request_downstream_device_update_request_codec() { + let instance_id: InstanceId = 0x01; + let req = RequestDownstreamDeviceUpdateRequest::new(instance_id, 1024, 4, 512); + + let mut buffer_encode = [0u8; core::mem::size_of::()]; + req.encode(&mut buffer_encode).unwrap(); + + let decoded = RequestDownstreamDeviceUpdateRequest::decode(&buffer_encode).unwrap(); + assert_eq!(req, decoded); + } + + #[test] + fn test_request_downstream_device_update_response_codec() { + let instance_id: InstanceId = 0x01; + let resp = RequestDownstreamDeviceUpdateResponse::new( + instance_id, + RequestDownstreamDeviceUpdateCode::BaseCodes(PldmBaseCompletionCode::Success), + 256, + DDWillSendGetPackageDataCommand::FDPShouldObtainLearn, + 512, + ); + + let mut buffer_encode = + [0u8; core::mem::size_of::()]; + resp.encode(&mut buffer_encode).unwrap(); + + let decoded = RequestDownstreamDeviceUpdateResponse::decode(&buffer_encode).unwrap(); + assert_eq!(resp, decoded); + } + + #[test] + fn test_downstream_device_codec() { + let descriptor = Descriptor { + descriptor_type: 0xff, + descriptor_length: 0x02, + descriptor_data: [0u8; 64], + }; + let descriptors: [Descriptor; 3] = [descriptor.clone(), descriptor.clone(), descriptor]; + + const DESC_LEN: usize = size_of::(); + let mut descriptor_bytes_max: [u8; DESC_LEN * 3] = [0u8; DESC_LEN * 3]; + let mut offset = 0; + + // encoding + for desc in descriptors.iter() { + let encoded = desc.encode(&mut descriptor_bytes_max[offset..]).unwrap(); + offset += encoded; + } + + let downstream_device_index = DownstreamDeviceIndex::try_from(1).unwrap(); + let downstream_device = + DownstreamDevice::new(downstream_device_index, &descriptor_bytes_max); + + let mut buffer = [0u8; 256]; + let bytes_written = downstream_device.encode(&mut buffer).unwrap(); + let decoded = DownstreamDevice::decode(&buffer[..bytes_written]).unwrap(); + assert_eq!(downstream_device, decoded); + } + + #[test] + fn test_iterator_query_downstream_identifiers_response() { + const DSC_DATA_LEN: usize = 16; + let instance_id: InstanceId = 0x01; + let ph: PortionHeader = PortionHeader { + downstream_devices_length: 1, + number_of_downstream_devices: 1, + }; + let dsdh: DownstreamDevicesHeader = DownstreamDevicesHeader { + downstream_devices_index: 1, + downstream_descriptor_count: 3, + }; + let dsc_0: Descriptor = Descriptor { + descriptor_type: DescriptorType::VendorDefined as u16, + descriptor_length: DSC_DATA_LEN as u16, + descriptor_data: [0u8; 64], + }; + + let mut dsc_1 = dsc_0.clone(); + dsc_1.descriptor_data[0..16].clone_from_slice(&[1u8; 16]); + + let mut dsc_2 = dsc_0.clone(); + dsc_2.descriptor_data[0..16].clone_from_slice(&[2u8; 16]); + + const LEN: usize = size_of::() + + size_of::() + + 3 * (2 * size_of::() + DSC_DATA_LEN); // type + length + data + + let mut offset = 0; + let mut portion: [u8; LEN] = [0u8; LEN]; + + portion[0..offset + size_of::()].copy_from_slice(&ph.as_bytes()); + offset += size_of::(); + + portion[offset..offset + size_of::()] + .copy_from_slice(&dsdh.as_bytes()); + offset += size_of::(); + + for desc in [dsc_0.clone(), dsc_1.clone(), dsc_2.clone()].iter() { + let size = &desc.encode(&mut portion[offset..]).unwrap(); + offset += size; + } + + let mut qdir = QueryDownstreamIdentifiersResponse::new( + instance_id, + QueryDownstreamIdentifiersResponseCode::BaseCodes(PldmBaseCompletionCode::Success), + 0x12345678, + 0x00, + &portion, + ); + + // test iterator implementation + let mut qdir_iter = qdir.next(); + let mut dsd_iter = qdir_iter.as_mut().unwrap().next(); + assert!(dsd_iter.is_some()); + assert_eq!(dsc_0, dsd_iter.unwrap()); + + dsd_iter = qdir_iter.as_mut().unwrap().next(); + assert!(dsd_iter.is_some()); + assert_eq!(dsc_1, dsd_iter.unwrap()); + + dsd_iter = qdir_iter.as_mut().unwrap().next(); + assert!(dsd_iter.is_some()); + assert_eq!(dsc_2, dsd_iter.unwrap()); + + qdir_iter = qdir.next(); + assert!(qdir_iter.is_none()); + + // test try_at + assert!(qdir.try_at(0).is_ok()); + assert!(qdir.try_at(1).is_err()); + } +} diff --git a/src/message/firmware_update/request_cancel.rs b/src/message/firmware_update/request_cancel.rs index 905698f..abd7a0d 100644 --- a/src/message/firmware_update/request_cancel.rs +++ b/src/message/firmware_update/request_cancel.rs @@ -133,7 +133,7 @@ mod test { use crate::codec::PldmCodec; #[test] - fn test_cancel_update_request() { + fn test_cancel_update_request_codec() { let cancel_update_request = CancelUpdateRequest::new(0x01, PldmMsgType::Request); let mut buffer = [0u8; core::mem::size_of::()]; cancel_update_request.encode(&mut buffer).unwrap(); @@ -142,7 +142,7 @@ mod test { } #[test] - fn test_cancel_update_response() { + fn test_cancel_update_response_codec() { let response = CancelUpdateResponse::new( 0x01, 0x00, diff --git a/src/message/firmware_update/request_fw_data.rs b/src/message/firmware_update/request_fw_data.rs index 432b124..c106d89 100644 --- a/src/message/firmware_update/request_fw_data.rs +++ b/src/message/firmware_update/request_fw_data.rs @@ -1,6 +1,5 @@ // Licensed under the Apache-2.0 license - -use crate::codec::{PldmCodec, PldmCodecError}; +use crate::codec::{PldmCodecError, PldmCodecWithLifetime}; use crate::protocol::base::{ InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, }; @@ -40,6 +39,7 @@ impl RequestFirmwareDataRequest { #[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] #[repr(C, packed)] +// TODO: this is calitpra code, but imho this structure is too clunky. pub struct RequestFirmwareDataResponseFixed { pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, pub completion_code: u8, @@ -52,12 +52,12 @@ pub struct RequestFirmwareDataResponse<'a> { pub data: &'a [u8], } -impl RequestFirmwareDataResponse<'_> { +impl<'a> RequestFirmwareDataResponse<'a> { pub fn new( instance_id: InstanceId, completion_code: u8, - data: &[u8], - ) -> RequestFirmwareDataResponse { + data: &'a [u8], + ) -> RequestFirmwareDataResponse<'a> { let fixed = RequestFirmwareDataResponseFixed { hdr: PldmMsgHeader::new( instance_id, @@ -77,7 +77,7 @@ impl RequestFirmwareDataResponse<'_> { } } -impl PldmCodec for RequestFirmwareDataResponse<'_> { +impl<'a> PldmCodecWithLifetime<'a> for RequestFirmwareDataResponse<'a> { fn encode(&self, buffer: &mut [u8]) -> Result { if buffer.len() < self.codec_size_in_bytes() { return Err(PldmCodecError::BufferTooShort); @@ -99,31 +99,45 @@ impl PldmCodec for RequestFirmwareDataResponse<'_> { } // Decoding is implemented for this struct. The caller should use the `length` field in the request to read the image portion data from the buffer. - fn decode(_buffer: &[u8]) -> Result { - Err(PldmCodecError::Unsupported) + fn decode(buffer: &'a [u8]) -> Result { + dbg!(buffer); + let size = core::mem::size_of::(); + if buffer.len() < size { + return Err(PldmCodecError::BufferTooShort); + } + + let (fixed, _) = + RequestFirmwareDataResponseFixed::read_from_prefix(&buffer[..size]).unwrap(); + Ok(RequestFirmwareDataResponse { + fixed, + data: &buffer[size..], + }) } } #[cfg(test)] mod test { use super::*; + use crate::codec::PldmCodec; #[test] - fn test_request_firmware_data_request() { + fn test_request_firmware_data_request_codec() { let request = RequestFirmwareDataRequest::new(1, PldmMsgType::Request, 0, 64); let mut buffer = [0u8; 1024]; let bytes = request.encode(&mut buffer).unwrap(); + let decoded_request = RequestFirmwareDataRequest::decode(&buffer[..bytes]).unwrap(); assert_eq!(request, decoded_request); } #[test] - fn test_request_firmware_data_response() { + fn test_request_firmware_data_response_codec() { let data = [0u8; 512]; let response = RequestFirmwareDataResponse::new(1, 0, &data); let mut buffer = [0u8; 1024]; let bytes = response.encode(&mut buffer).unwrap(); - let decoded_response = RequestFirmwareDataResponse::decode(&buffer[..bytes]); - assert!(decoded_response.is_err()); + + let decoded_response = RequestFirmwareDataResponse::decode(&buffer[..bytes]).unwrap(); + assert_eq!(response, decoded_response); } } diff --git a/src/message/firmware_update/request_update.rs b/src/message/firmware_update/request_update.rs index 9c8b154..751fae4 100644 --- a/src/message/firmware_update/request_update.rs +++ b/src/message/firmware_update/request_update.rs @@ -235,7 +235,7 @@ mod tests { use crate::protocol::firmware_update::PldmFirmwareString; #[test] - fn test_request_update_request() { + fn test_request_update_request_codec() { let request = RequestUpdateRequest::new( 0, PldmMsgType::Request, @@ -255,7 +255,7 @@ mod tests { } #[test] - fn test_request_update_response() { + fn test_request_update_response_codec() { let response = RequestUpdateResponse::new(1, 0, 128, 0x02, Some(2048)); let mut buffer = [0u8; 512]; diff --git a/src/message/firmware_update/transfer_complete.rs b/src/message/firmware_update/transfer_complete.rs index fae32c8..56c98c1 100644 --- a/src/message/firmware_update/transfer_complete.rs +++ b/src/message/firmware_update/transfer_complete.rs @@ -101,7 +101,7 @@ mod test { use crate::codec::PldmCodec; #[test] - fn test_transfer_complete_request() { + fn test_transfer_complete_request_codec() { let request = TransferCompleteRequest::new( 0x01, PldmMsgType::Request, @@ -114,7 +114,7 @@ mod test { } #[test] - fn test_transfer_complete_response() { + fn test_transfer_complete_response_codec() { let response = TransferCompleteResponse::new(0x01, 0x00); let mut buffer = [0u8; core::mem::size_of::()]; response.encode(&mut buffer).unwrap(); diff --git a/src/message/firmware_update/update_component.rs b/src/message/firmware_update/update_component.rs index 30f5384..f44a74b 100644 --- a/src/message/firmware_update/update_component.rs +++ b/src/message/firmware_update/update_component.rs @@ -299,7 +299,7 @@ mod test { use super::*; #[test] - fn test_update_component_request() { + fn test_update_component_request_codec() { let request = UpdateComponentRequest::new( 0, PldmMsgType::Request, @@ -319,7 +319,7 @@ mod test { } #[test] - fn test_update_component_response() { + fn test_update_component_response_codec() { let response = UpdateComponentResponse::new( 0, 0x00, diff --git a/src/message/firmware_update/verify_complete.rs b/src/message/firmware_update/verify_complete.rs index 1e7b0cb..28f55d3 100644 --- a/src/message/firmware_update/verify_complete.rs +++ b/src/message/firmware_update/verify_complete.rs @@ -91,7 +91,7 @@ mod test { use crate::codec::PldmCodec; #[test] - fn test_verify_complete_request() { + fn test_verify_complete_request_codec() { let request = VerifyCompleteRequest::new(0x01, PldmMsgType::Request, VerifyResult::VerifySuccess); let mut buffer = [0u8; core::mem::size_of::()]; @@ -101,7 +101,7 @@ mod test { } #[test] - fn test_verify_complete_response() { + fn test_verify_complete_response_codec() { let response = VerifyCompleteResponse::new(0x01, 0x00); let mut buffer = [0u8; core::mem::size_of::()]; response.encode(&mut buffer).unwrap(); diff --git a/src/protocol/base.rs b/src/protocol/base.rs index ccb757a..bb6dccf 100644 --- a/src/protocol/base.rs +++ b/src/protocol/base.rs @@ -90,7 +90,7 @@ pub enum PldmHeaderVersion { Version0 = 0x00, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] #[repr(u8)] pub enum PldmBaseCompletionCode { Success = 0x00, @@ -256,10 +256,67 @@ impl PldmFailureResponse { } } +/// Macro to define PLDM completion code enum with base codes and custom codes. +/// By default, the entire [PldmBaseCompletionCode] is included as a +/// variant named `BaseCodes`. The additional custom completion codes can be +/// specified as variants of [FwUpdateCompletionCode]. +/// +/// This macro is useful for the completion codes for the command responses, to ensure +/// type safety while still allowing the use of base completion codes and an +/// arbitrary number of custom completion codes specific to the command. +#[macro_export] +macro_rules! pldm_completion_code { + ( + $enum_name:ident { + $($variant:ident),* $(,)? + } + ) => { + #[derive(PartialEq, Debug)] + pub enum $enum_name { + BaseCodes(PldmBaseCompletionCode), + $($variant),* + } + + impl From<$enum_name> for u8 { + fn from(code: $enum_name) -> Self { + match code { + $enum_name::BaseCodes(code) => code as u8, + $( + $enum_name::$variant => { + FwUpdateCompletionCode::$variant as u8 + } + )* + } + } + } + + impl TryFrom for $enum_name { + type Error = $crate::error::PldmError; + + fn try_from(code: u8) -> Result { + if let Ok(base_code) = PldmBaseCompletionCode::try_from(code) { + return Ok($enum_name::BaseCodes(base_code)); + } + + match FwUpdateCompletionCode::try_from(code) { + $( + Ok(FwUpdateCompletionCode::$variant) => { + Ok($enum_name::$variant) + } + )* + Ok(_) => Err($crate::error::PldmError::InvalidCompletionCode), + Err(e) => Err(e), + } + } + } + }; +} + #[cfg(test)] mod tests { use super::*; use crate::codec::PldmCodec; + use crate::protocol::firmware_update::FwUpdateCompletionCode; #[test] fn test_pldm_msg_header() { @@ -299,4 +356,22 @@ mod tests { let decoded_resp = PldmFailureResponse::decode(&buffer).unwrap(); assert_eq!(resp, decoded_resp); } + + #[test] + fn test_pldm_completion_code_macro() { + pldm_completion_code! { + TestEnum { + InvalidStateForCommand, + NoDeviceMetadata, + InvalidTransferHandle, + InvalidTransferOperationFlag, + PackageDataError, + } + } + + let base = TestEnum::BaseCodes(PldmBaseCompletionCode::Success); + if let TestEnum::BaseCodes(i) = base { + assert_eq!(i, PldmBaseCompletionCode::Success); + } + } } diff --git a/src/protocol/firmware_update.rs b/src/protocol/firmware_update.rs index f959f50..b5b9c48 100644 --- a/src/protocol/firmware_update.rs +++ b/src/protocol/firmware_update.rs @@ -31,6 +31,14 @@ pub enum FwUpdateCmd { GetStatus = 0x1B, CancelUpdateComponent = 0x1C, CancelUpdate = 0x1D, + // new + QueryDownstreamDevices = 0x03, // defined in DSP0267 + QueryDownstreamIdentifiers = 0x04, + GetDownstreamFirmwareParameters = 0x05, + RequestDownstreamDeviceUpdate = 0x20, + GetPackageData = 0x11, + GetDeviceMetaData = 0x12, + GetMetaData = 0x19, } impl TryFrom for FwUpdateCmd { @@ -51,6 +59,13 @@ impl TryFrom for FwUpdateCmd { 0x1B => Ok(FwUpdateCmd::GetStatus), 0x1C => Ok(FwUpdateCmd::CancelUpdateComponent), 0x1D => Ok(FwUpdateCmd::CancelUpdate), + 0x03 => Ok(FwUpdateCmd::QueryDownstreamDevices), + 0x04 => Ok(FwUpdateCmd::QueryDownstreamIdentifiers), + 0x05 => Ok(FwUpdateCmd::GetDownstreamFirmwareParameters), + 0x20 => Ok(FwUpdateCmd::RequestDownstreamDeviceUpdate), + 0x11 => Ok(FwUpdateCmd::GetPackageData), + 0x12 => Ok(FwUpdateCmd::GetDeviceMetaData), + 0x19 => Ok(FwUpdateCmd::GetMetaData), _ => Err(PldmError::UnsupportedCmd), } } @@ -78,6 +93,9 @@ pub enum FwUpdateCompletionCode { InvalidTransferOperationFlag = 0x91, ActivatePendingImageNotPermitted = 0x92, PackageDataError = 0x93, + NoOpaqueData = 0x94, + UpdateSecurityRevisionNotPermitted = 0x95, + DownstreamDeviceListChanged = 0x96, } impl TryFrom for FwUpdateCompletionCode { @@ -267,7 +285,7 @@ pub fn get_descriptor_length(descriptor_type: DescriptorType) -> usize { } } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, FromBytes)] #[repr(C)] pub struct Descriptor { pub descriptor_type: u16, @@ -313,9 +331,52 @@ impl Descriptor { pub fn codec_size_in_bytes(&self) -> usize { core::mem::size_of::() * 2 + self.descriptor_length as usize } + + /// Calculates the total length of a list of descriptors from a blob. + /// + /// This simplifies the use case where multiple descriptors are used in a message, + /// and the total length needs to be calculated. + /// It follows the linked list of descriptors by decoding each one in sequence. + /// + /// The blob must start with the first descriptor, but may contain additional data after the last descriptor. + /// + /// ```rust + /// use pldm_lib::protocol::firmware_update::{Descriptor, DescriptorType, get_descriptor_length}; + /// use crate::pldm_lib::codec::PldmCodec; + /// + /// let dsc_0 = Descriptor::new(DescriptorType::PciVendorId, &[0x11, 0x22]).unwrap(); + /// let dsc_1 = Descriptor::new(DescriptorType::PciVendorId, &[0x33, 0x44]).unwrap(); + /// const BLOB_LEN: usize = (size_of::() * 2 + 2 * size_of::()) * 2 as usize; + /// let mut blob = [0u8; BLOB_LEN]; + /// + /// let offset = dsc_0.encode(&mut blob[0..]).unwrap(); + /// let _ = dsc_1.encode(&mut blob[offset..]).unwrap(); + /// + /// let total_len = Descriptor::try_get_descriptor_length_from_blob(&blob, 2).unwrap(); + /// assert_eq!(total_len, dsc_0.codec_size_in_bytes() + dsc_1.codec_size_in_bytes()); + /// ``` + pub fn try_get_descriptor_length_from_blob( + blob: &[u8], + desc_count: usize, + ) -> Result { + let mut offset = 0; + let mut total_len = 0; + + for _ in 0..desc_count { + let descriptor = Descriptor::decode(&blob[offset..])?; + total_len += descriptor.codec_size_in_bytes(); + offset += descriptor.codec_size_in_bytes(); + } + + Ok(total_len) + } } impl PldmCodec for Descriptor { + /// Custom encode implementation of PldmCodec for Descriptor to handle variable-length. + /// + /// The descriptor_data field is variable-length, so we need to ensure that only the valid portion + /// of the array is encoded, not all data of the fixed size array of length [DESCRIPTOR_DATA_MAX_LEN]. fn encode(&self, buffer: &mut [u8]) -> Result { if buffer.len() < self.codec_size_in_bytes() { return Err(PldmCodecError::BufferTooShort); @@ -340,6 +401,10 @@ impl PldmCodec for Descriptor { Ok(offset) } + /// Custom decode implementation of PldmCodec for Descriptor to handle variable-length. + /// + /// The descriptor_data field is variable-length, so we need to ensure that only the valid portion + /// of the array is decoded, not all data of the fixed size array of length [DESCRIPTOR_DATA_MAX_LEN]. fn decode(buffer: &[u8]) -> Result { let mut offset = 0; @@ -375,14 +440,17 @@ impl PldmCodec for Descriptor { } bitfield! { + /// FDPCapabilitiesDuringUpdate + /// + /// DSP0267, Table 20 #[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, PartialEq, Eq, Default)] pub struct FirmwareDeviceCapability(u32); impl Debug; - pub u32, reserved, _: 31, 10; + pub u32, reserved_0, _: 31, 10; pub u32, svn_update_support, set_svn_update_support: 9; pub u32, downgrade_restriction, set_downgrade_restriction: 8; pub u32, update_mode_restriction, set_update_mode_restriction: 7, 4; - pub u32, partial_updates, set_partial_updates: 3; + pub u32, reserved_1, _: 3; pub u32, host_func_reduced, set_func_reduced: 2; pub u32, update_failure_retry, set_update_failure_retry: 1; pub u32, update_failure_recovery, set_update_failure_recovery: 0; @@ -448,7 +516,8 @@ impl TryFrom for ComponentClassification { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, FromBytes, IntoBytes)] +#[repr(C, packed)] pub struct PldmFirmwareString { pub str_type: u8, pub str_len: u8, @@ -495,6 +564,68 @@ impl PldmFirmwareString { } } +/// This implementation is necessary to ensure, that only the valid portion of +/// the str_data array is encoded/decoded, not it's entirety. +/// For that we have to ensure, that [PldmFirmwareString] does not implement all +/// the traits automatically derived by [zerocopy]. +impl PldmCodec for PldmFirmwareString { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.str_len as usize + 2 * size_of::() { + return Err(PldmCodecError::BufferTooShort); + } + let mut offset = 0; + + self.str_type + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.str_len + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.str_data[..self.str_len as usize] + .write_to(&mut buffer[offset..offset + self.str_len as usize]) + .unwrap(); + + Ok(offset + self.str_len as usize) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + + let str_type = u8::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let str_len = u8::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let mut str_data = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + str_data[..str_len as usize].copy_from_slice( + buffer + .get(offset..offset + str_len as usize) + .ok_or(PldmCodecError::BufferTooShort)?, + ); + + Ok(PldmFirmwareString { + str_type, + str_len, + str_data, + }) + } +} + #[derive(Clone)] pub struct PldmFirmwareVersion<'a> { pub comparison_stamp: u32, @@ -843,10 +974,28 @@ mod test { descriptor.descriptor_length, get_descriptor_length(DescriptorType::Uuid) as u16 ); - let mut buffer = [0u8; 512]; + let mut buffer = [0u8; core::mem::size_of::()]; descriptor.encode(&mut buffer).unwrap(); + let decoded_descriptor = Descriptor::decode(&buffer).unwrap(); assert_eq!(descriptor, decoded_descriptor); + + let raw_descriptor = [ + 0xff, 0xff, // Type=VendorDefined + 0x10, 0x00, // size=16 + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, + ]; + let decoded_raw_descriptor = Descriptor::decode(&raw_descriptor).unwrap(); + assert_eq!( + decoded_raw_descriptor.descriptor_type, + DescriptorType::VendorDefined as u16, + ); + assert_eq!(decoded_raw_descriptor.descriptor_length, 16,); + assert_eq!( + &decoded_raw_descriptor.descriptor_data[..16], + &raw_descriptor[4..20] + ); } #[test] @@ -881,4 +1030,40 @@ mod test { assert_eq!(active_firmware_string, active_fw_ver); assert!(active_firmware_string < pending_firmware_string); } + + #[test] + fn test_pldm_firmware_string_codec() { + let test_str = "test"; + let fw_string = PldmFirmwareString::new("ASCII", &test_str).unwrap(); + assert_eq!(fw_string.str_type, VersionStringType::Ascii as u8); + assert_eq!(fw_string.str_len, test_str.len() as u8); + assert_eq!(&fw_string.str_data[..fw_string.str_len as usize], b"test"); + + let mut buffer = [0u8; 2 + 4]; + let bytes = fw_string.encode(&mut buffer).unwrap(); + assert_eq!(bytes, 2 + 4); + + let decoded_fw_string = PldmFirmwareString::decode(&buffer).unwrap(); + assert_eq!(fw_string, decoded_fw_string); + + let empty_fw_string = PldmFirmwareString::new("ASCII", "").unwrap(); + assert_eq!(empty_fw_string.str_type, VersionStringType::Ascii as u8); + assert_eq!(empty_fw_string.str_len, 0); + let mut empty_buffer = [0u8; 2]; + let bytes = empty_fw_string.encode(&mut empty_buffer).unwrap(); + assert_eq!(bytes, 2); + } + + #[test] + #[should_panic] + fn test_pldm_firmware_string_codec_invalid_size() { + let test_str = "test"; + let fw_string = PldmFirmwareString::new("ASCII", &test_str).unwrap(); + assert_eq!(fw_string.str_type, VersionStringType::Ascii as u8); + assert_eq!(fw_string.str_len, test_str.len() as u8); + assert_eq!(&fw_string.str_data[..fw_string.str_len as usize], b"test"); + + let mut buffer = [0u8; 0]; + fw_string.encode(&mut buffer).unwrap(); + } }