From ede7b9495c89b677cfec7ca4095fca8e474f1984 Mon Sep 17 00:00:00 2001 From: CourtneyDrant Date: Wed, 16 Jul 2025 21:52:00 -0700 Subject: [PATCH 1/5] Initial commit of a standalone version of caliptra-mcu common/pldm-lib --- Cargo.lock | 72 ++ Cargo.toml | 9 + src/codec.rs | 56 ++ src/error.rs | 36 + src/lib.rs | 9 + src/message/control.rs | 388 ++++++++ src/message/firmware_update/activate_fw.rs | 98 ++ src/message/firmware_update/apply_complete.rs | 117 +++ src/message/firmware_update/get_fw_params.rs | 362 +++++++ src/message/firmware_update/get_status.rs | 225 +++++ src/message/firmware_update/mod.rs | 14 + src/message/firmware_update/pass_component.rs | 189 ++++ src/message/firmware_update/query_devid.rs | 250 +++++ src/message/firmware_update/request_cancel.rs | 157 ++++ .../firmware_update/request_fw_data.rs | 129 +++ src/message/firmware_update/request_update.rs | 268 ++++++ .../firmware_update/transfer_complete.rs | 124 +++ .../firmware_update/update_component.rs | 338 +++++++ .../firmware_update/verify_complete.rs | 111 +++ src/message/mod.rs | 4 + src/protocol/base.rs | 302 ++++++ src/protocol/firmware_update.rs | 884 ++++++++++++++++++ src/protocol/mod.rs | 5 + src/protocol/version.rs | 250 +++++ src/util/fw_component.rs | 200 ++++ src/util/mctp_transport.rs | 110 +++ src/util/mod.rs | 4 + 27 files changed, 4711 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/codec.rs create mode 100644 src/error.rs create mode 100644 src/lib.rs create mode 100644 src/message/control.rs create mode 100644 src/message/firmware_update/activate_fw.rs create mode 100644 src/message/firmware_update/apply_complete.rs create mode 100644 src/message/firmware_update/get_fw_params.rs create mode 100644 src/message/firmware_update/get_status.rs create mode 100644 src/message/firmware_update/mod.rs create mode 100644 src/message/firmware_update/pass_component.rs create mode 100644 src/message/firmware_update/query_devid.rs create mode 100644 src/message/firmware_update/request_cancel.rs create mode 100644 src/message/firmware_update/request_fw_data.rs create mode 100644 src/message/firmware_update/request_update.rs create mode 100644 src/message/firmware_update/transfer_complete.rs create mode 100644 src/message/firmware_update/update_component.rs create mode 100644 src/message/firmware_update/verify_complete.rs create mode 100644 src/message/mod.rs create mode 100644 src/protocol/base.rs create mode 100644 src/protocol/firmware_update.rs create mode 100644 src/protocol/mod.rs create mode 100644 src/protocol/version.rs create mode 100644 src/util/fw_component.rs create mode 100644 src/util/mctp_transport.rs create mode 100644 src/util/mod.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..75e58c4 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,72 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "pldm-lib" +version = "0.1.0" +dependencies = [ + "bitfield", + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d798a4b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "pldm-lib" +version = "0.1.0" +authors = ["Caliptra contributors", "OpenPRoT contributors"] +edition = "2024" + +[dependencies] +zerocopy = {version = "0.8.17", features = ["derive"]} +bitfield = "0.14.0" diff --git a/src/codec.rs b/src/codec.rs new file mode 100644 index 0000000..15df19f --- /dev/null +++ b/src/codec.rs @@ -0,0 +1,56 @@ +// Licensed under the Apache-2.0 license + +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, PartialEq)] +pub enum PldmCodecError { + BufferTooShort, + Unsupported, +} + +/// A trait for encoding and decoding PLDM (Platform Level Data Model) messages. +/// +/// This trait provides methods for encoding a PLDM message into a byte buffer +/// and decoding a PLDM message from a byte buffer. Implementers of this trait +/// must also implement the `Debug` trait and be `Sized`. +pub trait PldmCodec: 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. + /// + /// # Returns + /// + /// A `Result` containing the decoded message on success, or a `PldmCodecError` on failure. + fn decode(buffer: &[u8]) -> Result; +} + +// Default implementation of PldmCodec for types that can leverage zerocopy. +impl PldmCodec for T +where + T: core::fmt::Debug + Sized + FromBytes + IntoBytes + Immutable, +{ + fn encode(&self, buffer: &mut [u8]) -> Result { + self.write_to_prefix(buffer) + .map_err(|_| PldmCodecError::BufferTooShort) + .map(|_| core::mem::size_of::()) + } + + fn decode(buffer: &[u8]) -> Result { + Ok(Self::read_from_prefix(buffer) + .map_err(|_| PldmCodecError::BufferTooShort)? + .0) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..17dd6a2 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,36 @@ +// Licensed under the Apache-2.0 license + +#[derive(Debug, Clone, PartialEq)] +pub enum PldmError { + InvalidData, + InvalidLength, + InvalidMsgType, + InvalidProtocolVersion, + UnsupportedCmd, + UnsupportedPldmType, + InvalidCompletionCode, + InvalidTransferOpFlag, + InvalidTransferRespFlag, + + InvalidVersionStringType, + InvalidVersionStringLength, + InvalidFdState, + InvalidDescriptorType, + InvalidDescriptorLength, + InvalidDescriptorCount, + InvalidComponentClassification, + InvalidComponentResponseCode, + InvalidComponentCompatibilityResponse, + InvalidComponentCompatibilityResponseCode, + InvalidTransferResult, + InvalidVerifyResult, + InvalidApplyResult, + InvalidGetStatusReasonCode, + InvalidAuxStateStatus, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum UtilError { + InvalidMctpPayloadLength, + InvalidMctpMsgType, +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..660c84c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +// Licensed under the Apache-2.0 license + +#![cfg_attr(target_arch = "riscv32", no_std)] + +pub mod codec; +pub mod error; +pub mod message; +pub mod protocol; +pub mod util; diff --git a/src/message/control.rs b/src/message/control.rs new file mode 100644 index 0000000..e836b08 --- /dev/null +++ b/src/message/control.rs @@ -0,0 +1,388 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmControlCmd, PldmMsgHeader, PldmMsgType, PldmSupportedType, + TransferOperationFlag, TransferRespFlag, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::version::{PldmVersion, ProtocolVersionStr, Ver32}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub const PLDM_CMDS_BITMAP_LEN: usize = 32; +pub const PLDM_TYPES_BITMAP_LEN: usize = 8; + +#[repr(C, packed)] +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +pub struct GetTidRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl GetTidRequest { + pub fn new(instance_id: InstanceId, message_type: PldmMsgType) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + message_type, + PldmSupportedType::Base, + PldmControlCmd::GetTid as u8, + ), + } + } +} + +#[repr(C, packed)] +#[derive(Debug, PartialEq, FromBytes, IntoBytes, Immutable, Clone)] +pub struct GetTidResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub tid: u8, +} + +impl GetTidResponse { + pub fn new(instance_id: InstanceId, tid: u8, completion_code: u8) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::Base, + PldmControlCmd::GetTid as u8, + ), + completion_code, + tid, + } + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +#[repr(C, packed)] +pub struct SetTidRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub tid: u8, +} +impl SetTidRequest { + pub fn new(instance_id: InstanceId, message_type: PldmMsgType, tid: u8) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + message_type, + PldmSupportedType::Base, + PldmControlCmd::SetTid as u8, + ), + tid, + } + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +#[repr(C, packed)] +pub struct SetTidResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, +} + +impl SetTidResponse { + pub fn new(instance_id: InstanceId, ompletion_code: u8) -> Self { + SetTidResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::Base, + PldmControlCmd::SetTid as u8, + ), + completion_code: ompletion_code, + } + } +} + +#[repr(C, packed)] +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +pub struct GetPldmCommandsRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub pldm_type: u8, + pub protocol_version: Ver32, +} + +impl GetPldmCommandsRequest { + pub fn new( + instance_id: InstanceId, + message_type: PldmMsgType, + pldm_type: u8, + version_str: ProtocolVersionStr, + ) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + message_type, + PldmSupportedType::Base, + PldmControlCmd::GetPldmCommands as u8, + ), + pldm_type, + protocol_version: PldmVersion::try_from(version_str) + .unwrap() + .bcd_encode_to_ver32(), + } + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +#[repr(C, packed)] +pub struct GetPldmCommandsResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub supported_cmds: [u8; PLDM_CMDS_BITMAP_LEN], +} + +impl GetPldmCommandsResponse { + pub fn new(instance_id: InstanceId, completion_code: u8, supported_cmds: &[u8]) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::Base, + PldmControlCmd::GetPldmCommands as u8, + ), + completion_code, + supported_cmds: construct_bitmap::(supported_cmds), + } + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +#[repr(C, packed)] +pub struct GetPldmTypeRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl GetPldmTypeRequest { + pub fn new(instance_id: InstanceId, message_type: PldmMsgType) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + message_type, + PldmSupportedType::Base, + PldmControlCmd::GetPldmTypes as u8, + ), + } + } +} + +fn construct_bitmap(items: &[u8]) -> [u8; N] { + let mut bitmap = [0u8; N]; + for &item in items.iter().take(N * 8) { + let byte_index = (item / 8) as usize; + let bit_index = (item % 8) as usize; + bitmap[byte_index] |= 1 << bit_index; + } + bitmap +} + +pub fn is_bit_set(bitmap: &[u8], item: u8) -> bool { + let byte_index = (item / 8) as usize; + let bit_index = (item % 8) as usize; + bitmap[byte_index] & (1 << bit_index) != 0 +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +#[repr(C, packed)] +pub struct GetPldmTypeResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub pldm_types: [u8; PLDM_TYPES_BITMAP_LEN], +} + +impl GetPldmTypeResponse { + pub fn new(instance_id: InstanceId, completion_code: u8, supported_types: &[u8]) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::Base, + PldmControlCmd::GetPldmTypes as u8, + ), + completion_code, + pldm_types: construct_bitmap::(supported_types), + } + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +#[repr(C, packed)] +pub struct GetPldmVersionRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub data_transfer_handle: u32, + pub transfer_op_flag: u8, + pub pldm_type: u8, +} + +impl GetPldmVersionRequest { + pub fn new( + instance_id: InstanceId, + message_type: PldmMsgType, + data_transfer_handle: u32, + transfer_op_flag: TransferOperationFlag, + pldm_type: PldmSupportedType, + ) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + message_type, + PldmSupportedType::Base, + PldmControlCmd::GetPldmVersion as u8, + ), + data_transfer_handle, + transfer_op_flag: transfer_op_flag as u8, + pldm_type: pldm_type as u8, + } + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +#[repr(C, packed)] +pub struct GetPldmVersionResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub next_transfer_handle: u32, // next portion of PLDM version data transfer + pub transfer_rsp_flag: u8, // PLDM GetVersion transfer flag + pub version_data: Ver32, // PLDM GetVersion version field. Support only 1 version field. Version data is version and checksum +} + +impl GetPldmVersionResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + next_transfer_handle: u32, + transfer_rsp_flag: TransferRespFlag, + version_str: ProtocolVersionStr, + ) -> Result { + Ok(Self { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::Base, + PldmControlCmd::GetPldmVersion as u8, + ), + completion_code, + next_transfer_handle, + transfer_rsp_flag: transfer_rsp_flag as u8, + version_data: PldmVersion::try_from(version_str) + .map_err(|_| PldmError::InvalidProtocolVersion)? + .bcd_encode_to_ver32(), + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::codec::{PldmCodec, PldmCodecError}; + + #[test] + fn test_get_tid_request() { + let request = GetTidRequest::new(0x01, PldmMsgType::Request); + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded_request = GetTidRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_get_tid_response() { + let response = GetTidResponse::new(0x01, 42, 0); + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = GetTidResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } + + #[test] + fn test_set_tid_request() { + let request = SetTidRequest::new(0x01, PldmMsgType::Request, 42); + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded_request = SetTidRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_set_tid_response() { + let response = SetTidResponse::new(0x01, 0); + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = SetTidResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } + + #[test] + fn test_get_pldm_commands_request() { + let request = GetPldmCommandsRequest::new(0x01, PldmMsgType::Request, 1, "1.0.0"); + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded_request = GetPldmCommandsRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_get_pldm_commands_response() { + let response = GetPldmCommandsResponse::new(0x01, 0, &[1, 2, 3]); + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = GetPldmCommandsResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } + + #[test] + fn test_get_pldm_type_request() { + let request = GetPldmTypeRequest::new(0x01, PldmMsgType::Request); + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded_request = GetPldmTypeRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_get_pldm_type_response() { + let response = GetPldmTypeResponse::new(0x01, 0, &[0, 5]); + // Check bit map + let mut expected_bitmap = [0u8; PLDM_TYPES_BITMAP_LEN]; + expected_bitmap[0] = 0b00100001; + assert_eq!(response.pldm_types, expected_bitmap); + + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = GetPldmTypeResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } + + #[test] + fn test_get_pldm_version_request() { + let request = GetPldmVersionRequest::new( + 0x01, + PldmMsgType::Request, + 0, + TransferOperationFlag::GetFirstPart, + PldmSupportedType::Base, + ); + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded_request = GetPldmVersionRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_get_pldm_version_response() { + let response = + GetPldmVersionResponse::new(0x01, 0, 0, TransferRespFlag::StartAndEnd, "1.3.0") + .unwrap(); + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = GetPldmVersionResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } + + #[test] + fn test_buffer_too_short() { + let buffer = [0u8; 2]; + let result = GetTidRequest::decode(&buffer); + assert_eq!(result, Err(PldmCodecError::BufferTooShort)); + } +} diff --git a/src/message/firmware_update/activate_fw.rs b/src/message/firmware_update/activate_fw.rs new file mode 100644 index 0000000..b7b083a --- /dev/null +++ b/src/message/firmware_update/activate_fw.rs @@ -0,0 +1,98 @@ +// Licensed under the Apache-2.0 license + +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::FwUpdateCmd; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(u8)] +pub enum SelfContainedActivationRequest { + NotActivateSelfContainedComponents = 0, + ActivateSelfContainedComponents = 1, +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct ActivateFirmwareRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub self_contained_activation_req: u8, +} + +impl ActivateFirmwareRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + self_contained_activation_req: SelfContainedActivationRequest, + ) -> ActivateFirmwareRequest { + ActivateFirmwareRequest { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::ActivateFirmware as u8, + ), + self_contained_activation_req: self_contained_activation_req as u8, + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct ActivateFirmwareResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub estimated_time_activation: u16, +} + +impl ActivateFirmwareResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + estimated_time_activation: u16, + ) -> ActivateFirmwareResponse { + ActivateFirmwareResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::ActivateFirmware as u8, + ), + completion_code, + estimated_time_activation, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_activate_firmware_request() { + let request = ActivateFirmwareRequest::new( + 1, + PldmMsgType::Request, + SelfContainedActivationRequest::ActivateSelfContainedComponents, + ); + + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + + let decoded_request = ActivateFirmwareRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_activate_firmware_response() { + let response = ActivateFirmwareResponse::new(1, 0, 10); + + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + + let decoded_response = ActivateFirmwareResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/src/message/firmware_update/apply_complete.rs b/src/message/firmware_update/apply_complete.rs new file mode 100644 index 0000000..8c6d6b0 --- /dev/null +++ b/src/message/firmware_update/apply_complete.rs @@ -0,0 +1,117 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::{ComponentActivationMethods, FwUpdateCmd}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum ApplyResult { + ApplySuccess = 0x00, + ApplySuccessWithActivationMethod = 0x01, + ApplyFailureMemoryIssue = 0x02, + ApplyTimeOut = 0x09, + #[default] + ApplyGenericError = 0x0a, + VendorDefined, +} + +impl TryFrom for ApplyResult { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(ApplyResult::ApplySuccess), + 0x01 => Ok(ApplyResult::ApplySuccessWithActivationMethod), + 0x02 => Ok(ApplyResult::ApplyFailureMemoryIssue), + 0x09 => Ok(ApplyResult::ApplyTimeOut), + 0x0a => Ok(ApplyResult::ApplyGenericError), + 0xb0..=0xcf => Ok(ApplyResult::VendorDefined), + _ => Err(PldmError::InvalidApplyResult), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct ApplyCompleteRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub apply_result: u8, + pub comp_activation_methods_modification: u16, +} + +impl ApplyCompleteRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + apply_result: ApplyResult, + comp_activation_methods: ComponentActivationMethods, + ) -> Self { + ApplyCompleteRequest { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::ApplyComplete as u8, + ), + apply_result: apply_result as u8, + comp_activation_methods_modification: comp_activation_methods.0, + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct ApplyCompleteResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, +} + +impl ApplyCompleteResponse { + pub fn new(instance_id: InstanceId, completion_code: u8) -> ApplyCompleteResponse { + ApplyCompleteResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::ApplyComplete as u8, + ), + completion_code, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_apply_complete_request() { + let request = ApplyCompleteRequest::new( + 1, + PldmMsgType::Request, + ApplyResult::ApplySuccess, + ComponentActivationMethods(0x0001), + ); + + let mut buffer = [0u8; 64]; + let bytes_written = request.encode(&mut buffer).unwrap(); + assert_eq!(bytes_written, core::mem::size_of::()); + let decoded_request = ApplyCompleteRequest::decode(&buffer[..bytes_written]).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_apply_complete_response() { + let response = ApplyCompleteResponse::new(1, 0); + let mut buffer = [0u8; 64]; + let bytes_written = response.encode(&mut buffer).unwrap(); + assert_eq!(bytes_written, core::mem::size_of::()); + let decoded_response = ApplyCompleteResponse::decode(&buffer[..bytes_written]).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/src/message/firmware_update/get_fw_params.rs b/src/message/firmware_update/get_fw_params.rs new file mode 100644 index 0000000..c18d92d --- /dev/null +++ b/src/message/firmware_update/get_fw_params.rs @@ -0,0 +1,362 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError}; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::{ + ComponentParameterEntry, FirmwareDeviceCapability, FwUpdateCmd, PldmFirmwareString, + MAX_COMPONENT_COUNT, PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN, +}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct GetFirmwareParametersRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl GetFirmwareParametersRequest { + pub fn new(instance_id: InstanceId, message_type: PldmMsgType) -> Self { + GetFirmwareParametersRequest { + hdr: PldmMsgHeader::new( + instance_id, + message_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetFirmwareParameters as u8, + ), + } + } +} + +#[derive(Debug, Clone, PartialEq, FromBytes, IntoBytes, Immutable)] +#[repr(C, packed)] +pub struct FirmwareParamFixed { + pub capabilities_during_update: FirmwareDeviceCapability, + pub comp_count: u16, + pub active_comp_image_set_ver_str_type: u8, + pub active_comp_image_set_ver_str_len: u8, + pub pending_comp_image_set_ver_str_type: u8, + pub pending_comp_image_set_ver_str_len: u8, +} + +impl Default for FirmwareParamFixed { + fn default() -> Self { + FirmwareParamFixed { + capabilities_during_update: FirmwareDeviceCapability(0), + comp_count: 0, + active_comp_image_set_ver_str_type: 0, + active_comp_image_set_ver_str_len: 0, + pending_comp_image_set_ver_str_type: 0, + pending_comp_image_set_ver_str_len: 0, + } + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +#[repr(C)] +pub struct FirmwareParameters { + pub params_fixed: FirmwareParamFixed, + pub active_comp_image_set_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], + pub pending_comp_image_set_ver_str: Option<[u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]>, + pub comp_param_table: [ComponentParameterEntry; MAX_COMPONENT_COUNT], +} + +impl FirmwareParameters { + pub fn new( + capabilities_during_update: FirmwareDeviceCapability, + comp_count: u16, + active_comp_image_set_version: &PldmFirmwareString, + pending_comp_image_set_version: &PldmFirmwareString, + comp_param_table: &[ComponentParameterEntry], + ) -> Self { + FirmwareParameters { + params_fixed: FirmwareParamFixed { + capabilities_during_update, + comp_count, + active_comp_image_set_ver_str_type: active_comp_image_set_version.str_type, + active_comp_image_set_ver_str_len: active_comp_image_set_version.str_len, + pending_comp_image_set_ver_str_type: pending_comp_image_set_version.str_type, + pending_comp_image_set_ver_str_len: pending_comp_image_set_version.str_len, + }, + pending_comp_image_set_ver_str: if pending_comp_image_set_version.str_len > 0 { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = pending_comp_image_set_version.str_data.len(); + arr[..len].copy_from_slice(&pending_comp_image_set_version.str_data); + Some(arr) + } else { + None + }, + active_comp_image_set_ver_str: { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = active_comp_image_set_version.str_data.len(); + arr[..len].copy_from_slice(&active_comp_image_set_version.str_data); + arr + }, + comp_param_table: { + let count = comp_param_table.len().min(MAX_COMPONENT_COUNT); + core::array::from_fn(|i| { + if i < count { + comp_param_table[i].clone() + } else { + ComponentParameterEntry::default() + } + }) + }, + } + } + + fn codec_size_in_bytes(&self) -> usize { + let mut bytes = core::mem::size_of::(); + if self.pending_comp_image_set_ver_str.is_some() { + bytes += self.params_fixed.pending_comp_image_set_ver_str_len as usize; + } + bytes += self.params_fixed.active_comp_image_set_ver_str_len as usize; + + for i in 0..self.params_fixed.comp_count as usize { + bytes += self.comp_param_table[i].codec_size_in_bytes(); + } + bytes + } +} + +impl PldmCodec for FirmwareParameters { + fn encode(&self, buffer: &mut [u8]) -> Result { + let mut offset = 0; + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + self.params_fixed + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + + offset += core::mem::size_of::(); + + let len = self.params_fixed.active_comp_image_set_ver_str_len as usize; + buffer[offset..offset + len].copy_from_slice(&self.active_comp_image_set_ver_str[..len]); + offset += len; + + if let Some(pending_comp_image_set_ver_str) = &self.pending_comp_image_set_ver_str { + let len = self.params_fixed.pending_comp_image_set_ver_str_len as usize; + buffer[offset..offset + len].copy_from_slice(&pending_comp_image_set_ver_str[..len]); + offset += len; + } + + for i in 0..self.params_fixed.comp_count as usize { + let bytes = self.comp_param_table[i].encode(&mut buffer[offset..])?; + offset += bytes; + } + + Ok(offset) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + + let params_fixed = FirmwareParamFixed::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let mut active_comp_image_set_ver_str = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = params_fixed.active_comp_image_set_ver_str_len as usize; + active_comp_image_set_ver_str[..len].copy_from_slice( + buffer + .get(offset..offset + len) + .ok_or(PldmCodecError::BufferTooShort)?, + ); + + offset += len; + + let pending_comp_image_set_ver_str = if params_fixed.pending_comp_image_set_ver_str_len > 0 + { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = params_fixed.pending_comp_image_set_ver_str_len as usize; + arr[..len].copy_from_slice( + buffer + .get(offset..offset + len) + .ok_or(PldmCodecError::BufferTooShort)?, + ); + Some(arr) + } else { + None + }; + offset += params_fixed.pending_comp_image_set_ver_str_len as usize; + + let mut index = 0; + let comp_param_table: [ComponentParameterEntry; MAX_COMPONENT_COUNT] = + core::array::from_fn(|_| { + if index < params_fixed.comp_count as usize { + let comp_param_table_entry = + ComponentParameterEntry::decode(&buffer[offset..]).unwrap(); + offset += comp_param_table_entry.codec_size_in_bytes(); + index += 1; + comp_param_table_entry + } else { + ComponentParameterEntry::default() // Fill remaining slots with default values + } + }); + + Ok(FirmwareParameters { + params_fixed, + active_comp_image_set_ver_str, + pending_comp_image_set_ver_str, + comp_param_table, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +#[repr(C)] +pub struct GetFirmwareParametersResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub parms: FirmwareParameters, +} + +impl GetFirmwareParametersResponse { + pub fn new(instance_id: InstanceId, completion_code: u8, parms: &FirmwareParameters) -> Self { + GetFirmwareParametersResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetFirmwareParameters as u8, + ), + completion_code, + parms: parms.clone(), + } + } + + // Calculate the size of the response in bytes for encoding + pub fn codec_size_in_bytes(&self) -> usize { + let mut bytes = 0; + bytes += + PLDM_MSG_HEADER_LEN + core::mem::size_of::() + self.parms.codec_size_in_bytes(); + bytes + } +} + +impl PldmCodec for GetFirmwareParametersResponse { + fn encode(&self, buffer: &mut [u8]) -> Result { + let mut offset = 0; + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + self.hdr + .write_to(&mut buffer[offset..offset + PLDM_MSG_HEADER_LEN]) + .unwrap(); + offset += PLDM_MSG_HEADER_LEN; + + self.completion_code + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + let bytes = self.parms.encode(&mut buffer[offset..])?; + offset += bytes; + Ok(offset) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + + let hdr = PldmMsgHeader::read_from_bytes( + buffer + .get(offset..offset + PLDM_MSG_HEADER_LEN) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + + offset += PLDM_MSG_HEADER_LEN; + let completion_code = u8::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let parms = FirmwareParameters::decode(&buffer[offset..])?; + Ok(GetFirmwareParametersResponse { + hdr, + completion_code, + parms, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + use crate::protocol::firmware_update::{ + ComponentActivationMethods, ComponentClassification, FirmwareDeviceCapability, + PldmFirmwareString, PldmFirmwareVersion, + }; + + fn construct_firmware_params() -> FirmwareParameters { + // Construct firmware params + let active_firmware_string = PldmFirmwareString::new("ASCII", "mcu-runtime-1.0").unwrap(); + let active_firmware_version = + PldmFirmwareVersion::new(0x12345678, &active_firmware_string, Some("20250210")); + let pending_firmware_string = PldmFirmwareString::new("ASCII", "mcu-runtime-1.5").unwrap(); + let pending_firmware_version = + PldmFirmwareVersion::new(0x87654321, &pending_firmware_string, Some("20250213")); + let comp_activation_methods = ComponentActivationMethods(0x0001); + let capabilities_during_update = FirmwareDeviceCapability(0x0010); + let component_parameter_entry = ComponentParameterEntry::new( + ComponentClassification::Firmware, + 0x0001, + 0x01, + &active_firmware_version, + &pending_firmware_version, + comp_activation_methods, + capabilities_during_update, + ); + + const COMP_COUNT: usize = 8; + let comp_param_table: [ComponentParameterEntry; COMP_COUNT] = + core::array::from_fn(|_| component_parameter_entry.clone()); + FirmwareParameters::new( + capabilities_during_update, + COMP_COUNT as u16, + &active_firmware_string, + &pending_firmware_string, + &comp_param_table, + ) + } + + #[test] + fn test_get_firmware_parameters_request() { + 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] + fn test_get_firmware_parameters() { + 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] + fn test_get_firmware_parameters_response() { + 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); + } +} diff --git a/src/message/firmware_update/get_status.rs b/src/message/firmware_update/get_status.rs new file mode 100644 index 0000000..42c5baa --- /dev/null +++ b/src/message/firmware_update/get_status.rs @@ -0,0 +1,225 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::{FirmwareDeviceState, FwUpdateCmd, UpdateOptionFlags}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub const PROGRESS_PERCENT_NOT_SUPPORTED: u8 = 101; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ProgressPercent(u8); + +impl Default for ProgressPercent { + fn default() -> Self { + ProgressPercent::new(PROGRESS_PERCENT_NOT_SUPPORTED).unwrap() + } +} +impl ProgressPercent { + pub fn new(value: u8) -> Result { + if value > PROGRESS_PERCENT_NOT_SUPPORTED { + Err(PldmError::InvalidData) + } else { + Ok(ProgressPercent(value)) + } + } + + pub fn value(&self) -> u8 { + self.0 + } + + pub fn set_value(&mut self, value: u8) -> Result<(), PldmError> { + if value > 100 { + Err(PldmError::InvalidData) + } else { + self.0 = value; + Ok(()) + } + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AuxState { + OperationInProgress = 0, + OperationSuccessful = 1, + OperationFailed = 2, + IdleLearnComponentsReadXfer = 3, +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AuxStateStatus { + AuxStateInProgressOrSuccess = 0x00, + Reserved, + Timeout = 0x09, + GenericError = 0x0a, + VendorDefined, +} + +impl TryFrom for AuxStateStatus { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(AuxStateStatus::AuxStateInProgressOrSuccess), + 0x01..=0x08 => Ok(AuxStateStatus::Reserved), + 0x09 => Ok(AuxStateStatus::Timeout), + 0x0a => Ok(AuxStateStatus::GenericError), + 0x70..=0xef => Ok(AuxStateStatus::VendorDefined), + _ => Err(PldmError::InvalidAuxStateStatus), + } + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GetStatusReasonCode { + Initialization = 0, + ActivateFw = 1, + CancelUpdate = 2, + LearnComponentTimeout = 3, + ReadyXferTimeout = 4, + DownloadTimeout = 5, + VerifyTimeout = 6, + ApplyTimeout = 7, + VendorDefined, +} + +impl TryFrom for GetStatusReasonCode { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(GetStatusReasonCode::Initialization), + 1 => Ok(GetStatusReasonCode::ActivateFw), + 2 => Ok(GetStatusReasonCode::CancelUpdate), + 3 => Ok(GetStatusReasonCode::LearnComponentTimeout), + 4 => Ok(GetStatusReasonCode::ReadyXferTimeout), + 5 => Ok(GetStatusReasonCode::DownloadTimeout), + 6 => Ok(GetStatusReasonCode::VerifyTimeout), + 7 => Ok(GetStatusReasonCode::ApplyTimeout), + 200..=255 => Ok(GetStatusReasonCode::VendorDefined), + _ => Err(PldmError::InvalidGetStatusReasonCode), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct GetStatusRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl GetStatusRequest { + pub fn new(instance_id: InstanceId, msg_type: PldmMsgType) -> GetStatusRequest { + GetStatusRequest { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetStatus as u8, + ), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +#[repr(u8)] +pub enum UpdateOptionResp { + NoForceUpdate = 0, + ForceUpdate = 1, +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct GetStatusResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub current_state: u8, + pub previous_state: u8, + pub aux_state: u8, + pub aux_state_status: u8, + pub progress_percent: u8, + pub reason_code: u8, + pub update_option_flags_enabled: u32, // Assuming bitfield32_t is a 32-bit integer +} + +#[allow(clippy::too_many_arguments)] +impl GetStatusResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + current_state: FirmwareDeviceState, + previous_state: FirmwareDeviceState, + aux_state: AuxState, + aux_state_status: u8, + progress_percent: ProgressPercent, + reason_code: GetStatusReasonCode, + update_option: UpdateOptionResp, + ) -> GetStatusResponse { + GetStatusResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetStatus as u8, + ), + completion_code, + current_state: current_state as u8, + previous_state: previous_state as u8, + aux_state: aux_state as u8, + aux_state_status, + progress_percent: progress_percent.value(), + reason_code: reason_code as u8, + update_option_flags_enabled: { + let mut flags = UpdateOptionFlags(0); + flags.set_request_force_update(update_option == UpdateOptionResp::ForceUpdate); + flags.0 + }, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_get_status_request() { + let instance_id = 1; + let msg_type = PldmMsgType::Request; + let request = GetStatusRequest::new(instance_id, msg_type); + let mut buffer = [0u8; 16]; + let encoded_size = request.encode(&mut buffer).unwrap(); + assert_eq!(encoded_size, core::mem::size_of::()); + + let decoded_request = GetStatusRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_get_status_response() { + let response = GetStatusResponse::new( + 1, + 0, + FirmwareDeviceState::Idle, + FirmwareDeviceState::Idle, + AuxState::IdleLearnComponentsReadXfer, + AuxStateStatus::AuxStateInProgressOrSuccess as u8, + ProgressPercent::new(50).unwrap(), + GetStatusReasonCode::Initialization, + UpdateOptionResp::NoForceUpdate, + ); + + let mut buffer = [0u8; 32]; + let encoded_size = response.encode(&mut buffer).unwrap(); + assert_eq!(encoded_size, core::mem::size_of::()); + + let decoded_response = GetStatusResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/src/message/firmware_update/mod.rs b/src/message/firmware_update/mod.rs new file mode 100644 index 0000000..859a174 --- /dev/null +++ b/src/message/firmware_update/mod.rs @@ -0,0 +1,14 @@ +// Licensed under the Apache-2.0 license + +pub mod activate_fw; +pub mod apply_complete; +pub mod get_fw_params; +pub mod get_status; +pub mod pass_component; +pub mod query_devid; +pub mod request_cancel; +pub mod request_fw_data; +pub mod request_update; +pub mod transfer_complete; +pub mod update_component; +pub mod verify_complete; diff --git a/src/message/firmware_update/pass_component.rs b/src/message/firmware_update/pass_component.rs new file mode 100644 index 0000000..a8d2b7c --- /dev/null +++ b/src/message/firmware_update/pass_component.rs @@ -0,0 +1,189 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError}; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, TransferRespFlag, + PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::{ + ComponentClassification, ComponentResponse, ComponentResponseCode, FwUpdateCmd, + PldmFirmwareString, PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN, +}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(C)] +pub struct PassComponentTableRequest { + pub fixed: PassComponentTableRequestFixed, + pub comp_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], +} + +#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct PassComponentTableRequestFixed { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub transfer_flag: u8, + pub comp_classification: u16, + pub comp_identifier: u16, + pub comp_classification_index: u8, + pub comp_comparison_stamp: u32, + pub comp_ver_str_type: u8, + pub comp_ver_str_len: u8, +} + +#[allow(clippy::too_many_arguments)] +impl PassComponentTableRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + transfer_flag: TransferRespFlag, + comp_classification: ComponentClassification, + comp_identifier: u16, + comp_classification_index: u8, + comp_comparison_stamp: u32, + comp_version_string: &PldmFirmwareString, + ) -> PassComponentTableRequest { + PassComponentTableRequest { + fixed: PassComponentTableRequestFixed { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::PassComponentTable as u8, + ), + transfer_flag: transfer_flag as u8, + comp_classification: comp_classification as u16, + comp_identifier, + comp_classification_index, + comp_comparison_stamp, + comp_ver_str_type: comp_version_string.str_type, + comp_ver_str_len: comp_version_string.str_len, + }, + comp_ver_str: { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = comp_version_string.str_data.len(); + arr[..len].copy_from_slice(&comp_version_string.str_data); + arr + }, + } + } + + pub fn codec_size_in_bytes(&self) -> usize { + let mut bytes = core::mem::size_of::(); + bytes += self.fixed.comp_ver_str_len as usize; + bytes + } +} + +impl PldmCodec for PassComponentTableRequest { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + self.fixed + .write_to( + &mut buffer + [offset..offset + core::mem::size_of::()], + ) + .unwrap(); + offset += core::mem::size_of::(); + + let str_len = self.fixed.comp_ver_str_len as usize; + buffer[offset..offset + str_len].copy_from_slice(&self.comp_ver_str[..str_len]); + Ok(offset + str_len) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + let fixed = PassComponentTableRequestFixed::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let str_len = fixed.comp_ver_str_len as usize; + let mut comp_ver_str = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + comp_ver_str[..str_len].copy_from_slice( + &buffer + .get(offset..offset + str_len) + .ok_or(PldmCodecError::BufferTooShort)?[..str_len], + ); + + Ok(PassComponentTableRequest { + fixed, + comp_ver_str, + }) + } +} +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq, Default)] +#[repr(C, packed)] +pub struct PassComponentTableResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub comp_resp: u8, + pub comp_resp_code: u8, +} + +impl PassComponentTableResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + comp_resp: ComponentResponse, + comp_resp_code: ComponentResponseCode, + ) -> PassComponentTableResponse { + PassComponentTableResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::PassComponentTable as u8, + ), + completion_code, + comp_resp: comp_resp as u8, + comp_resp_code: comp_resp_code as u8, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pass_component_table_request() { + let request = PassComponentTableRequest::new( + 1, + PldmMsgType::Request, + TransferRespFlag::StartAndEnd, + ComponentClassification::Firmware, + 2, + 3, + 4, + &PldmFirmwareString::new("UTF-8", "bmc-fw-1.2.0").unwrap(), + ); + + let mut buffer = [0u8; 1024]; + let encoded_size = request.encode(&mut buffer).unwrap(); + let decoded_request = PassComponentTableRequest::decode(&buffer[..encoded_size]).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_pass_component_table_response() { + let response = PassComponentTableResponse::new( + 0, + 0, + ComponentResponse::CompCanBeUpdated, + ComponentResponseCode::CompCanBeUpdated, + ); + + let mut buffer = [0u8; 1024]; + let encoded_size = response.encode(&mut buffer).unwrap(); + let decoded_response = PassComponentTableResponse::decode(&buffer[..encoded_size]).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/src/message/firmware_update/query_devid.rs b/src/message/firmware_update/query_devid.rs new file mode 100644 index 0000000..d17a980 --- /dev/null +++ b/src/message/firmware_update/query_devid.rs @@ -0,0 +1,250 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError}; +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::{Descriptor, FwUpdateCmd}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub const ADDITIONAL_DESCRIPTORS_MAX_COUNT: usize = 4; // Arbitrary limit for static storage + +#[derive(Debug, Clone, FromBytes, IntoBytes, PartialEq, Immutable)] +#[repr(C, packed)] +pub struct QueryDeviceIdentifiersRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl QueryDeviceIdentifiersRequest { + pub fn new(instance_id: InstanceId, message_type: PldmMsgType) -> Self { + QueryDeviceIdentifiersRequest { + hdr: PldmMsgHeader::new( + instance_id, + message_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::QueryDeviceIdentifiers as u8, + ), + } + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +#[repr(C)] +pub struct QueryDeviceIdentifiersResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub device_identifiers_len: u32, + pub descriptor_count: u8, + pub initial_descriptor: Descriptor, + pub additional_descriptors: Option<[Descriptor; ADDITIONAL_DESCRIPTORS_MAX_COUNT]>, +} + +impl QueryDeviceIdentifiersResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + initial_descriptor: &Descriptor, + additional_descriptors: Option<&[Descriptor]>, + ) -> Result { + let descriptor_count = + 1 + additional_descriptors.map_or(0, |descriptors| descriptors.len()); + if descriptor_count > ADDITIONAL_DESCRIPTORS_MAX_COUNT + 1 { + return Err(PldmError::InvalidDescriptorCount); + } + + Ok(QueryDeviceIdentifiersResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::QueryDeviceIdentifiers as u8, + ), + completion_code, + device_identifiers_len: { + let mut len = initial_descriptor.codec_size_in_bytes(); + if let Some(additional) = additional_descriptors { + for descriptor in additional.iter() { + len += descriptor.codec_size_in_bytes(); + } + } + len as u32 + }, + descriptor_count: descriptor_count as u8, + initial_descriptor: *initial_descriptor, + additional_descriptors: if descriptor_count > 1 { + if let Some(additional) = additional_descriptors { + let mut descriptors = + [Descriptor::new_empty(); ADDITIONAL_DESCRIPTORS_MAX_COUNT]; + descriptors[..additional.len()].copy_from_slice(additional); + Some(descriptors) + } else { + None + } + } else { + None + }, + }) + } + + pub fn codec_size_in_bytes(&self) -> usize { + let mut size = PLDM_MSG_HEADER_LEN + + core::mem::size_of::() + + core::mem::size_of::() + + core::mem::size_of::(); + size += self.initial_descriptor.codec_size_in_bytes(); + + if let Some(additional_descriptors) = &self.additional_descriptors { + for descriptor in additional_descriptors + .iter() + .take(self.descriptor_count as usize - 1) + { + size += descriptor.codec_size_in_bytes(); + } + } + size + } +} + +impl PldmCodec for QueryDeviceIdentifiersResponse { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + let mut offset = 0; + self.hdr + .write_to(&mut buffer[offset..offset + PLDM_MSG_HEADER_LEN]) + .unwrap(); + offset += PLDM_MSG_HEADER_LEN; + + self.completion_code + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.device_identifiers_len + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.descriptor_count + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + let bytes = self.initial_descriptor.encode(&mut buffer[offset..])?; + offset += bytes; + + if let Some(additional_descriptors) = &self.additional_descriptors { + for descriptor in additional_descriptors + .iter() + .take(self.descriptor_count as usize - 1) + { + let bytes = descriptor.encode(&mut buffer[offset..])?; + offset += bytes; + } + } + Ok(offset) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + + let hdr = PldmMsgHeader::<[u8; PLDM_MSG_HEADER_LEN]>::read_from_bytes( + buffer + .get(offset..offset + PLDM_MSG_HEADER_LEN) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += PLDM_MSG_HEADER_LEN; + + let completion_code = u8::read_from_bytes( + buffer + .get(offset..offset + 1) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += 1; + + let device_identifiers_len = u32::read_from_bytes( + buffer + .get(offset..offset + 4) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += 4; + + let descriptor_count = u8::read_from_bytes( + buffer + .get(offset..offset + 1) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += 1; + + let initial_descriptor = Descriptor::decode(&buffer[offset..])?; + offset += Descriptor::codec_size_in_bytes(&initial_descriptor); + + let additional_descriptors = if descriptor_count > 1 { + let mut descriptors = [Descriptor::new_empty(); ADDITIONAL_DESCRIPTORS_MAX_COUNT]; + let count = descriptor_count as usize - 1; + for descriptor in descriptors.iter_mut().take(count) { + *descriptor = Descriptor::decode(&buffer[offset..])?; + offset += Descriptor::codec_size_in_bytes(descriptor); + } + Some(descriptors) + } else { + None + }; + + Ok(QueryDeviceIdentifiersResponse { + hdr, + completion_code, + device_identifiers_len, + descriptor_count, + initial_descriptor, + additional_descriptors, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::protocol::firmware_update::{Descriptor, DescriptorType}; + + #[test] + fn test_query_device_identifiers_resp() { + let instance_id = 0; + let completion_code = 0; + + let test_uid = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, + ]; + let initial_descriptor = Descriptor::new(DescriptorType::Uuid, &test_uid).unwrap(); + let additional_descriptor = Descriptor::new(DescriptorType::Uuid, &test_uid).unwrap(); + + let resp = QueryDeviceIdentifiersResponse::new( + instance_id, + completion_code, + &initial_descriptor, + Some(&[additional_descriptor]), + ) + .unwrap(); + + assert_eq!(resp.descriptor_count, 2); + assert_eq!( + resp.device_identifiers_len, + (initial_descriptor.codec_size_in_bytes() + additional_descriptor.codec_size_in_bytes()) + as u32 + ); + + let mut buffer: [u8; 256] = [0; 256]; + let resp_len = resp.encode(&mut buffer).unwrap(); + assert_eq!(resp_len, resp.codec_size_in_bytes()); + + let resp_decoded = QueryDeviceIdentifiersResponse::decode(&buffer).unwrap(); + assert_eq!(resp, resp_decoded); + } +} diff --git a/src/message/firmware_update/request_cancel.rs b/src/message/firmware_update/request_cancel.rs new file mode 100644 index 0000000..905698f --- /dev/null +++ b/src/message/firmware_update/request_cancel.rs @@ -0,0 +1,157 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::FwUpdateCmd; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[repr(u8)] +pub enum NonFunctioningComponentIndication { + ComponentsFunctioning = 0, + ComponentsNotFunctioning = 1, +} + +impl TryFrom for NonFunctioningComponentIndication { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(NonFunctioningComponentIndication::ComponentsFunctioning), + 1 => Ok(NonFunctioningComponentIndication::ComponentsNotFunctioning), + _ => Err(PldmError::InvalidData), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable)] +#[repr(C, packed)] +pub struct CancelUpdateComponentRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl CancelUpdateComponentRequest { + pub fn new(instance_id: InstanceId, msg_type: PldmMsgType) -> Self { + CancelUpdateComponentRequest { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::CancelUpdateComponent as u8, + ), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable)] +#[repr(C, packed)] +pub struct CancelUpdateComponentResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, +} + +impl CancelUpdateComponentResponse { + pub fn new(instance_id: InstanceId, completion_code: u8) -> CancelUpdateComponentResponse { + CancelUpdateComponentResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::CancelUpdateComponent as u8, + ), + completion_code, + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct CancelUpdateRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl CancelUpdateRequest { + pub fn new(instance_id: InstanceId, msg_type: PldmMsgType) -> Self { + CancelUpdateRequest { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::CancelUpdate as u8, + ), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable)] +pub struct NonFunctioningComponentBitmap(u64); + +impl NonFunctioningComponentBitmap { + pub fn new(value: u64) -> Self { + NonFunctioningComponentBitmap(value) + } + + pub fn value(&self) -> u64 { + self.0 + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct CancelUpdateResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub non_functioning_component_indication: u8, + pub non_functioning_component_bitmap: u64, +} + +impl CancelUpdateResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + non_functioning_component_indication: NonFunctioningComponentIndication, + non_functioning_component_bitmap: NonFunctioningComponentBitmap, + ) -> CancelUpdateResponse { + CancelUpdateResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::CancelUpdate as u8, + ), + completion_code, + non_functioning_component_indication: non_functioning_component_indication as u8, + non_functioning_component_bitmap: non_functioning_component_bitmap.value(), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_cancel_update_request() { + let cancel_update_request = CancelUpdateRequest::new(0x01, PldmMsgType::Request); + let mut buffer = [0u8; core::mem::size_of::()]; + cancel_update_request.encode(&mut buffer).unwrap(); + let decoded_request = CancelUpdateRequest::decode(&buffer).unwrap(); + assert_eq!(cancel_update_request, decoded_request); + } + + #[test] + fn test_cancel_update_response() { + let response = CancelUpdateResponse::new( + 0x01, + 0x00, + NonFunctioningComponentIndication::ComponentsFunctioning, + NonFunctioningComponentBitmap::new(0x00), + ); + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = CancelUpdateResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/src/message/firmware_update/request_fw_data.rs b/src/message/firmware_update/request_fw_data.rs new file mode 100644 index 0000000..432b124 --- /dev/null +++ b/src/message/firmware_update/request_fw_data.rs @@ -0,0 +1,129 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError}; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::FwUpdateCmd; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub const MAX_TRANSFER_SIZE: usize = 512; // Define an appropriate size + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct RequestFirmwareDataRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub offset: u32, + pub length: u32, +} + +impl RequestFirmwareDataRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + offset: u32, + length: u32, + ) -> RequestFirmwareDataRequest { + let hdr = PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::RequestFirmwareData as u8, + ); + RequestFirmwareDataRequest { + hdr, + offset, + length, + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct RequestFirmwareDataResponseFixed { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, +} + +#[derive(Debug, Clone, PartialEq)] +#[repr(C)] +pub struct RequestFirmwareDataResponse<'a> { + pub fixed: RequestFirmwareDataResponseFixed, + pub data: &'a [u8], +} + +impl RequestFirmwareDataResponse<'_> { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + data: &[u8], + ) -> RequestFirmwareDataResponse { + let fixed = RequestFirmwareDataResponseFixed { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::RequestFirmwareData as u8, + ), + completion_code, + }; + RequestFirmwareDataResponse { fixed, data } + } + + pub fn codec_size_in_bytes(&self) -> usize { + let mut bytes = core::mem::size_of::(); + bytes += self.data.len(); + bytes + } +} + +impl PldmCodec for RequestFirmwareDataResponse<'_> { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + let bytes = core::mem::size_of::(); + self.fixed + .write_to(&mut buffer[offset..offset + bytes]) + .unwrap(); + offset += bytes; + + let data_len = self.data.len(); + if data_len > MAX_TRANSFER_SIZE { + return Err(PldmCodecError::BufferTooShort); + } + buffer[offset..offset + data_len].copy_from_slice(self.data); + Ok(bytes + data_len) + } + + // 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) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_request_firmware_data_request() { + 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() { + 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()); + } +} diff --git a/src/message/firmware_update/request_update.rs b/src/message/firmware_update/request_update.rs new file mode 100644 index 0000000..9c8b154 --- /dev/null +++ b/src/message/firmware_update/request_update.rs @@ -0,0 +1,268 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError}; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::{ + FwUpdateCmd, PldmFirmwareString, PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN, +}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(C)] +pub struct RequestUpdateRequest { + pub fixed: RequestUpdateRequestFixed, + pub comp_image_set_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], +} + +#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct RequestUpdateRequestFixed { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub max_transfer_size: u32, + pub num_of_comp: u16, + pub max_outstanding_transfer_req: u8, + pub pkg_data_len: u16, + pub comp_image_set_ver_str_type: u8, + pub comp_image_set_ver_str_len: u8, +} + +impl RequestUpdateRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + max_transfer_size: u32, + num_of_comp: u16, + max_outstanding_transfer_req: u8, + pkg_data_len: u16, + comp_image_set_version_string: &PldmFirmwareString, + ) -> Self { + RequestUpdateRequest { + fixed: RequestUpdateRequestFixed { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::RequestUpdate as u8, + ), + max_transfer_size, + num_of_comp, + max_outstanding_transfer_req, + pkg_data_len, + comp_image_set_ver_str_type: comp_image_set_version_string.str_type, + comp_image_set_ver_str_len: comp_image_set_version_string.str_len, + }, + comp_image_set_ver_str: { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = comp_image_set_version_string.str_data.len(); + arr[..len].copy_from_slice(&comp_image_set_version_string.str_data); + arr + }, + } + } + + pub fn get_comp_image_set_ver_str(&self) -> PldmFirmwareString { + PldmFirmwareString { + str_type: self.fixed.comp_image_set_ver_str_type, + str_len: self.fixed.comp_image_set_ver_str_len, + str_data: { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + arr.copy_from_slice( + &self.comp_image_set_ver_str[..self.fixed.comp_image_set_ver_str_len as usize], + ); + arr + }, + } + } + + pub fn codec_size_in_bytes(&self) -> usize { + let mut bytes = core::mem::size_of::(); + bytes += self.fixed.comp_image_set_ver_str_len as usize; + bytes + } +} + +impl PldmCodec for RequestUpdateRequest { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + self.fixed + .write_to( + &mut buffer[offset..offset + core::mem::size_of::()], + ) + .unwrap(); + offset += core::mem::size_of::(); + + let str_len = self.fixed.comp_image_set_ver_str_len as usize; + buffer[offset..offset + str_len].copy_from_slice(&self.comp_image_set_ver_str[..str_len]); + Ok(offset + str_len) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + let fixed = RequestUpdateRequestFixed::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let str_len = fixed.comp_image_set_ver_str_len as usize; + let mut comp_image_set_ver_str = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + comp_image_set_ver_str[..str_len].copy_from_slice( + &buffer + .get(offset..offset + str_len) + .ok_or(PldmCodecError::BufferTooShort)?[..str_len], + ); + + Ok(RequestUpdateRequest { + fixed, + comp_image_set_ver_str, + }) + } +} + +#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, PartialEq, Default)] +#[repr(C, packed)] +pub struct RequestUpdateResponseFixed { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub fd_meta_data_len: u16, + pub fd_will_send_pkg_data_cmd: u8, +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct RequestUpdateResponse { + pub fixed: RequestUpdateResponseFixed, + // This field is only present if FDWillSendGetPackageDataCommand is set to 0x02. + pub get_pkg_data_max_transfer_size: Option, +} + +impl RequestUpdateResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + fd_meta_data_len: u16, + fd_will_send_pkg_data_cmd: u8, + get_pkg_data_max_transfer_size: Option, + ) -> RequestUpdateResponse { + RequestUpdateResponse { + fixed: RequestUpdateResponseFixed { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::RequestUpdate as u8, + ), + completion_code, + fd_meta_data_len, + fd_will_send_pkg_data_cmd, + }, + get_pkg_data_max_transfer_size, + } + } + + pub fn codec_size_in_bytes(&self) -> usize { + let mut bytes = core::mem::size_of::(); + if self.fixed.fd_will_send_pkg_data_cmd == 0x02 { + bytes += core::mem::size_of::(); + } + bytes + } +} + +impl PldmCodec for RequestUpdateResponse { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + self.fixed + .write_to( + &mut buffer[offset..offset + core::mem::size_of::()], + ) + .unwrap(); + offset += core::mem::size_of::(); + + if let Some(size) = self.get_pkg_data_max_transfer_size { + size.write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + } + + Ok(offset) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + let fixed = RequestUpdateResponseFixed::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let get_pkg_data_max_transfer_size = if fixed.fd_will_send_pkg_data_cmd == 0x02 { + Some( + u32::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(), + ) + } else { + None + }; + + Ok(RequestUpdateResponse { + fixed, + get_pkg_data_max_transfer_size, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::protocol::firmware_update::PldmFirmwareString; + + #[test] + fn test_request_update_request() { + let request = RequestUpdateRequest::new( + 0, + PldmMsgType::Request, + 512, + 2, + 1, + 256, + &PldmFirmwareString::new("ASCII", "mcu-1.0.0").unwrap(), + ); + + let mut buffer = [0u8; 512]; + let encoded_size = request.encode(&mut buffer).unwrap(); + assert_eq!(encoded_size, request.codec_size_in_bytes()); + + let decoded_request = RequestUpdateRequest::decode(&buffer[..encoded_size]).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_request_update_response() { + let response = RequestUpdateResponse::new(1, 0, 128, 0x02, Some(2048)); + + let mut buffer = [0u8; 512]; + let encoded_size = response.encode(&mut buffer).unwrap(); + assert_eq!(encoded_size, response.codec_size_in_bytes()); + + let decoded_response = RequestUpdateResponse::decode(&buffer[..encoded_size]).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/src/message/firmware_update/transfer_complete.rs b/src/message/firmware_update/transfer_complete.rs new file mode 100644 index 0000000..fae32c8 --- /dev/null +++ b/src/message/firmware_update/transfer_complete.rs @@ -0,0 +1,124 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::FwUpdateCmd; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum TransferResult { + TransferSuccess = 0x00, + TransferErrorImageCorrupt = 0x01, + TransferErrorVersionMismatch = 0x02, + FdAbortedTransfer = 0x03, + TransferTimeOut = 0x09, + #[default] + TransferGenericError = 0x0a, + FdAbortedTransferLowPowerState = 0x0b, + FdAbortedTransferResetNeeded = 0x0c, + FdAbortedTransferStorageIssue = 0x0d, + FdAbortedTransferInvalidComponentOpaqueData = 0x0e, + FdAbortedTransferDownstreamDeviceIssue = 0x0f, + FdAbortedTransferSecurityRevisionError = 0x10, + VendorDefined, // 0x70..=0x8f +} + +impl TryFrom for TransferResult { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(TransferResult::TransferSuccess), + 0x01 => Ok(TransferResult::TransferErrorImageCorrupt), + 0x02 => Ok(TransferResult::TransferErrorVersionMismatch), + 0x03 => Ok(TransferResult::FdAbortedTransfer), + 0x09 => Ok(TransferResult::TransferTimeOut), + 0x0a => Ok(TransferResult::TransferGenericError), + 0x0b => Ok(TransferResult::FdAbortedTransferLowPowerState), + 0x0c => Ok(TransferResult::FdAbortedTransferResetNeeded), + 0x0d => Ok(TransferResult::FdAbortedTransferStorageIssue), + 0x0e => Ok(TransferResult::FdAbortedTransferInvalidComponentOpaqueData), + 0x0f => Ok(TransferResult::FdAbortedTransferDownstreamDeviceIssue), + 0x10 => Ok(TransferResult::FdAbortedTransferSecurityRevisionError), + 0x70..=0x8f => Ok(TransferResult::VendorDefined), + _ => Err(PldmError::InvalidTransferResult), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct TransferCompleteRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub tranfer_result: u8, +} + +impl TransferCompleteRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + tranfer_result: TransferResult, + ) -> Self { + TransferCompleteRequest { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::TransferComplete as u8, + ), + tranfer_result: tranfer_result as u8, + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct TransferCompleteResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, +} + +impl TransferCompleteResponse { + pub fn new(instance_id: InstanceId, completion_code: u8) -> TransferCompleteResponse { + TransferCompleteResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::TransferComplete as u8, + ), + completion_code, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_transfer_complete_request() { + let request = TransferCompleteRequest::new( + 0x01, + PldmMsgType::Request, + TransferResult::TransferSuccess, + ); + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded_request = TransferCompleteRequest::decode(&buffer).unwrap(); + assert_eq!(decoded_request, request); + } + + #[test] + fn test_transfer_complete_response() { + let response = TransferCompleteResponse::new(0x01, 0x00); + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = TransferCompleteResponse::decode(&buffer).unwrap(); + assert_eq!(decoded_response, response); + } +} diff --git a/src/message/firmware_update/update_component.rs b/src/message/firmware_update/update_component.rs new file mode 100644 index 0000000..30f5384 --- /dev/null +++ b/src/message/firmware_update/update_component.rs @@ -0,0 +1,338 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError}; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::{ + ComponentClassification, ComponentCompatibilityResponse, ComponentCompatibilityResponseCode, + FwUpdateCmd, PldmFirmwareString, UpdateOptionFlags, PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN, +}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct UpdateComponentRequestFixed { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub comp_classification: u16, + pub comp_identifier: u16, + pub comp_classification_index: u8, + pub comp_comparison_stamp: u32, + pub comp_image_size: u32, + pub update_option_flags: u32, + pub comp_ver_str_type: u8, + pub comp_ver_str_len: u8, +} + +#[derive(Debug, Clone, PartialEq)] +#[repr(C)] +pub struct UpdateComponentRequest { + pub fixed: UpdateComponentRequestFixed, + pub comp_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], +} + +#[allow(clippy::too_many_arguments)] +impl UpdateComponentRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + comp_classification: ComponentClassification, + comp_identifier: u16, + comp_classification_index: u8, + comp_comparison_stamp: u32, + comp_image_size: u32, + update_option_flags: UpdateOptionFlags, + comp_version_string: &PldmFirmwareString, + ) -> UpdateComponentRequest { + UpdateComponentRequest { + fixed: UpdateComponentRequestFixed { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::UpdateComponent as u8, + ), + comp_classification: comp_classification as u16, + comp_identifier, + comp_classification_index, + comp_comparison_stamp, + comp_image_size, + update_option_flags: update_option_flags.0, + comp_ver_str_type: comp_version_string.str_type, + comp_ver_str_len: comp_version_string.str_len, + }, + comp_ver_str: { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = comp_version_string.str_data.len(); + arr[..len].copy_from_slice(&comp_version_string.str_data); + arr + }, + } + } + + fn codec_size_in_bytes(&self) -> usize { + let mut bytes = 0; + bytes += core::mem::size_of::(); + bytes += self.fixed.comp_ver_str_len as usize; + bytes + } +} + +impl PldmCodec for UpdateComponentRequest { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + let bytes = core::mem::size_of::(); + self.fixed + .write_to(&mut buffer[offset..offset + bytes]) + .unwrap(); + offset += bytes; + + let str_len = self.fixed.comp_ver_str_len as usize; + buffer[offset..offset + str_len].copy_from_slice(&self.comp_ver_str[..str_len]); + Ok(offset + str_len) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + let bytes = core::mem::size_of::(); + let fixed = UpdateComponentRequestFixed::read_from_bytes( + buffer + .get(offset..offset + bytes) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += bytes; + + let comp_ver_str = { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let str_len = fixed.comp_ver_str_len as usize; + arr[..str_len].copy_from_slice(&buffer[offset..offset + str_len]); + arr + }; + Ok(UpdateComponentRequest { + fixed, + comp_ver_str, + }) + } +} + +#[derive(Debug, Clone, PartialEq)] +#[repr(C)] +pub struct UpdateComponentResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub comp_compatibility_resp: u8, + pub comp_compatibility_resp_code: u8, + pub update_option_flags_enabled: u32, + pub time_before_req_fw_data: u16, + pub get_comp_opaque_data_max_transfer_size: Option, +} + +impl UpdateComponentResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + comp_compatibility_resp: ComponentCompatibilityResponse, + comp_compatibility_resp_code: ComponentCompatibilityResponseCode, + update_option_flags_enabled: UpdateOptionFlags, + time_before_req_fw_data: u16, + get_comp_opaque_data_max_transfer_size: Option, + ) -> UpdateComponentResponse { + UpdateComponentResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::UpdateComponent as u8, + ), + completion_code, + comp_compatibility_resp: comp_compatibility_resp as u8, + comp_compatibility_resp_code: comp_compatibility_resp_code as u8, + update_option_flags_enabled: update_option_flags_enabled.0, + time_before_req_fw_data, + get_comp_opaque_data_max_transfer_size, + } + } + + fn codec_size_in_bytes(&self) -> usize { + let mut bytes = PLDM_MSG_HEADER_LEN + + core::mem::size_of::() * 3 + + core::mem::size_of::() + + core::mem::size_of::(); + if self.get_comp_opaque_data_max_transfer_size.is_some() { + bytes += core::mem::size_of::(); + } + bytes + } +} + +impl PldmCodec for UpdateComponentResponse { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + self.hdr + .write_to(&mut buffer[offset..offset + PLDM_MSG_HEADER_LEN]) + .unwrap(); + offset += PLDM_MSG_HEADER_LEN; + + self.completion_code + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.comp_compatibility_resp + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.comp_compatibility_resp_code + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.update_option_flags_enabled + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.time_before_req_fw_data + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + if let Some(size) = self.get_comp_opaque_data_max_transfer_size { + size.write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + } + + Ok(offset) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + let hdr = PldmMsgHeader::read_from_bytes( + buffer + .get(offset..offset + PLDM_MSG_HEADER_LEN) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += PLDM_MSG_HEADER_LEN; + + let completion_code = u8::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let comp_compatibility_resp = u8::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let comp_compatibility_resp_code = u8::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let update_option_flags_enabled = u32::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let time_before_req_fw_data = u16::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let update_option_flags = UpdateOptionFlags(update_option_flags_enabled); + + let get_comp_opaque_data_max_transfer_size = if update_option_flags.component_opaque_data() + { + Some( + u32::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(), + ) + } else { + None + }; + + Ok(UpdateComponentResponse { + hdr, + completion_code, + comp_compatibility_resp, + comp_compatibility_resp_code, + update_option_flags_enabled, + time_before_req_fw_data, + get_comp_opaque_data_max_transfer_size, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_update_component_request() { + let request = UpdateComponentRequest::new( + 0, + PldmMsgType::Request, + ComponentClassification::Firmware, + 0x0001, + 0x01, + 0x00000001, + 0x00000001, + UpdateOptionFlags(0x00000002), + &PldmFirmwareString::new("UTF-8", "mcu-fw-1.2.0").unwrap(), + ); + let mut buffer = [0u8; 512]; + let bytes = request.encode(&mut buffer).unwrap(); + assert_eq!(bytes, request.codec_size_in_bytes()); + let decoded_request = UpdateComponentRequest::decode(&buffer[..bytes]).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_update_component_response() { + let response = UpdateComponentResponse::new( + 0, + 0x00, + ComponentCompatibilityResponse::CompCanBeUpdated, + ComponentCompatibilityResponseCode::NoResponseCode, + UpdateOptionFlags(0x00000002), + 0x0001, + Some(0x00000100), + ); + let mut buffer = [0u8; 512]; + let bytes = response.encode(&mut buffer).unwrap(); + assert_eq!(bytes, response.codec_size_in_bytes()); + let decoded_response = UpdateComponentResponse::decode(&buffer[..bytes]).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/src/message/firmware_update/verify_complete.rs b/src/message/firmware_update/verify_complete.rs new file mode 100644 index 0000000..1e7b0cb --- /dev/null +++ b/src/message/firmware_update/verify_complete.rs @@ -0,0 +1,111 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::FwUpdateCmd; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum VerifyResult { + VerifySuccess = 0x00, + VerifyErrorVerificationFailure = 0x01, + VerifyErrorVersionMismatch = 0x02, + VerifyFailedFdSecurityChecks = 0x03, + VerifyErrorImageIncomplete = 0x04, + VerifyTimeOut = 0x09, + #[default] + VerifyGenericError = 0x0a, + VendorDefined, +} + +impl TryFrom for VerifyResult { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(VerifyResult::VerifySuccess), + 0x01 => Ok(VerifyResult::VerifyErrorVerificationFailure), + 0x02 => Ok(VerifyResult::VerifyErrorVersionMismatch), + 0x03 => Ok(VerifyResult::VerifyFailedFdSecurityChecks), + 0x04 => Ok(VerifyResult::VerifyErrorImageIncomplete), + 0x09 => Ok(VerifyResult::VerifyTimeOut), + 0x0a => Ok(VerifyResult::VerifyGenericError), + 0x90..=0xaf => Ok(VerifyResult::VendorDefined), + _ => Err(PldmError::InvalidVerifyResult), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct VerifyCompleteRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub verify_result: u8, +} + +impl VerifyCompleteRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + verify_result: VerifyResult, + ) -> Self { + VerifyCompleteRequest { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::VerifyComplete as u8, + ), + verify_result: verify_result as u8, + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct VerifyCompleteResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, +} + +impl VerifyCompleteResponse { + pub fn new(instance_id: InstanceId, completion_code: u8) -> VerifyCompleteResponse { + VerifyCompleteResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::VerifyComplete as u8, + ), + completion_code, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_verify_complete_request() { + let request = + VerifyCompleteRequest::new(0x01, PldmMsgType::Request, VerifyResult::VerifySuccess); + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded_request = VerifyCompleteRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_verify_complete_response() { + let response = VerifyCompleteResponse::new(0x01, 0x00); + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = VerifyCompleteResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/src/message/mod.rs b/src/message/mod.rs new file mode 100644 index 0000000..0fbfacb --- /dev/null +++ b/src/message/mod.rs @@ -0,0 +1,4 @@ +// Licensed under the Apache-2.0 license + +pub mod control; +pub mod firmware_update; diff --git a/src/protocol/base.rs b/src/protocol/base.rs new file mode 100644 index 0000000..ccb757a --- /dev/null +++ b/src/protocol/base.rs @@ -0,0 +1,302 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use bitfield::bitfield; +use core::convert::TryFrom; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub const PLDM_MSG_HEADER_LEN: usize = 3; +pub const PLDM_FAILURE_RESP_LEN: usize = 4; +pub type InstanceId = u8; + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum PldmSupportedType { + Base = 0x00, + Platform = 0x02, + Bios = 0x03, + Fru = 0x04, + FwUpdate = 0x05, + Oem = 0x3F, +} + +impl TryFrom for PldmSupportedType { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(PldmSupportedType::Base), + 0x02 => Ok(PldmSupportedType::Platform), + 0x03 => Ok(PldmSupportedType::Bios), + 0x04 => Ok(PldmSupportedType::Fru), + 0x05 => Ok(PldmSupportedType::FwUpdate), + 0x3F => Ok(PldmSupportedType::Oem), + _ => Err(PldmError::UnsupportedPldmType), + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum PldmControlCmd { + SetTid = 0x1, + GetTid = 0x2, + GetPldmVersion = 0x3, + GetPldmTypes = 0x4, + GetPldmCommands = 0x5, +} + +impl TryFrom for PldmControlCmd { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x1 => Ok(PldmControlCmd::SetTid), + 0x2 => Ok(PldmControlCmd::GetTid), + 0x3 => Ok(PldmControlCmd::GetPldmVersion), + 0x4 => Ok(PldmControlCmd::GetPldmTypes), + 0x5 => Ok(PldmControlCmd::GetPldmCommands), + _ => Err(PldmError::UnsupportedCmd), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum PldmMsgType { + Response = 0x00, + Reserved = 0x01, + Request = 0x02, + AsyncRequestNotify = 0x03, +} + +impl TryFrom for PldmMsgType { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(PldmMsgType::Response), + 0x01 => Ok(PldmMsgType::Reserved), + 0x02 => Ok(PldmMsgType::Request), + 0x03 => Ok(PldmMsgType::AsyncRequestNotify), + _ => Err(PldmError::InvalidMsgType), + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum PldmHeaderVersion { + Version0 = 0x00, +} + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum PldmBaseCompletionCode { + Success = 0x00, + Error = 0x01, + InvalidData = 0x02, + InvalidLength = 0x03, + NotReady = 0x04, + UnsupportedPldmCmd = 0x05, + InvalidPldmType = 0x20, +} + +impl TryFrom for PldmBaseCompletionCode { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(PldmBaseCompletionCode::Success), + 0x01 => Ok(PldmBaseCompletionCode::Error), + 0x02 => Ok(PldmBaseCompletionCode::InvalidData), + 0x03 => Ok(PldmBaseCompletionCode::InvalidLength), + 0x04 => Ok(PldmBaseCompletionCode::NotReady), + 0x05 => Ok(PldmBaseCompletionCode::UnsupportedPldmCmd), + 0x20 => Ok(PldmBaseCompletionCode::InvalidPldmType), + _ => Err(PldmError::InvalidCompletionCode), + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum PldmControlCompletionCode { + InvalidDataTransferHandle = 0x80, + InvalidTransferOperationFlag = 0x81, + InvalidPldmTypeInRequestData = 0x83, + InvalidPldmVersionInRequestData = 0x84, +} + +impl TryFrom for PldmControlCompletionCode { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x80 => Ok(PldmControlCompletionCode::InvalidDataTransferHandle), + 0x81 => Ok(PldmControlCompletionCode::InvalidTransferOperationFlag), + 0x83 => Ok(PldmControlCompletionCode::InvalidPldmTypeInRequestData), + 0x84 => Ok(PldmControlCompletionCode::InvalidPldmVersionInRequestData), + _ => Err(PldmError::InvalidCompletionCode), + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum TransferOperationFlag { + GetNextPart = 0, + GetFirstPart = 1, +} + +impl TryFrom for TransferOperationFlag { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(TransferOperationFlag::GetNextPart), + 1 => Ok(TransferOperationFlag::GetFirstPart), + _ => Err(PldmError::InvalidTransferOpFlag), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum TransferRespFlag { + Start = 0x01, + Middle = 0x02, + End = 0x04, + StartAndEnd = 0x05, +} + +impl TryFrom for TransferRespFlag { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x01 => Ok(TransferRespFlag::Start), + 0x02 => Ok(TransferRespFlag::Middle), + 0x04 => Ok(TransferRespFlag::End), + 0x05 => Ok(TransferRespFlag::StartAndEnd), + _ => Err(PldmError::InvalidTransferRespFlag), + } + } +} + +bitfield! { + #[repr(C)] + #[derive(Copy, Clone, FromBytes, IntoBytes, Immutable, PartialEq, Default)] + pub struct PldmMsgHeader([u8]); + impl Debug; + pub u8, instance_id, set_instance_id: 4, 0; + pub u8, reserved, _: 5, 5; + pub u8, datagram, set_datagram: 6, 6; + pub u8, rq, set_rq: 7, 7; + pub u8, pldm_type, set_pldm_type: 13, 8; + pub u8, hdr_ver, set_hdr_ver: 15, 14; + pub u8, cmd_code, set_command_code: 23, 16; +} + +impl PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]> { + const DATAGRAM_MASK: u8 = 0x01; + const REQUEST_MASK: u8 = 0x01 << 1; + + pub fn new( + instance_id: InstanceId, + message_type: PldmMsgType, + pldm_type: PldmSupportedType, + cmd_code: u8, + ) -> Self { + let mut header = PldmMsgHeader([0; PLDM_MSG_HEADER_LEN]); + header.set_instance_id(instance_id); + header.set_datagram(message_type as u8 & Self::DATAGRAM_MASK); + header.set_rq((message_type as u8 & Self::REQUEST_MASK) >> 1); + header.set_pldm_type(pldm_type as u8); + header.set_hdr_ver(PldmHeaderVersion::Version0 as u8); + header.set_command_code(cmd_code); + header + } + + pub fn is_request(&self) -> bool { + self.rq() == (PldmMsgType::Request as u8 >> 0x01) + } + + pub fn is_hdr_ver_valid(&self) -> bool { + self.hdr_ver() == PldmHeaderVersion::Version0 as u8 + } + + // switch the message type to response + pub fn into_response(&self) -> Self { + let mut header = *self; + header.set_rq(PldmMsgType::Response as u8); + header + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct PldmFailureResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, +} + +impl PldmFailureResponse { + pub fn new( + instance_id: InstanceId, + pldm_type: PldmSupportedType, + cmd_code: u8, + completion_code: u8, + ) -> Self { + let hdr = PldmMsgHeader::new(instance_id, PldmMsgType::Response, pldm_type, cmd_code); + PldmFailureResponse { + hdr, + completion_code, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_pldm_msg_header() { + let header = PldmMsgHeader::new( + 0x01, + PldmMsgType::Request, + PldmSupportedType::Base, + PldmControlCmd::GetTid as u8, + ); + assert_eq!(header.0, [0x81, 0x00, 0x02]); + assert!(header.is_request()); + let response = header.into_response(); + assert_eq!(response.0, [0x01, 0x00, 0x02]); + assert_eq!(response.rq(), PldmMsgType::Response as u8); + + let mut buffer = [0; PLDM_MSG_HEADER_LEN]; + let size = header.encode(&mut buffer).unwrap(); + assert_eq!(size, PLDM_MSG_HEADER_LEN); + + let decoded_header = PldmMsgHeader::decode(&buffer).unwrap(); + assert_eq!(header, decoded_header); + } + + #[test] + fn test_pldm_failure_resp() { + let resp = PldmFailureResponse::new( + 0x01, + PldmSupportedType::Base, + PldmControlCmd::GetTid as u8, + PldmBaseCompletionCode::Success as u8, + ); + + let mut buffer = [0; PLDM_FAILURE_RESP_LEN]; + let size = resp.encode(&mut buffer).unwrap(); + assert_eq!(size, PLDM_FAILURE_RESP_LEN); + + let decoded_resp = PldmFailureResponse::decode(&buffer).unwrap(); + assert_eq!(resp, decoded_resp); + } +} diff --git a/src/protocol/firmware_update.rs b/src/protocol/firmware_update.rs new file mode 100644 index 0000000..f959f50 --- /dev/null +++ b/src/protocol/firmware_update.rs @@ -0,0 +1,884 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError}; +use crate::error::PldmError; +use bitfield::bitfield; +use core::convert::TryFrom; +use core::fmt; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub const PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN: usize = 8; +pub const PLDM_FWUP_BASELINE_TRANSFER_SIZE: usize = 32; +pub const PLDM_FWUP_MAX_PADDING_SIZE: usize = PLDM_FWUP_BASELINE_TRANSFER_SIZE; +pub const PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN: usize = 32; +pub const DESCRIPTOR_DATA_MAX_LEN: usize = 64; // Arbitrary limit for static storage +pub const MAX_COMPONENT_COUNT: usize = 8; // Arbitrary limit, change as needed +pub const MAX_DESCRIPTORS_COUNT: usize = 4; // Arbitrary limit, change as needed +pub type PldmFdTime = u64; // Monotonic timestamp in milliseconds + +#[repr(u8)] +pub enum FwUpdateCmd { + QueryDeviceIdentifiers = 0x01, + GetFirmwareParameters = 0x02, + RequestUpdate = 0x10, + PassComponentTable = 0x13, + UpdateComponent = 0x14, + RequestFirmwareData = 0x15, + TransferComplete = 0x16, + VerifyComplete = 0x17, + ApplyComplete = 0x18, + ActivateFirmware = 0x1A, + GetStatus = 0x1B, + CancelUpdateComponent = 0x1C, + CancelUpdate = 0x1D, +} + +impl TryFrom for FwUpdateCmd { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x01 => Ok(FwUpdateCmd::QueryDeviceIdentifiers), + 0x02 => Ok(FwUpdateCmd::GetFirmwareParameters), + 0x10 => Ok(FwUpdateCmd::RequestUpdate), + 0x13 => Ok(FwUpdateCmd::PassComponentTable), + 0x14 => Ok(FwUpdateCmd::UpdateComponent), + 0x15 => Ok(FwUpdateCmd::RequestFirmwareData), + 0x16 => Ok(FwUpdateCmd::TransferComplete), + 0x17 => Ok(FwUpdateCmd::VerifyComplete), + 0x18 => Ok(FwUpdateCmd::ApplyComplete), + 0x1A => Ok(FwUpdateCmd::ActivateFirmware), + 0x1B => Ok(FwUpdateCmd::GetStatus), + 0x1C => Ok(FwUpdateCmd::CancelUpdateComponent), + 0x1D => Ok(FwUpdateCmd::CancelUpdate), + _ => Err(PldmError::UnsupportedCmd), + } + } +} + +#[repr(u8)] +pub enum FwUpdateCompletionCode { + NotInUpdateMode = 0x80, + AlreadyInUpdateMode = 0x81, + DataOutOfRange = 0x82, + InvalidTransferLength = 0x83, + InvalidStateForCommand = 0x84, + IncompleteUpdate = 0x85, + BusyInBackground = 0x86, + CancelPending = 0x87, + CommandNotExpected = 0x88, + RetryRequestFwData = 0x89, + UnableToInitiateUpdate = 0x8A, + ActivationNotRequired = 0x8B, + SelfContainedActivationNotPermitted = 0x8C, + NoDeviceMetadata = 0x8D, + RetryRequestUpdate = 0x8E, + NoPackageData = 0x8F, + InvalidTransferHandle = 0x90, + InvalidTransferOperationFlag = 0x91, + ActivatePendingImageNotPermitted = 0x92, + PackageDataError = 0x93, +} + +impl TryFrom for FwUpdateCompletionCode { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x80 => Ok(FwUpdateCompletionCode::NotInUpdateMode), + 0x81 => Ok(FwUpdateCompletionCode::AlreadyInUpdateMode), + 0x82 => Ok(FwUpdateCompletionCode::DataOutOfRange), + 0x83 => Ok(FwUpdateCompletionCode::InvalidTransferLength), + 0x84 => Ok(FwUpdateCompletionCode::InvalidStateForCommand), + 0x85 => Ok(FwUpdateCompletionCode::IncompleteUpdate), + 0x86 => Ok(FwUpdateCompletionCode::BusyInBackground), + 0x87 => Ok(FwUpdateCompletionCode::CancelPending), + 0x88 => Ok(FwUpdateCompletionCode::CommandNotExpected), + 0x89 => Ok(FwUpdateCompletionCode::RetryRequestFwData), + 0x8A => Ok(FwUpdateCompletionCode::UnableToInitiateUpdate), + 0x8B => Ok(FwUpdateCompletionCode::ActivationNotRequired), + 0x8C => Ok(FwUpdateCompletionCode::SelfContainedActivationNotPermitted), + 0x8D => Ok(FwUpdateCompletionCode::NoDeviceMetadata), + 0x8E => Ok(FwUpdateCompletionCode::RetryRequestUpdate), + 0x8F => Ok(FwUpdateCompletionCode::NoPackageData), + 0x90 => Ok(FwUpdateCompletionCode::InvalidTransferHandle), + 0x91 => Ok(FwUpdateCompletionCode::InvalidTransferOperationFlag), + 0x92 => Ok(FwUpdateCompletionCode::ActivatePendingImageNotPermitted), + 0x93 => Ok(FwUpdateCompletionCode::PackageDataError), + _ => Err(PldmError::InvalidCompletionCode), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +#[repr(u8)] +pub enum FirmwareDeviceState { + Idle = 0, + LearnComponents = 1, + ReadyXfer = 2, + Download = 3, + Verify = 4, + Apply = 5, + Activate = 6, +} + +impl TryFrom for FirmwareDeviceState { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(FirmwareDeviceState::Idle), + 1 => Ok(FirmwareDeviceState::LearnComponents), + 2 => Ok(FirmwareDeviceState::ReadyXfer), + 3 => Ok(FirmwareDeviceState::Download), + 4 => Ok(FirmwareDeviceState::Verify), + 5 => Ok(FirmwareDeviceState::Apply), + 6 => Ok(FirmwareDeviceState::Activate), + _ => Err(PldmError::InvalidFdState), + } + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, FromBytes, IntoBytes)] + pub struct UpdateOptionFlags(u32); + impl Debug; + pub u32, reserved, _: 31, 3; + pub u32, svn_delayed_update, set_svn_delayed_update: 2; + pub u32, component_opaque_data, set_component_opaque_data: 1; + pub u32, request_force_update, set_request_force_update: 0; +} + +#[repr(u8)] +pub enum VersionStringType { + Unspecified = 0, + Ascii = 1, + Utf8 = 2, + Utf16 = 3, + Utf16Le = 4, + Utf16Be = 5, +} + +impl VersionStringType { + fn as_string(&self) -> &str { + match *self { + VersionStringType::Ascii => "ASCII", + VersionStringType::Utf8 => "UTF-8", + VersionStringType::Utf16 => "UTF-16", + VersionStringType::Utf16Le => "UTF-16LE", + VersionStringType::Utf16Be => "UTF-16BE", + VersionStringType::Unspecified => "UNSPECIFIED", + } + } +} + +impl fmt::Display for VersionStringType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_string()) + } +} +impl TryFrom<&str> for VersionStringType { + type Error = PldmError; + + fn try_from(input: &str) -> Result { + match input { + "ASCII" | "ascii" => Ok(VersionStringType::Ascii), + "UTF-8" | "utf-8" => Ok(VersionStringType::Utf8), + "UTF-16" | "utf-16" => Ok(VersionStringType::Utf16), + "UTF-16LE" | "utf-16le" => Ok(VersionStringType::Utf16Le), + "UTF-16BE" | "utf-16be" => Ok(VersionStringType::Utf16Be), + "UNSPECIFIED" | "unspecified" => Ok(VersionStringType::Unspecified), + _ => Err(PldmError::InvalidVersionStringType), + } + } +} + +#[derive(Clone, Copy, PartialEq)] +#[repr(u16)] +pub enum DescriptorType { + PciVendorId = 0x0000, + IanaEnterpriseId = 0x0001, + Uuid = 0x0002, + PnpVendorId = 0x0003, + AcpiVendorId = 0x0004, + IeeeAssignedCompanyId = 0x0005, + ScsiVendorId = 0x0006, + PciDeviceId = 0x0100, + PciSubsystemVendorId = 0x0101, + PciSubsystemId = 0x0102, + PciRevisionId = 0x0103, + PnpProductIdentifier = 0x0104, + AcpiProductIdentifier = 0x0105, + AsciiModelNumberLongString = 0x0106, + AsciiModelNumberShortString = 0x0107, + ScsiProductId = 0x0108, + UbmControllerDeviceCode = 0x0109, + VendorDefined = 0xFFFF, +} + +impl TryFrom for DescriptorType { + type Error = PldmError; + + fn try_from(value: u16) -> Result { + match value { + 0x0000 => Ok(DescriptorType::PciVendorId), + 0x0001 => Ok(DescriptorType::IanaEnterpriseId), + 0x0002 => Ok(DescriptorType::Uuid), + 0x0003 => Ok(DescriptorType::PnpVendorId), + 0x0004 => Ok(DescriptorType::AcpiVendorId), + 0x0005 => Ok(DescriptorType::IeeeAssignedCompanyId), + 0x0006 => Ok(DescriptorType::ScsiVendorId), + 0x0100 => Ok(DescriptorType::PciDeviceId), + 0x0101 => Ok(DescriptorType::PciSubsystemVendorId), + 0x0102 => Ok(DescriptorType::PciSubsystemId), + 0x0103 => Ok(DescriptorType::PciRevisionId), + 0x0104 => Ok(DescriptorType::PnpProductIdentifier), + 0x0105 => Ok(DescriptorType::AcpiProductIdentifier), + 0x0106 => Ok(DescriptorType::AsciiModelNumberLongString), + 0x0107 => Ok(DescriptorType::AsciiModelNumberShortString), + 0x0108 => Ok(DescriptorType::ScsiProductId), + 0x0109 => Ok(DescriptorType::UbmControllerDeviceCode), + 0xFFFF => Ok(DescriptorType::VendorDefined), + _ => Err(PldmError::InvalidDescriptorType), + } + } +} + +pub fn get_descriptor_length(descriptor_type: DescriptorType) -> usize { + match &descriptor_type { + DescriptorType::PciVendorId => 2, + DescriptorType::IanaEnterpriseId => 4, + DescriptorType::Uuid => 16, + DescriptorType::PnpVendorId => 3, + DescriptorType::AcpiVendorId => 5, + DescriptorType::IeeeAssignedCompanyId => 3, + DescriptorType::ScsiVendorId => 8, + DescriptorType::PciDeviceId => 2, + DescriptorType::PciSubsystemVendorId => 2, + DescriptorType::PciSubsystemId => 2, + DescriptorType::PciRevisionId => 1, + DescriptorType::PnpProductIdentifier => 4, + DescriptorType::AcpiProductIdentifier => 4, + DescriptorType::AsciiModelNumberLongString => 40, + DescriptorType::AsciiModelNumberShortString => 10, + DescriptorType::ScsiProductId => 16, + DescriptorType::UbmControllerDeviceCode => 4, + DescriptorType::VendorDefined => DESCRIPTOR_DATA_MAX_LEN, + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(C)] +pub struct Descriptor { + pub descriptor_type: u16, + pub descriptor_length: u16, + pub descriptor_data: [u8; DESCRIPTOR_DATA_MAX_LEN], +} + +impl Default for Descriptor { + fn default() -> Self { + Descriptor { + descriptor_type: 0, + descriptor_length: 0, + descriptor_data: [0; DESCRIPTOR_DATA_MAX_LEN], + } + } +} + +impl Descriptor { + pub fn new_empty() -> Self { + Descriptor { + descriptor_type: 0, + descriptor_length: 0, + descriptor_data: [0; DESCRIPTOR_DATA_MAX_LEN], + } + } + + pub fn new(descriptor_type: DescriptorType, descriptor_data: &[u8]) -> Result { + let descriptor_length = get_descriptor_length(descriptor_type); + if descriptor_data.len() != descriptor_length { + return Err(PldmError::InvalidDescriptorLength); + } + + let mut descriptor_data_array = [0u8; DESCRIPTOR_DATA_MAX_LEN]; + descriptor_data_array[..descriptor_length].copy_from_slice(descriptor_data); + + Ok(Descriptor { + descriptor_type: descriptor_type as u16, + descriptor_length: descriptor_length as u16, + descriptor_data: descriptor_data_array, + }) + } + + pub fn codec_size_in_bytes(&self) -> usize { + core::mem::size_of::() * 2 + self.descriptor_length as usize + } +} + +impl PldmCodec for Descriptor { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + let mut offset = 0; + + self.descriptor_type + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.descriptor_length + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.descriptor_data[..self.descriptor_length as usize] + .write_to(&mut buffer[offset..offset + self.descriptor_length as usize]) + .unwrap(); + offset += self.descriptor_length as usize; + + Ok(offset) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + + let descriptor_type = u16::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let descriptor_length = u16::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let mut descriptor_data = [0u8; DESCRIPTOR_DATA_MAX_LEN]; + descriptor_data[..descriptor_length as usize].copy_from_slice( + buffer + .get(offset..offset + descriptor_length as usize) + .ok_or(PldmCodecError::BufferTooShort)?, + ); + + Ok(Descriptor { + descriptor_type, + descriptor_length, + descriptor_data, + }) + } +} + +bitfield! { + #[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, PartialEq, Eq, Default)] + pub struct FirmwareDeviceCapability(u32); + impl Debug; + pub u32, reserved, _: 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, 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; +} + +bitfield! { + #[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, PartialEq, Eq)] + pub struct ComponentActivationMethods(u16); + impl Debug; + pub u16, reserved, _: 15, 8; + pub u16, activate_pending_comp_image_set, set_activate_pending_comp_image_set: 7; + pub u16, activate_pending_image, set_activate_pending_image: 6; + pub u16, ac_power_cycle, set_ac_power_cycle: 5; + pub u16, dc_power_cycle, set_dc_power_cycle: 4; + pub u16, system_reboot, set_system_reboot: 3; + pub u16, medium_specific_reset, set_medium_specific_reset: 2; + pub u16, self_contained, set_self_contained: 1; + pub u16, automatic, set_automatic: 0; +} + +#[repr(u16)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ComponentClassification { + Unspecified = 0x0000, + Other = 0x0001, + Driver = 0x0002, + ConfigurationSoftware = 0x0003, + ApplicationSoftware = 0x0004, + Instrumentation = 0x0005, + FirmwareOrBios = 0x0006, + DiagnosticSoftware = 0x0007, + OperatingSystem = 0x0008, + Middleware = 0x0009, + Firmware = 0x000A, + BiosOrFcode = 0x000B, + SupportOrServicePack = 0x000C, + SoftwareBundle = 0x000D, + DownstreamDevice = 0xFFFF, +} + +impl TryFrom for ComponentClassification { + type Error = PldmError; + + fn try_from(value: u16) -> Result { + match value { + 0x0000 => Ok(ComponentClassification::Unspecified), + 0x0001 => Ok(ComponentClassification::Other), + 0x0002 => Ok(ComponentClassification::Driver), + 0x0003 => Ok(ComponentClassification::ConfigurationSoftware), + 0x0004 => Ok(ComponentClassification::ApplicationSoftware), + 0x0005 => Ok(ComponentClassification::Instrumentation), + 0x0006 => Ok(ComponentClassification::FirmwareOrBios), + 0x0007 => Ok(ComponentClassification::DiagnosticSoftware), + 0x0008 => Ok(ComponentClassification::OperatingSystem), + 0x0009 => Ok(ComponentClassification::Middleware), + 0x000A => Ok(ComponentClassification::Firmware), + 0x000B => Ok(ComponentClassification::BiosOrFcode), + 0x000C => Ok(ComponentClassification::SupportOrServicePack), + 0x000D => Ok(ComponentClassification::SoftwareBundle), + 0xFFFF => Ok(ComponentClassification::DownstreamDevice), + _ => Err(PldmError::InvalidComponentClassification), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct PldmFirmwareString { + pub str_type: u8, + pub str_len: u8, + pub str_data: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], +} + +impl Default for PldmFirmwareString { + fn default() -> Self { + PldmFirmwareString { + str_type: 0, + str_len: 0, + str_data: [0; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], + } + } +} + +impl PartialOrd for PldmFirmwareString { + fn partial_cmp(&self, other: &Self) -> Option { + if self.str_type != other.str_type { + return None; + } + let self_str = &self.str_data[..self.str_len as usize]; + let other_str = &other.str_data[..other.str_len as usize]; + Some(self_str.cmp(other_str)) + } +} + +impl PldmFirmwareString { + pub fn new(str_type: &str, fw_str: &str) -> Result { + let str_type = VersionStringType::try_from(str_type)?; + + if fw_str.len() > PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN { + return Err(PldmError::InvalidVersionStringLength); + } + + let mut str_data = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + str_data[..fw_str.len()].copy_from_slice(fw_str.as_bytes()); + + Ok(PldmFirmwareString { + str_type: str_type as u8, + str_len: fw_str.len() as u8, + str_data, + }) + } +} + +#[derive(Clone)] +pub struct PldmFirmwareVersion<'a> { + pub comparison_stamp: u32, + pub str: &'a PldmFirmwareString, + pub date: [u8; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], +} + +impl Default for PldmFirmwareVersion<'_> { + fn default() -> Self { + static DEFAULT_FW_STRING: PldmFirmwareString = PldmFirmwareString { + str_type: 0, + str_len: 0, + str_data: [0; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], + }; + PldmFirmwareVersion { + comparison_stamp: 0, + str: &DEFAULT_FW_STRING, + date: [0; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], + } + } +} + +impl<'a> PldmFirmwareVersion<'a> { + pub fn new(comparison_stamp: u32, str: &'a PldmFirmwareString, date_str: Option<&str>) -> Self { + let mut date_array = [0u8; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN]; + if let Some(date_str) = date_str { + let date_bytes = date_str.as_bytes(); + let len = date_bytes.len().min(PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN); + date_array[..len].copy_from_slice(&date_bytes[..len]); + } + PldmFirmwareVersion { + comparison_stamp, + str, + date: date_array, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, FromBytes, IntoBytes, Immutable, Copy)] +#[repr(C, packed)] +pub struct ComponentParameterEntryFixed { + pub comp_classification: u16, + pub comp_identifier: u16, + pub comp_classification_index: u8, + pub active_comp_comparison_stamp: u32, + pub active_comp_ver_str_type: u8, + pub active_comp_ver_str_len: u8, + pub active_comp_release_date: [u8; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], + pub pending_comp_comparison_stamp: u32, + pub pending_comp_ver_str_type: u8, + pub pending_comp_ver_str_len: u8, + pub pending_comp_release_date: [u8; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], + pub comp_activation_methods: ComponentActivationMethods, + pub capabilities_during_update: FirmwareDeviceCapability, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[repr(C)] +pub struct ComponentParameterEntry { + pub comp_param_entry_fixed: ComponentParameterEntryFixed, + pub active_comp_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], + pub pending_comp_ver_str: Option<[u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]>, +} + +impl Default for ComponentParameterEntry { + fn default() -> Self { + ComponentParameterEntry { + comp_param_entry_fixed: ComponentParameterEntryFixed { + comp_classification: 0, + comp_identifier: 0, + comp_classification_index: 0, + active_comp_comparison_stamp: 0, + active_comp_ver_str_type: 0, + active_comp_ver_str_len: 0, + active_comp_release_date: [0; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], + pending_comp_comparison_stamp: 0, + pending_comp_ver_str_type: 0, + pending_comp_ver_str_len: 0, + pending_comp_release_date: [0; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], + comp_activation_methods: ComponentActivationMethods(0), + capabilities_during_update: FirmwareDeviceCapability(0), + }, + active_comp_ver_str: [0; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], + pending_comp_ver_str: None, + } + } +} + +impl ComponentParameterEntry { + pub fn new( + comp_classification: ComponentClassification, + comp_identifier: u16, + comp_classification_index: u8, + active_firmware_version: &PldmFirmwareVersion, + pending_firmware_version: &PldmFirmwareVersion, + comp_activation_methods: ComponentActivationMethods, + capabilities_during_update: FirmwareDeviceCapability, + ) -> Self { + ComponentParameterEntry { + comp_param_entry_fixed: ComponentParameterEntryFixed { + comp_classification: comp_classification as u16, + comp_identifier, + comp_classification_index, + active_comp_comparison_stamp: active_firmware_version.comparison_stamp, + active_comp_ver_str_type: active_firmware_version.str.str_type, + active_comp_ver_str_len: active_firmware_version.str.str_len, + active_comp_release_date: active_firmware_version.date, + pending_comp_comparison_stamp: pending_firmware_version.comparison_stamp, + pending_comp_ver_str_type: pending_firmware_version.str.str_type, + pending_comp_ver_str_len: pending_firmware_version.str.str_len, + pending_comp_release_date: pending_firmware_version.date, + comp_activation_methods, + capabilities_during_update, + }, + active_comp_ver_str: { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = active_firmware_version.str.str_len as usize; + arr[..len].copy_from_slice(&active_firmware_version.str.str_data[..len]); + arr + }, + pending_comp_ver_str: { + if pending_firmware_version.str.str_len > 0 { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = pending_firmware_version.str.str_len as usize; + arr[..len].copy_from_slice(&pending_firmware_version.str.str_data[..len]); + Some(arr) + } else { + None + } + }, + } + } + + pub fn codec_size_in_bytes(&self) -> usize { + let mut bytes = 0; + bytes += core::mem::size_of::(); + bytes += self.comp_param_entry_fixed.active_comp_ver_str_len as usize; + if self.pending_comp_ver_str.is_some() { + bytes += self.comp_param_entry_fixed.pending_comp_ver_str_len as usize; + } + bytes + } + + pub fn get_active_fw_ver(&self) -> PldmFirmwareString { + PldmFirmwareString { + str_type: self.comp_param_entry_fixed.active_comp_ver_str_type, + str_len: self.comp_param_entry_fixed.active_comp_ver_str_len, + str_data: self.active_comp_ver_str, + } + } +} + +impl PldmCodec for ComponentParameterEntry { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + let mut offset = 0; + + self.comp_param_entry_fixed + .write_to( + &mut buffer[offset..offset + core::mem::size_of::()], + ) + .unwrap(); + + offset += core::mem::size_of::(); + + let len = self.comp_param_entry_fixed.active_comp_ver_str_len as usize; + self.active_comp_ver_str[..len] + .write_to(&mut buffer[offset..offset + len]) + .unwrap(); + offset += len; + + if let Some(pending_comp_ver_str) = &self.pending_comp_ver_str { + let len = self.comp_param_entry_fixed.pending_comp_ver_str_len as usize; + pending_comp_ver_str[..len] + .write_to(&mut buffer[offset..offset + len]) + .unwrap(); + offset += len; + } + + Ok(offset) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + + let comp_param_entry_fixed = ComponentParameterEntryFixed::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + + offset += core::mem::size_of::(); + + let active_comp_ver_str_len = comp_param_entry_fixed.active_comp_ver_str_len as usize; + let mut active_comp_ver_str = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + active_comp_ver_str[..active_comp_ver_str_len].copy_from_slice( + buffer + .get(offset..offset + active_comp_ver_str_len) + .ok_or(PldmCodecError::BufferTooShort)?, + ); + offset += active_comp_ver_str_len; + + let pending_comp_ver_str = if comp_param_entry_fixed.pending_comp_ver_str_len > 0 { + let len = comp_param_entry_fixed.pending_comp_ver_str_len as usize; + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + arr[..len].copy_from_slice( + buffer + .get(offset..offset + len) + .ok_or(PldmCodecError::BufferTooShort)?, + ); + Some(arr) + } else { + None + }; + + Ok(ComponentParameterEntry { + comp_param_entry_fixed, + active_comp_ver_str, + pending_comp_ver_str, + }) + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +pub enum ComponentResponse { + CompCanBeUpdated, + CompCannotBeUpdated, +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ComponentResponseCode { + CompCanBeUpdated = 0x00, + CompComparisonStampIdentical = 0x01, + CompComparisonStampLower = 0x02, + InvalidCompComparisonStamp = 0x03, + CompConflict = 0x04, + CompPrerequisitesNotMet = 0x05, + CompNotSupported = 0x06, + CompSecurityRestrictions = 0x07, + IncompleteCompImageSet = 0x08, + ActiveImageNotUpdateableSubsequently = 0x09, + CompVerStrIdentical = 0x0a, + CompVerStrLower = 0x0b, + VendorDefined, // 0xd0..=0xef +} + +impl TryFrom for ComponentResponseCode { + type Error = PldmError; + + fn try_from(val: u8) -> Result { + match val { + 0x00 => Ok(ComponentResponseCode::CompCanBeUpdated), + 0x01 => Ok(ComponentResponseCode::CompComparisonStampIdentical), + 0x02 => Ok(ComponentResponseCode::CompComparisonStampLower), + 0x03 => Ok(ComponentResponseCode::InvalidCompComparisonStamp), + 0x04 => Ok(ComponentResponseCode::CompConflict), + 0x05 => Ok(ComponentResponseCode::CompPrerequisitesNotMet), + 0x06 => Ok(ComponentResponseCode::CompNotSupported), + 0x07 => Ok(ComponentResponseCode::CompSecurityRestrictions), + 0x08 => Ok(ComponentResponseCode::IncompleteCompImageSet), + 0x09 => Ok(ComponentResponseCode::ActiveImageNotUpdateableSubsequently), + 0x0a => Ok(ComponentResponseCode::CompVerStrIdentical), + 0x0b => Ok(ComponentResponseCode::CompVerStrLower), + 0xd0..=0xef => Ok(ComponentResponseCode::VendorDefined), + _ => Err(PldmError::InvalidComponentResponseCode), + } + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +pub enum ComponentCompatibilityResponse { + CompCanBeUpdated = 0, + CompCannotBeUpdated = 1, +} + +impl TryFrom for ComponentCompatibilityResponse { + type Error = PldmError; + + fn try_from(val: u8) -> Result { + match val { + 0 => Ok(ComponentCompatibilityResponse::CompCanBeUpdated), + 1 => Ok(ComponentCompatibilityResponse::CompCannotBeUpdated), + _ => Err(PldmError::InvalidComponentCompatibilityResponse), + } + } +} + +#[repr(u8)] +pub enum ComponentCompatibilityResponseCode { + NoResponseCode = 0x00, + CompComparisonStampIdentical = 0x01, + CompComparisonStampLower = 0x02, + InvalidCompComparisonStamp = 0x03, + CompConflict = 0x04, + CompPrerequisitesNotMet = 0x05, + CompNotSupported = 0x06, + CompSecurityRestrictions = 0x07, + IncompleteCompImageSet = 0x08, + CompInfoNoMatch = 0x09, + CompVerStrIdentical = 0x0a, + CompVerStrLower = 0x0b, + VendorDefined, +} + +impl TryFrom for ComponentCompatibilityResponseCode { + type Error = PldmError; + + fn try_from(val: u8) -> Result { + match val { + 0x00 => Ok(ComponentCompatibilityResponseCode::NoResponseCode), + 0x01 => Ok(ComponentCompatibilityResponseCode::CompComparisonStampIdentical), + 0x02 => Ok(ComponentCompatibilityResponseCode::CompComparisonStampLower), + 0x03 => Ok(ComponentCompatibilityResponseCode::InvalidCompComparisonStamp), + 0x04 => Ok(ComponentCompatibilityResponseCode::CompConflict), + 0x05 => Ok(ComponentCompatibilityResponseCode::CompPrerequisitesNotMet), + 0x06 => Ok(ComponentCompatibilityResponseCode::CompNotSupported), + 0x07 => Ok(ComponentCompatibilityResponseCode::CompSecurityRestrictions), + 0x08 => Ok(ComponentCompatibilityResponseCode::IncompleteCompImageSet), + 0x09 => Ok(ComponentCompatibilityResponseCode::CompInfoNoMatch), + 0x0a => Ok(ComponentCompatibilityResponseCode::CompVerStrIdentical), + 0x0b => Ok(ComponentCompatibilityResponseCode::CompVerStrLower), + 0xd0..=0xef => Ok(ComponentCompatibilityResponseCode::VendorDefined), + _ => Err(PldmError::InvalidComponentCompatibilityResponseCode), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_descriptor_encode_decode() { + let test_uid = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, + ]; + let descriptor = Descriptor::new(DescriptorType::Uuid, &test_uid).unwrap(); + assert_eq!( + descriptor.descriptor_length, + get_descriptor_length(DescriptorType::Uuid) as u16 + ); + let mut buffer = [0u8; 512]; + descriptor.encode(&mut buffer).unwrap(); + let decoded_descriptor = Descriptor::decode(&buffer).unwrap(); + assert_eq!(descriptor, decoded_descriptor); + } + + #[test] + fn test_component_parameter_entry() { + let active_firmware_string = PldmFirmwareString::new("UTF-8", "mcu-runtime-1.0").unwrap(); + let active_firmware_version = + PldmFirmwareVersion::new(0x12345678, &active_firmware_string, Some("20250210")); + + let pending_firmware_string = PldmFirmwareString::new("UTF-8", "mcu-runtime-1.5").unwrap(); + let pending_firmware_version = + PldmFirmwareVersion::new(0x87654321, &pending_firmware_string, Some("20250213")); + + let comp_activation_methods = ComponentActivationMethods(0x0001); + let capabilities_during_update = FirmwareDeviceCapability(0x0010); + + let component_parameter_entry = ComponentParameterEntry::new( + ComponentClassification::Firmware, + 0x0001, + 0x01, + &active_firmware_version, + &pending_firmware_version, + comp_activation_methods, + capabilities_during_update, + ); + + let mut buffer = [0u8; 512]; + component_parameter_entry.encode(&mut buffer).unwrap(); + let decoded_component_parameter_entry = ComponentParameterEntry::decode(&buffer).unwrap(); + assert_eq!(component_parameter_entry, decoded_component_parameter_entry); + + let active_fw_ver = component_parameter_entry.get_active_fw_ver(); + assert_eq!(active_firmware_string, active_fw_ver); + assert!(active_firmware_string < pending_firmware_string); + } +} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs new file mode 100644 index 0000000..1622365 --- /dev/null +++ b/src/protocol/mod.rs @@ -0,0 +1,5 @@ +// Licensed under the Apache-2.0 license + +pub mod base; +pub mod firmware_update; +pub mod version; diff --git a/src/protocol/version.rs b/src/protocol/version.rs new file mode 100644 index 0000000..bdbe090 --- /dev/null +++ b/src/protocol/version.rs @@ -0,0 +1,250 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use core::convert::TryFrom; + +pub type Ver32 = u32; +pub type VersionCheckSum = u32; +pub type ProtocolVersionStr = &'static str; + +// The PLDM base protocol version 1.1.0 +pub const PLDM_BASE_PROTOCOL_VERSION: ProtocolVersionStr = "1.1.0"; + +// PLDM firmware update protocol 1.3.0 +pub const PLDM_FW_UPDATE_PROTOCOL_VERSION: ProtocolVersionStr = "1.3.0"; + +/// PLDM version structure. Ver32 encoding +/// +/// The "major," "minor," and "update" bytes are BCD-encoded, and each byte holds two BCD +/// digits. The "alpha" byte holds an optional alphanumeric character extension that is encoded using the +/// ISO/IEC 8859-1 Character Set. The value 0xF in the most-significant nibble of a BCD-encoded value indicates that the most +/// significant nibble should be ignored and the overall field treated as a single-digit value. Software +/// or utilities that display the number should display only a single digit and should not put in a +/// leading "0" when displaying the number. +/// +/// A value of 0xFF in the "update" field indicates that the entire field is not present. 0xFF is not +/// allowed as a value for the "major" or "minor" fields. Software or utilities that display the version +/// number should not display any characters for this field. +/// +/// For example: +/// - Version 3.7.10a → 0xF3F71061 +/// +/// - Version 3.1 → 0xF3F1FF00 +/// - Version 1.0a → 0xF1F0FF61 +#[derive(Debug, Clone, PartialEq, Eq)] +#[repr(C)] +pub struct PldmVersion { + pub alpha: u8, + pub update: u8, + pub minor: u8, + pub major: u8, +} + +impl TryFrom for PldmVersion { + type Error = PldmError; + + fn try_from(version: ProtocolVersionStr) -> Result { + let mut parts = version.split('.'); + let major_str = parts.next().ok_or(PldmError::InvalidProtocolVersion)?; + let minor_str = parts.next().ok_or(PldmError::InvalidProtocolVersion)?; + let update_str = parts.next().unwrap_or("FF"); + + if parts.next().is_some() { + return Err(PldmError::InvalidProtocolVersion); + } + + let (alpha, minor_str) = if let Some(last_char) = minor_str.chars().last() { + if last_char.is_alphabetic() { + (last_char as u8, &minor_str[..minor_str.len() - 1]) + } else { + (0, minor_str) + } + } else { + (0, minor_str) + }; + + let (alpha, update_str) = if update_str != "FF" { + if let Some(last_char) = update_str.chars().last() { + if last_char.is_alphabetic() { + (last_char as u8, &update_str[..update_str.len() - 1]) + } else { + (alpha, update_str) + } + } else { + (alpha, update_str) + } + } else { + (alpha, update_str) + }; + + let major = major_str + .parse::() + .map_err(|_| PldmError::InvalidProtocolVersion)?; + let minor = minor_str + .parse::() + .map_err(|_| PldmError::InvalidProtocolVersion)?; + let update = if update_str == "FF" { + 0xFF + } else { + update_str + .parse::() + .map_err(|_| PldmError::InvalidProtocolVersion)? + }; + + Ok(PldmVersion { + alpha, + update, + minor, + major, + }) + } +} + +impl PldmVersion { + pub fn new(alpha: u8, update: u8, minor: u8, major: u8) -> Self { + PldmVersion { + alpha, + update, + minor, + major, + } + } + + pub fn bcd_encode_to_ver32(&self) -> Ver32 { + let major_bcd = if self.major < 10 { + 0xF0 | self.major + } else { + ((self.major / 10) << 4) | (self.major % 10) + }; + let minor_bcd = if self.minor < 10 { + 0xF0 | self.minor + } else { + ((self.minor / 10) << 4) | (self.minor % 10) + }; + let update_bcd = if self.update == 0xFF { + 0xFF + } else if self.update < 10 { + 0xF0 | self.update + } else { + ((self.update / 10) << 4) | (self.update % 10) + }; + let alpha_bcd = self.alpha; // Alpha is directly used as it's already in the correct format or 0x00 if not present + + (major_bcd as u32) << 24 + | (minor_bcd as u32) << 16 + | (update_bcd as u32) << 8 + | alpha_bcd as u32 + } + + pub fn bcd_decode_from_ver32(encoded_ver32: Ver32) -> Self { + let major_bcd = ((encoded_ver32 >> 24) & 0xFF) as u8; + let minor_bcd = ((encoded_ver32 >> 16) & 0xFF) as u8; + let update_bcd = ((encoded_ver32 >> 8) & 0xFF) as u8; + let alpha = (encoded_ver32 & 0xFF) as u8; + + let major = if major_bcd >> 4 == 0xF { + major_bcd & 0x0F + } else { + ((major_bcd >> 4) * 10) + (major_bcd & 0x0F) + }; + let minor = if minor_bcd >> 4 == 0xF { + minor_bcd & 0x0F + } else { + ((minor_bcd >> 4) * 10) + (minor_bcd & 0x0F) + }; + let update = if update_bcd == 0xFF { + update_bcd + } else if update_bcd >> 4 == 0xF { + update_bcd & 0x0F + } else { + ((update_bcd >> 4) * 10) + (update_bcd & 0x0F) + }; + + PldmVersion { + alpha, + update, + minor, + major, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_pldm_version_try_from_str() { + let test_version = PldmVersion::try_from("3.7.10").unwrap(); + assert_eq!(test_version, PldmVersion::new(0, 10, 7, 3)); + + let test_version = PldmVersion::try_from("3.7").unwrap(); + assert_eq!(test_version, PldmVersion::new(0, 0xFF, 7, 3)); + + let test_version = PldmVersion::try_from("1.1.0").unwrap(); + assert_eq!(test_version, PldmVersion::new(0, 0, 1, 1)); + + let test_version = PldmVersion::try_from("1.3.0").unwrap(); + assert_eq!(test_version, PldmVersion::new(0, 0, 3, 1)); + + let test_version = PldmVersion::try_from("1.5.18a").unwrap(); + assert_eq!(test_version, PldmVersion::new(0x61, 18, 5, 1)); + + let test_version = PldmVersion::try_from("1.5a").unwrap(); + assert_eq!(test_version, PldmVersion::new(0x61, 0xFF, 5, 1)); + } + + #[test] + fn test_pldm_version_try_from_str_error() { + let test_version = PldmVersion::try_from("3.FF.10"); + assert!(test_version.is_err()); + + let test_version = PldmVersion::try_from("3.7.10a.1"); + assert!(test_version.is_err()); + + let test_version = PldmVersion::try_from("3.7.10a.1.1"); + assert!(test_version.is_err()); + + let test_version = PldmVersion::try_from("3a.7.10"); + assert!(test_version.is_err()); + } + + #[test] + fn test_pldm_version_bcd_encode() { + let test_version1 = PldmVersion::new(0x61, 0x10, 0x7, 0x3); + assert_eq!(test_version1.bcd_encode_to_ver32(), 0xF3F71661); + + let test_version2 = PldmVersion::new(0x61, 0xFF, 0x1, 0x3); + assert_eq!(test_version2.bcd_encode_to_ver32(), 0xF3F1FF61); + + let test_version3 = PldmVersion::new(0x61, 0xFF, 0xa, 0x1); + assert_eq!(test_version3.bcd_encode_to_ver32(), 0xF110FF61); + } + + #[test] + fn test_pldm_version_bcd_decode_from_ver32() { + let test_version1 = PldmVersion::bcd_decode_from_ver32(0xF3F71661); + assert_eq!(test_version1, PldmVersion::new(0x61, 0x10, 0x7, 0x3)); + + let test_version2 = PldmVersion::bcd_decode_from_ver32(0xF3F1FF61); + assert_eq!(test_version2, PldmVersion::new(0x61, 0xFF, 0x1, 0x3)); + + let test_version3 = PldmVersion::bcd_decode_from_ver32(0xF1F0FF62); + assert_eq!(test_version3, PldmVersion::new(0x62, 0xFF, 0x0, 0x1)); + } + + #[test] + fn test_pldm_version_str_to_ver32() { + let test_version1 = "1.3.0"; + let test_version1_ver32 = PldmVersion::try_from(test_version1) + .unwrap() + .bcd_encode_to_ver32(); + assert_eq!(test_version1_ver32, 0xF1F3F000); + + let test_version2 = "1.1.0"; + let test_version2_ver32 = PldmVersion::try_from(test_version2) + .unwrap() + .bcd_encode_to_ver32(); + assert_eq!(test_version2_ver32, 0xF1F1F000); + } +} diff --git a/src/util/fw_component.rs b/src/util/fw_component.rs new file mode 100644 index 0000000..17ff3ff --- /dev/null +++ b/src/util/fw_component.rs @@ -0,0 +1,200 @@ +// Licensed under the Apache-2.0 license + +use crate::message::firmware_update::get_fw_params::FirmwareParameters; +use crate::protocol::firmware_update::{ + ComponentResponseCode, PldmFirmwareString, UpdateOptionFlags, +}; + +// An entry for Pass Component Table or Update Component +#[derive(Clone, Default, PartialEq)] +pub struct FirmwareComponent { + pub comp_classification: u16, + pub comp_identifier: u16, + pub comp_classification_index: u8, + pub comp_comparison_stamp: u32, + pub comp_version: PldmFirmwareString, + pub comp_image_size: Option, + pub update_option_flags: Option, +} + +impl FirmwareComponent { + pub fn new( + comp_classification: u16, + comp_identifier: u16, + comp_classification_index: u8, + comp_comparison_stamp: u32, + comp_version: PldmFirmwareString, + comp_image_size: Option, + update_option_flags: Option, + ) -> Self { + Self { + comp_classification, + comp_identifier, + comp_classification_index, + comp_comparison_stamp, + comp_version, + comp_image_size, + update_option_flags, + } + } + + // Determines if the component is eligible for an update based on the firmware parameters and returns the appropriate ComponentResponseCode + // defined in the PLDM firmware update specification. + pub fn evaluate_update_eligibility( + &self, + fw_params: &FirmwareParameters, + ) -> ComponentResponseCode { + if let Some(entry) = fw_params.comp_param_table.iter().find(|entry| { + entry.comp_param_entry_fixed.comp_classification == self.comp_classification + && entry.comp_param_entry_fixed.comp_identifier == self.comp_identifier + && entry.comp_param_entry_fixed.comp_classification_index + == self.comp_classification_index + }) { + if self.comp_comparison_stamp + == entry.comp_param_entry_fixed.active_comp_comparison_stamp + { + ComponentResponseCode::CompComparisonStampIdentical + } else if self.comp_comparison_stamp + < entry.comp_param_entry_fixed.active_comp_comparison_stamp + { + ComponentResponseCode::CompComparisonStampLower + } else if self.comp_version == entry.get_active_fw_ver() { + ComponentResponseCode::CompVerStrIdentical + } else if self.comp_version < entry.get_active_fw_ver() { + ComponentResponseCode::CompVerStrLower + } else { + ComponentResponseCode::CompCanBeUpdated + } + } else { + ComponentResponseCode::CompNotSupported + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::protocol::firmware_update::{ + ComponentActivationMethods, ComponentClassification, ComponentParameterEntry, + FirmwareDeviceCapability, PldmFirmwareVersion, + }; + + fn construct_firmware_params() -> FirmwareParameters { + let active_firmware_string = PldmFirmwareString::new("UTF-8", "mcu-runtime-1.0").unwrap(); + let active_firmware_version = + PldmFirmwareVersion::new(0x12345678, &active_firmware_string, Some("20250210")); + let pending_firmware_string = PldmFirmwareString::new("UTF-8", "mcu-runtime-1.5").unwrap(); + let pending_firmware_version = + PldmFirmwareVersion::new(0x87654321, &pending_firmware_string, Some("20250213")); + let comp_activation_methods = ComponentActivationMethods(0x0001); + let capabilities_during_update = FirmwareDeviceCapability(0x0010); + let component_parameter_entry = ComponentParameterEntry::new( + ComponentClassification::Firmware, + 0x0001, + 0x01, + &active_firmware_version, + &pending_firmware_version, + comp_activation_methods, + capabilities_during_update, + ); + + const COMP_COUNT: usize = 8; + let comp_param_table: [ComponentParameterEntry; COMP_COUNT] = + core::array::from_fn(|_| component_parameter_entry.clone()); + FirmwareParameters::new( + capabilities_during_update, + COMP_COUNT as u16, + &active_firmware_string, + &pending_firmware_string, + &comp_param_table, + ) + } + + #[test] + fn test_check_update_component() { + let fw_params = construct_firmware_params(); + let comp = FirmwareComponent::new( + ComponentClassification::Firmware as u16, + 0x0001, + 0x01, + 0x12345680, + PldmFirmwareString::new("UTF-8", "mcu-runtime-1.2").unwrap(), + None, + None, + ); + assert_eq!( + comp.evaluate_update_eligibility(&fw_params), + ComponentResponseCode::CompCanBeUpdated + ); + + let comp = FirmwareComponent::new( + ComponentClassification::Firmware as u16, + 0x0001, + 0x01, + 0x12345670, + PldmFirmwareString::new("UTF-8", "mcu-runtime-1.2").unwrap(), + None, + None, + ); + assert_eq!( + comp.evaluate_update_eligibility(&fw_params), + ComponentResponseCode::CompComparisonStampLower + ); + + let comp = FirmwareComponent::new( + ComponentClassification::Firmware as u16, + 0x0001, + 0x01, + 0x12345678, + PldmFirmwareString::new("UTF-8", "mcu-runtime-1.5").unwrap(), + None, + None, + ); + assert_eq!( + comp.evaluate_update_eligibility(&fw_params), + ComponentResponseCode::CompComparisonStampIdentical + ); + + let comp = FirmwareComponent::new( + ComponentClassification::Firmware as u16, + 0x0001, + 0x01, + 0x12345680, + PldmFirmwareString::new("UTF-8", "mcu-runtime-0.5").unwrap(), + None, + None, + ); + assert_eq!( + comp.evaluate_update_eligibility(&fw_params), + ComponentResponseCode::CompVerStrLower + ); + + let comp = FirmwareComponent::new( + ComponentClassification::Firmware as u16, + 0x0001, + 0x01, + 0x12345680, + PldmFirmwareString::new("UTF-8", "mcu-runtime-1.0").unwrap(), + None, + None, + ); + assert_eq!( + comp.evaluate_update_eligibility(&fw_params), + ComponentResponseCode::CompVerStrIdentical + ); + + let comp = FirmwareComponent::new( + ComponentClassification::Firmware as u16, + 0x0001, + 0x05, + 0x12345680, + PldmFirmwareString::new("UTF-8", "mcu-runtime-1.0").unwrap(), + None, + None, + ); + assert_eq!( + comp.evaluate_update_eligibility(&fw_params), + ComponentResponseCode::CompNotSupported + ); + } +} diff --git a/src/util/mctp_transport.rs b/src/util/mctp_transport.rs new file mode 100644 index 0000000..166163a --- /dev/null +++ b/src/util/mctp_transport.rs @@ -0,0 +1,110 @@ +// Licensed under the Apache-2.0 license + +use crate::error::UtilError; +use crate::protocol::base::PLDM_MSG_HEADER_LEN; +use bitfield::bitfield; + +pub const MCTP_PLDM_MSG_TYPE: u8 = 0x01; +pub const MCTP_COMMON_HEADER_OFFSET: usize = 0; +pub const PLDM_MSG_OFFSET: usize = 1; + +bitfield! { + #[derive(Copy, Clone, PartialEq)] + pub struct MctpCommonHeader(u8); + impl Debug; + pub u8, ic, set_ic: 7, 7; + pub u8, msg_type, set_msg_type: 6, 0; +} + +// +/// Extracts the PLDM message from the given MCTP payload. +/// +/// # Arguments +/// +/// * `mctp_payload` - A mutable reference to the MCTP payload. +/// +/// # Returns +/// +/// * `Result<&mut [u8], UtilError>` - A result containing a mutable reference to the PLDM message slice +/// if successful, or a `UtilError` if the payload length is invalid or the message type is incorrect. +pub fn extract_pldm_msg(mctp_payload: &mut [u8]) -> Result<&mut [u8], UtilError> { + // Check if the payload length is sufficient to contain the MCTP common header and PLDM message header. + if mctp_payload.len() < 1 + PLDM_MSG_HEADER_LEN { + return Err(UtilError::InvalidMctpPayloadLength); + } + + // Extract the MCTP common header from the payload. + let mctp_common_header = MctpCommonHeader(mctp_payload[MCTP_COMMON_HEADER_OFFSET]); + + // Validate the integrity check (IC) and message type fields. + if mctp_common_header.ic() != 0 || mctp_common_header.msg_type() != MCTP_PLDM_MSG_TYPE { + return Err(UtilError::InvalidMctpMsgType); + } + + // Return a mutable reference to the PLDM message slice. + Ok(&mut mctp_payload[PLDM_MSG_OFFSET..]) +} + +/// Constructs an MCTP payload with a PLDM message. +/// +/// # Arguments +/// +/// * `mctp_payload` - A mutable reference to the MCTP payload. +/// +/// # Returns +/// +/// * `Result<&mut [u8], UtilError>` - A result containing a mutable reference to the PLDM message slice +/// if successful, or a `UtilError` if the payload length is invalid. +pub fn construct_mctp_pldm_msg(mctp_payload: &mut [u8]) -> Result<&mut [u8], UtilError> { + // Check if the payload length is sufficient to contain the MCTP common header and PLDM message header. + if mctp_payload.len() < 1 + PLDM_MSG_HEADER_LEN { + return Err(UtilError::InvalidMctpPayloadLength); + } + + // Initialize the MCTP common header. + let mut mctp_common_header = MctpCommonHeader(0); + mctp_common_header.set_ic(0); + mctp_common_header.set_msg_type(MCTP_PLDM_MSG_TYPE); + + // Set the MCTP common header in the payload. + mctp_payload[MCTP_COMMON_HEADER_OFFSET] = mctp_common_header.0; + + // Return a mutable reference to the PLDM message slice. + Ok(&mut mctp_payload[PLDM_MSG_OFFSET..]) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_retrieve_pldm_msg() { + let mut mctp_payload = [0u8; 8]; + assert_eq!( + extract_pldm_msg(&mut mctp_payload), + Err(UtilError::InvalidMctpMsgType) + ); + mctp_payload[0] = 0x01; + assert_eq!(extract_pldm_msg(&mut mctp_payload).unwrap(), &mut [0u8; 7]); + let mut mctp_payload = [0u8; 3]; + assert_eq!( + extract_pldm_msg(&mut mctp_payload), + Err(UtilError::InvalidMctpPayloadLength) + ); + } + + #[test] + fn test_construct_mctp_pldm_msg() { + let mut mctp_payload = [0u8; 10]; + assert_eq!( + construct_mctp_pldm_msg(&mut mctp_payload).unwrap(), + &mut [0u8; 9] + ); + assert_eq!(mctp_payload[0], 0x01); + let mut mctp_payload = [0u8; 3]; + assert_eq!( + construct_mctp_pldm_msg(&mut mctp_payload), + Err(UtilError::InvalidMctpPayloadLength) + ); + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..e435fb3 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,4 @@ +// Licensed under the Apache-2.0 license + +pub mod fw_component; +pub mod mctp_transport; From 201591dad33d750560c6844ad840f176f3bea220 Mon Sep 17 00:00:00 2001 From: CourtneyDrant Date: Tue, 23 Sep 2025 15:55:42 -0700 Subject: [PATCH 2/5] Intermediate commit. fd_ops and fd_internal are both structs with placeholder member functions. IPC calls will be placed into their bodies and fd_internal as well as fd_ops content will be moved to a fd Hubris driver task. --- pldm-common/Cargo.toml | 12 + pldm-common/src/codec.rs | 56 + pldm-common/src/error.rs | 36 + pldm-common/src/lib.rs | 8 + pldm-common/src/message/control.rs | 388 ++++++ .../message/firmware_update/activate_fw.rs | 98 ++ .../message/firmware_update/apply_complete.rs | 117 ++ .../message/firmware_update/get_fw_params.rs | 362 ++++++ .../src/message/firmware_update/get_status.rs | 225 ++++ .../src/message/firmware_update/mod.rs | 14 + .../message/firmware_update/pass_component.rs | 189 +++ .../message/firmware_update/query_devid.rs | 250 ++++ .../message/firmware_update/request_cancel.rs | 157 +++ .../firmware_update/request_fw_data.rs | 129 ++ .../message/firmware_update/request_update.rs | 268 +++++ .../firmware_update/transfer_complete.rs | 124 ++ .../firmware_update/update_component.rs | 338 ++++++ .../firmware_update/verify_complete.rs | 111 ++ pldm-common/src/message/mod.rs | 4 + pldm-common/src/protocol/base.rs | 302 +++++ pldm-common/src/protocol/firmware_update.rs | 884 ++++++++++++++ pldm-common/src/protocol/mod.rs | 5 + pldm-common/src/protocol/version.rs | 250 ++++ pldm-common/src/util/fw_component.rs | 200 ++++ pldm-common/src/util/mctp_transport.rs | 110 ++ pldm-common/src/util/mod.rs | 4 + pldm-service/Cargo.toml | 12 + pldm-service/src/cmd_interface.rs | 226 ++++ pldm-service/src/config.rs | 50 + pldm-service/src/control_context.rs | 309 +++++ pldm-service/src/error.rs | 18 + .../src/firmware_device/fd_context.rs | 1046 +++++++++++++++++ .../src/firmware_device/fd_internal.rs | 194 +++ pldm-service/src/firmware_device/fd_ops.rs | 272 +++++ pldm-service/src/firmware_device/mod.rs | 5 + pldm-service/src/lib.rs | 9 + pldm-service/src/timer.rs | 83 ++ pldm-service/src/transport.rs | 31 + 38 files changed, 6896 insertions(+) create mode 100644 pldm-common/Cargo.toml create mode 100644 pldm-common/src/codec.rs create mode 100644 pldm-common/src/error.rs create mode 100644 pldm-common/src/lib.rs create mode 100644 pldm-common/src/message/control.rs create mode 100644 pldm-common/src/message/firmware_update/activate_fw.rs create mode 100644 pldm-common/src/message/firmware_update/apply_complete.rs create mode 100644 pldm-common/src/message/firmware_update/get_fw_params.rs create mode 100644 pldm-common/src/message/firmware_update/get_status.rs create mode 100644 pldm-common/src/message/firmware_update/mod.rs create mode 100644 pldm-common/src/message/firmware_update/pass_component.rs create mode 100644 pldm-common/src/message/firmware_update/query_devid.rs create mode 100644 pldm-common/src/message/firmware_update/request_cancel.rs create mode 100644 pldm-common/src/message/firmware_update/request_fw_data.rs create mode 100644 pldm-common/src/message/firmware_update/request_update.rs create mode 100644 pldm-common/src/message/firmware_update/transfer_complete.rs create mode 100644 pldm-common/src/message/firmware_update/update_component.rs create mode 100644 pldm-common/src/message/firmware_update/verify_complete.rs create mode 100644 pldm-common/src/message/mod.rs create mode 100644 pldm-common/src/protocol/base.rs create mode 100644 pldm-common/src/protocol/firmware_update.rs create mode 100644 pldm-common/src/protocol/mod.rs create mode 100644 pldm-common/src/protocol/version.rs create mode 100644 pldm-common/src/util/fw_component.rs create mode 100644 pldm-common/src/util/mctp_transport.rs create mode 100644 pldm-common/src/util/mod.rs create mode 100644 pldm-service/Cargo.toml create mode 100644 pldm-service/src/cmd_interface.rs create mode 100644 pldm-service/src/config.rs create mode 100644 pldm-service/src/control_context.rs create mode 100644 pldm-service/src/error.rs create mode 100644 pldm-service/src/firmware_device/fd_context.rs create mode 100644 pldm-service/src/firmware_device/fd_internal.rs create mode 100644 pldm-service/src/firmware_device/fd_ops.rs create mode 100644 pldm-service/src/firmware_device/mod.rs create mode 100644 pldm-service/src/lib.rs create mode 100644 pldm-service/src/timer.rs create mode 100644 pldm-service/src/transport.rs diff --git a/pldm-common/Cargo.toml b/pldm-common/Cargo.toml new file mode 100644 index 0000000..ac80d23 --- /dev/null +++ b/pldm-common/Cargo.toml @@ -0,0 +1,12 @@ +# Licensed under the Apache-2.0 license + +[package] +name = "pldm-common" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[dependencies] +zerocopy.workspace = true +bitfield.workspace = true + diff --git a/pldm-common/src/codec.rs b/pldm-common/src/codec.rs new file mode 100644 index 0000000..15df19f --- /dev/null +++ b/pldm-common/src/codec.rs @@ -0,0 +1,56 @@ +// Licensed under the Apache-2.0 license + +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, PartialEq)] +pub enum PldmCodecError { + BufferTooShort, + Unsupported, +} + +/// A trait for encoding and decoding PLDM (Platform Level Data Model) messages. +/// +/// This trait provides methods for encoding a PLDM message into a byte buffer +/// and decoding a PLDM message from a byte buffer. Implementers of this trait +/// must also implement the `Debug` trait and be `Sized`. +pub trait PldmCodec: 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. + /// + /// # Returns + /// + /// A `Result` containing the decoded message on success, or a `PldmCodecError` on failure. + fn decode(buffer: &[u8]) -> Result; +} + +// Default implementation of PldmCodec for types that can leverage zerocopy. +impl PldmCodec for T +where + T: core::fmt::Debug + Sized + FromBytes + IntoBytes + Immutable, +{ + fn encode(&self, buffer: &mut [u8]) -> Result { + self.write_to_prefix(buffer) + .map_err(|_| PldmCodecError::BufferTooShort) + .map(|_| core::mem::size_of::()) + } + + fn decode(buffer: &[u8]) -> Result { + Ok(Self::read_from_prefix(buffer) + .map_err(|_| PldmCodecError::BufferTooShort)? + .0) + } +} diff --git a/pldm-common/src/error.rs b/pldm-common/src/error.rs new file mode 100644 index 0000000..17dd6a2 --- /dev/null +++ b/pldm-common/src/error.rs @@ -0,0 +1,36 @@ +// Licensed under the Apache-2.0 license + +#[derive(Debug, Clone, PartialEq)] +pub enum PldmError { + InvalidData, + InvalidLength, + InvalidMsgType, + InvalidProtocolVersion, + UnsupportedCmd, + UnsupportedPldmType, + InvalidCompletionCode, + InvalidTransferOpFlag, + InvalidTransferRespFlag, + + InvalidVersionStringType, + InvalidVersionStringLength, + InvalidFdState, + InvalidDescriptorType, + InvalidDescriptorLength, + InvalidDescriptorCount, + InvalidComponentClassification, + InvalidComponentResponseCode, + InvalidComponentCompatibilityResponse, + InvalidComponentCompatibilityResponseCode, + InvalidTransferResult, + InvalidVerifyResult, + InvalidApplyResult, + InvalidGetStatusReasonCode, + InvalidAuxStateStatus, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum UtilError { + InvalidMctpPayloadLength, + InvalidMctpMsgType, +} diff --git a/pldm-common/src/lib.rs b/pldm-common/src/lib.rs new file mode 100644 index 0000000..1cbd5bb --- /dev/null +++ b/pldm-common/src/lib.rs @@ -0,0 +1,8 @@ +// Licensed under the Apache-2.0 license + + +pub mod codec; +pub mod error; +pub mod message; +pub mod protocol; +pub mod util; \ No newline at end of file diff --git a/pldm-common/src/message/control.rs b/pldm-common/src/message/control.rs new file mode 100644 index 0000000..e836b08 --- /dev/null +++ b/pldm-common/src/message/control.rs @@ -0,0 +1,388 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmControlCmd, PldmMsgHeader, PldmMsgType, PldmSupportedType, + TransferOperationFlag, TransferRespFlag, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::version::{PldmVersion, ProtocolVersionStr, Ver32}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub const PLDM_CMDS_BITMAP_LEN: usize = 32; +pub const PLDM_TYPES_BITMAP_LEN: usize = 8; + +#[repr(C, packed)] +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +pub struct GetTidRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl GetTidRequest { + pub fn new(instance_id: InstanceId, message_type: PldmMsgType) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + message_type, + PldmSupportedType::Base, + PldmControlCmd::GetTid as u8, + ), + } + } +} + +#[repr(C, packed)] +#[derive(Debug, PartialEq, FromBytes, IntoBytes, Immutable, Clone)] +pub struct GetTidResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub tid: u8, +} + +impl GetTidResponse { + pub fn new(instance_id: InstanceId, tid: u8, completion_code: u8) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::Base, + PldmControlCmd::GetTid as u8, + ), + completion_code, + tid, + } + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +#[repr(C, packed)] +pub struct SetTidRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub tid: u8, +} +impl SetTidRequest { + pub fn new(instance_id: InstanceId, message_type: PldmMsgType, tid: u8) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + message_type, + PldmSupportedType::Base, + PldmControlCmd::SetTid as u8, + ), + tid, + } + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +#[repr(C, packed)] +pub struct SetTidResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, +} + +impl SetTidResponse { + pub fn new(instance_id: InstanceId, ompletion_code: u8) -> Self { + SetTidResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::Base, + PldmControlCmd::SetTid as u8, + ), + completion_code: ompletion_code, + } + } +} + +#[repr(C, packed)] +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +pub struct GetPldmCommandsRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub pldm_type: u8, + pub protocol_version: Ver32, +} + +impl GetPldmCommandsRequest { + pub fn new( + instance_id: InstanceId, + message_type: PldmMsgType, + pldm_type: u8, + version_str: ProtocolVersionStr, + ) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + message_type, + PldmSupportedType::Base, + PldmControlCmd::GetPldmCommands as u8, + ), + pldm_type, + protocol_version: PldmVersion::try_from(version_str) + .unwrap() + .bcd_encode_to_ver32(), + } + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +#[repr(C, packed)] +pub struct GetPldmCommandsResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub supported_cmds: [u8; PLDM_CMDS_BITMAP_LEN], +} + +impl GetPldmCommandsResponse { + pub fn new(instance_id: InstanceId, completion_code: u8, supported_cmds: &[u8]) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::Base, + PldmControlCmd::GetPldmCommands as u8, + ), + completion_code, + supported_cmds: construct_bitmap::(supported_cmds), + } + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +#[repr(C, packed)] +pub struct GetPldmTypeRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl GetPldmTypeRequest { + pub fn new(instance_id: InstanceId, message_type: PldmMsgType) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + message_type, + PldmSupportedType::Base, + PldmControlCmd::GetPldmTypes as u8, + ), + } + } +} + +fn construct_bitmap(items: &[u8]) -> [u8; N] { + let mut bitmap = [0u8; N]; + for &item in items.iter().take(N * 8) { + let byte_index = (item / 8) as usize; + let bit_index = (item % 8) as usize; + bitmap[byte_index] |= 1 << bit_index; + } + bitmap +} + +pub fn is_bit_set(bitmap: &[u8], item: u8) -> bool { + let byte_index = (item / 8) as usize; + let bit_index = (item % 8) as usize; + bitmap[byte_index] & (1 << bit_index) != 0 +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +#[repr(C, packed)] +pub struct GetPldmTypeResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub pldm_types: [u8; PLDM_TYPES_BITMAP_LEN], +} + +impl GetPldmTypeResponse { + pub fn new(instance_id: InstanceId, completion_code: u8, supported_types: &[u8]) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::Base, + PldmControlCmd::GetPldmTypes as u8, + ), + completion_code, + pldm_types: construct_bitmap::(supported_types), + } + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +#[repr(C, packed)] +pub struct GetPldmVersionRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub data_transfer_handle: u32, + pub transfer_op_flag: u8, + pub pldm_type: u8, +} + +impl GetPldmVersionRequest { + pub fn new( + instance_id: InstanceId, + message_type: PldmMsgType, + data_transfer_handle: u32, + transfer_op_flag: TransferOperationFlag, + pldm_type: PldmSupportedType, + ) -> Self { + Self { + hdr: PldmMsgHeader::new( + instance_id, + message_type, + PldmSupportedType::Base, + PldmControlCmd::GetPldmVersion as u8, + ), + data_transfer_handle, + transfer_op_flag: transfer_op_flag as u8, + pldm_type: pldm_type as u8, + } + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] +#[repr(C, packed)] +pub struct GetPldmVersionResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub next_transfer_handle: u32, // next portion of PLDM version data transfer + pub transfer_rsp_flag: u8, // PLDM GetVersion transfer flag + pub version_data: Ver32, // PLDM GetVersion version field. Support only 1 version field. Version data is version and checksum +} + +impl GetPldmVersionResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + next_transfer_handle: u32, + transfer_rsp_flag: TransferRespFlag, + version_str: ProtocolVersionStr, + ) -> Result { + Ok(Self { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::Base, + PldmControlCmd::GetPldmVersion as u8, + ), + completion_code, + next_transfer_handle, + transfer_rsp_flag: transfer_rsp_flag as u8, + version_data: PldmVersion::try_from(version_str) + .map_err(|_| PldmError::InvalidProtocolVersion)? + .bcd_encode_to_ver32(), + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::codec::{PldmCodec, PldmCodecError}; + + #[test] + fn test_get_tid_request() { + let request = GetTidRequest::new(0x01, PldmMsgType::Request); + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded_request = GetTidRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_get_tid_response() { + let response = GetTidResponse::new(0x01, 42, 0); + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = GetTidResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } + + #[test] + fn test_set_tid_request() { + let request = SetTidRequest::new(0x01, PldmMsgType::Request, 42); + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded_request = SetTidRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_set_tid_response() { + let response = SetTidResponse::new(0x01, 0); + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = SetTidResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } + + #[test] + fn test_get_pldm_commands_request() { + let request = GetPldmCommandsRequest::new(0x01, PldmMsgType::Request, 1, "1.0.0"); + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded_request = GetPldmCommandsRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_get_pldm_commands_response() { + let response = GetPldmCommandsResponse::new(0x01, 0, &[1, 2, 3]); + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = GetPldmCommandsResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } + + #[test] + fn test_get_pldm_type_request() { + let request = GetPldmTypeRequest::new(0x01, PldmMsgType::Request); + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded_request = GetPldmTypeRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_get_pldm_type_response() { + let response = GetPldmTypeResponse::new(0x01, 0, &[0, 5]); + // Check bit map + let mut expected_bitmap = [0u8; PLDM_TYPES_BITMAP_LEN]; + expected_bitmap[0] = 0b00100001; + assert_eq!(response.pldm_types, expected_bitmap); + + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = GetPldmTypeResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } + + #[test] + fn test_get_pldm_version_request() { + let request = GetPldmVersionRequest::new( + 0x01, + PldmMsgType::Request, + 0, + TransferOperationFlag::GetFirstPart, + PldmSupportedType::Base, + ); + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded_request = GetPldmVersionRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_get_pldm_version_response() { + let response = + GetPldmVersionResponse::new(0x01, 0, 0, TransferRespFlag::StartAndEnd, "1.3.0") + .unwrap(); + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = GetPldmVersionResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } + + #[test] + fn test_buffer_too_short() { + let buffer = [0u8; 2]; + let result = GetTidRequest::decode(&buffer); + assert_eq!(result, Err(PldmCodecError::BufferTooShort)); + } +} diff --git a/pldm-common/src/message/firmware_update/activate_fw.rs b/pldm-common/src/message/firmware_update/activate_fw.rs new file mode 100644 index 0000000..b7b083a --- /dev/null +++ b/pldm-common/src/message/firmware_update/activate_fw.rs @@ -0,0 +1,98 @@ +// Licensed under the Apache-2.0 license + +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::FwUpdateCmd; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(u8)] +pub enum SelfContainedActivationRequest { + NotActivateSelfContainedComponents = 0, + ActivateSelfContainedComponents = 1, +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct ActivateFirmwareRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub self_contained_activation_req: u8, +} + +impl ActivateFirmwareRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + self_contained_activation_req: SelfContainedActivationRequest, + ) -> ActivateFirmwareRequest { + ActivateFirmwareRequest { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::ActivateFirmware as u8, + ), + self_contained_activation_req: self_contained_activation_req as u8, + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct ActivateFirmwareResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub estimated_time_activation: u16, +} + +impl ActivateFirmwareResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + estimated_time_activation: u16, + ) -> ActivateFirmwareResponse { + ActivateFirmwareResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::ActivateFirmware as u8, + ), + completion_code, + estimated_time_activation, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_activate_firmware_request() { + let request = ActivateFirmwareRequest::new( + 1, + PldmMsgType::Request, + SelfContainedActivationRequest::ActivateSelfContainedComponents, + ); + + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + + let decoded_request = ActivateFirmwareRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_activate_firmware_response() { + let response = ActivateFirmwareResponse::new(1, 0, 10); + + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + + let decoded_response = ActivateFirmwareResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/pldm-common/src/message/firmware_update/apply_complete.rs b/pldm-common/src/message/firmware_update/apply_complete.rs new file mode 100644 index 0000000..8c6d6b0 --- /dev/null +++ b/pldm-common/src/message/firmware_update/apply_complete.rs @@ -0,0 +1,117 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::{ComponentActivationMethods, FwUpdateCmd}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum ApplyResult { + ApplySuccess = 0x00, + ApplySuccessWithActivationMethod = 0x01, + ApplyFailureMemoryIssue = 0x02, + ApplyTimeOut = 0x09, + #[default] + ApplyGenericError = 0x0a, + VendorDefined, +} + +impl TryFrom for ApplyResult { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(ApplyResult::ApplySuccess), + 0x01 => Ok(ApplyResult::ApplySuccessWithActivationMethod), + 0x02 => Ok(ApplyResult::ApplyFailureMemoryIssue), + 0x09 => Ok(ApplyResult::ApplyTimeOut), + 0x0a => Ok(ApplyResult::ApplyGenericError), + 0xb0..=0xcf => Ok(ApplyResult::VendorDefined), + _ => Err(PldmError::InvalidApplyResult), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct ApplyCompleteRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub apply_result: u8, + pub comp_activation_methods_modification: u16, +} + +impl ApplyCompleteRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + apply_result: ApplyResult, + comp_activation_methods: ComponentActivationMethods, + ) -> Self { + ApplyCompleteRequest { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::ApplyComplete as u8, + ), + apply_result: apply_result as u8, + comp_activation_methods_modification: comp_activation_methods.0, + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct ApplyCompleteResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, +} + +impl ApplyCompleteResponse { + pub fn new(instance_id: InstanceId, completion_code: u8) -> ApplyCompleteResponse { + ApplyCompleteResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::ApplyComplete as u8, + ), + completion_code, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_apply_complete_request() { + let request = ApplyCompleteRequest::new( + 1, + PldmMsgType::Request, + ApplyResult::ApplySuccess, + ComponentActivationMethods(0x0001), + ); + + let mut buffer = [0u8; 64]; + let bytes_written = request.encode(&mut buffer).unwrap(); + assert_eq!(bytes_written, core::mem::size_of::()); + let decoded_request = ApplyCompleteRequest::decode(&buffer[..bytes_written]).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_apply_complete_response() { + let response = ApplyCompleteResponse::new(1, 0); + let mut buffer = [0u8; 64]; + let bytes_written = response.encode(&mut buffer).unwrap(); + assert_eq!(bytes_written, core::mem::size_of::()); + let decoded_response = ApplyCompleteResponse::decode(&buffer[..bytes_written]).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/pldm-common/src/message/firmware_update/get_fw_params.rs b/pldm-common/src/message/firmware_update/get_fw_params.rs new file mode 100644 index 0000000..c18d92d --- /dev/null +++ b/pldm-common/src/message/firmware_update/get_fw_params.rs @@ -0,0 +1,362 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError}; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::{ + ComponentParameterEntry, FirmwareDeviceCapability, FwUpdateCmd, PldmFirmwareString, + MAX_COMPONENT_COUNT, PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN, +}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct GetFirmwareParametersRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl GetFirmwareParametersRequest { + pub fn new(instance_id: InstanceId, message_type: PldmMsgType) -> Self { + GetFirmwareParametersRequest { + hdr: PldmMsgHeader::new( + instance_id, + message_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetFirmwareParameters as u8, + ), + } + } +} + +#[derive(Debug, Clone, PartialEq, FromBytes, IntoBytes, Immutable)] +#[repr(C, packed)] +pub struct FirmwareParamFixed { + pub capabilities_during_update: FirmwareDeviceCapability, + pub comp_count: u16, + pub active_comp_image_set_ver_str_type: u8, + pub active_comp_image_set_ver_str_len: u8, + pub pending_comp_image_set_ver_str_type: u8, + pub pending_comp_image_set_ver_str_len: u8, +} + +impl Default for FirmwareParamFixed { + fn default() -> Self { + FirmwareParamFixed { + capabilities_during_update: FirmwareDeviceCapability(0), + comp_count: 0, + active_comp_image_set_ver_str_type: 0, + active_comp_image_set_ver_str_len: 0, + pending_comp_image_set_ver_str_type: 0, + pending_comp_image_set_ver_str_len: 0, + } + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +#[repr(C)] +pub struct FirmwareParameters { + pub params_fixed: FirmwareParamFixed, + pub active_comp_image_set_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], + pub pending_comp_image_set_ver_str: Option<[u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]>, + pub comp_param_table: [ComponentParameterEntry; MAX_COMPONENT_COUNT], +} + +impl FirmwareParameters { + pub fn new( + capabilities_during_update: FirmwareDeviceCapability, + comp_count: u16, + active_comp_image_set_version: &PldmFirmwareString, + pending_comp_image_set_version: &PldmFirmwareString, + comp_param_table: &[ComponentParameterEntry], + ) -> Self { + FirmwareParameters { + params_fixed: FirmwareParamFixed { + capabilities_during_update, + comp_count, + active_comp_image_set_ver_str_type: active_comp_image_set_version.str_type, + active_comp_image_set_ver_str_len: active_comp_image_set_version.str_len, + pending_comp_image_set_ver_str_type: pending_comp_image_set_version.str_type, + pending_comp_image_set_ver_str_len: pending_comp_image_set_version.str_len, + }, + pending_comp_image_set_ver_str: if pending_comp_image_set_version.str_len > 0 { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = pending_comp_image_set_version.str_data.len(); + arr[..len].copy_from_slice(&pending_comp_image_set_version.str_data); + Some(arr) + } else { + None + }, + active_comp_image_set_ver_str: { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = active_comp_image_set_version.str_data.len(); + arr[..len].copy_from_slice(&active_comp_image_set_version.str_data); + arr + }, + comp_param_table: { + let count = comp_param_table.len().min(MAX_COMPONENT_COUNT); + core::array::from_fn(|i| { + if i < count { + comp_param_table[i].clone() + } else { + ComponentParameterEntry::default() + } + }) + }, + } + } + + fn codec_size_in_bytes(&self) -> usize { + let mut bytes = core::mem::size_of::(); + if self.pending_comp_image_set_ver_str.is_some() { + bytes += self.params_fixed.pending_comp_image_set_ver_str_len as usize; + } + bytes += self.params_fixed.active_comp_image_set_ver_str_len as usize; + + for i in 0..self.params_fixed.comp_count as usize { + bytes += self.comp_param_table[i].codec_size_in_bytes(); + } + bytes + } +} + +impl PldmCodec for FirmwareParameters { + fn encode(&self, buffer: &mut [u8]) -> Result { + let mut offset = 0; + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + self.params_fixed + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + + offset += core::mem::size_of::(); + + let len = self.params_fixed.active_comp_image_set_ver_str_len as usize; + buffer[offset..offset + len].copy_from_slice(&self.active_comp_image_set_ver_str[..len]); + offset += len; + + if let Some(pending_comp_image_set_ver_str) = &self.pending_comp_image_set_ver_str { + let len = self.params_fixed.pending_comp_image_set_ver_str_len as usize; + buffer[offset..offset + len].copy_from_slice(&pending_comp_image_set_ver_str[..len]); + offset += len; + } + + for i in 0..self.params_fixed.comp_count as usize { + let bytes = self.comp_param_table[i].encode(&mut buffer[offset..])?; + offset += bytes; + } + + Ok(offset) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + + let params_fixed = FirmwareParamFixed::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let mut active_comp_image_set_ver_str = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = params_fixed.active_comp_image_set_ver_str_len as usize; + active_comp_image_set_ver_str[..len].copy_from_slice( + buffer + .get(offset..offset + len) + .ok_or(PldmCodecError::BufferTooShort)?, + ); + + offset += len; + + let pending_comp_image_set_ver_str = if params_fixed.pending_comp_image_set_ver_str_len > 0 + { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = params_fixed.pending_comp_image_set_ver_str_len as usize; + arr[..len].copy_from_slice( + buffer + .get(offset..offset + len) + .ok_or(PldmCodecError::BufferTooShort)?, + ); + Some(arr) + } else { + None + }; + offset += params_fixed.pending_comp_image_set_ver_str_len as usize; + + let mut index = 0; + let comp_param_table: [ComponentParameterEntry; MAX_COMPONENT_COUNT] = + core::array::from_fn(|_| { + if index < params_fixed.comp_count as usize { + let comp_param_table_entry = + ComponentParameterEntry::decode(&buffer[offset..]).unwrap(); + offset += comp_param_table_entry.codec_size_in_bytes(); + index += 1; + comp_param_table_entry + } else { + ComponentParameterEntry::default() // Fill remaining slots with default values + } + }); + + Ok(FirmwareParameters { + params_fixed, + active_comp_image_set_ver_str, + pending_comp_image_set_ver_str, + comp_param_table, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +#[repr(C)] +pub struct GetFirmwareParametersResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub parms: FirmwareParameters, +} + +impl GetFirmwareParametersResponse { + pub fn new(instance_id: InstanceId, completion_code: u8, parms: &FirmwareParameters) -> Self { + GetFirmwareParametersResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetFirmwareParameters as u8, + ), + completion_code, + parms: parms.clone(), + } + } + + // Calculate the size of the response in bytes for encoding + pub fn codec_size_in_bytes(&self) -> usize { + let mut bytes = 0; + bytes += + PLDM_MSG_HEADER_LEN + core::mem::size_of::() + self.parms.codec_size_in_bytes(); + bytes + } +} + +impl PldmCodec for GetFirmwareParametersResponse { + fn encode(&self, buffer: &mut [u8]) -> Result { + let mut offset = 0; + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + self.hdr + .write_to(&mut buffer[offset..offset + PLDM_MSG_HEADER_LEN]) + .unwrap(); + offset += PLDM_MSG_HEADER_LEN; + + self.completion_code + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + let bytes = self.parms.encode(&mut buffer[offset..])?; + offset += bytes; + Ok(offset) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + + let hdr = PldmMsgHeader::read_from_bytes( + buffer + .get(offset..offset + PLDM_MSG_HEADER_LEN) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + + offset += PLDM_MSG_HEADER_LEN; + let completion_code = u8::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let parms = FirmwareParameters::decode(&buffer[offset..])?; + Ok(GetFirmwareParametersResponse { + hdr, + completion_code, + parms, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + use crate::protocol::firmware_update::{ + ComponentActivationMethods, ComponentClassification, FirmwareDeviceCapability, + PldmFirmwareString, PldmFirmwareVersion, + }; + + fn construct_firmware_params() -> FirmwareParameters { + // Construct firmware params + let active_firmware_string = PldmFirmwareString::new("ASCII", "mcu-runtime-1.0").unwrap(); + let active_firmware_version = + PldmFirmwareVersion::new(0x12345678, &active_firmware_string, Some("20250210")); + let pending_firmware_string = PldmFirmwareString::new("ASCII", "mcu-runtime-1.5").unwrap(); + let pending_firmware_version = + PldmFirmwareVersion::new(0x87654321, &pending_firmware_string, Some("20250213")); + let comp_activation_methods = ComponentActivationMethods(0x0001); + let capabilities_during_update = FirmwareDeviceCapability(0x0010); + let component_parameter_entry = ComponentParameterEntry::new( + ComponentClassification::Firmware, + 0x0001, + 0x01, + &active_firmware_version, + &pending_firmware_version, + comp_activation_methods, + capabilities_during_update, + ); + + const COMP_COUNT: usize = 8; + let comp_param_table: [ComponentParameterEntry; COMP_COUNT] = + core::array::from_fn(|_| component_parameter_entry.clone()); + FirmwareParameters::new( + capabilities_during_update, + COMP_COUNT as u16, + &active_firmware_string, + &pending_firmware_string, + &comp_param_table, + ) + } + + #[test] + fn test_get_firmware_parameters_request() { + 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] + fn test_get_firmware_parameters() { + 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] + fn test_get_firmware_parameters_response() { + 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); + } +} diff --git a/pldm-common/src/message/firmware_update/get_status.rs b/pldm-common/src/message/firmware_update/get_status.rs new file mode 100644 index 0000000..42c5baa --- /dev/null +++ b/pldm-common/src/message/firmware_update/get_status.rs @@ -0,0 +1,225 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::{FirmwareDeviceState, FwUpdateCmd, UpdateOptionFlags}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub const PROGRESS_PERCENT_NOT_SUPPORTED: u8 = 101; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ProgressPercent(u8); + +impl Default for ProgressPercent { + fn default() -> Self { + ProgressPercent::new(PROGRESS_PERCENT_NOT_SUPPORTED).unwrap() + } +} +impl ProgressPercent { + pub fn new(value: u8) -> Result { + if value > PROGRESS_PERCENT_NOT_SUPPORTED { + Err(PldmError::InvalidData) + } else { + Ok(ProgressPercent(value)) + } + } + + pub fn value(&self) -> u8 { + self.0 + } + + pub fn set_value(&mut self, value: u8) -> Result<(), PldmError> { + if value > 100 { + Err(PldmError::InvalidData) + } else { + self.0 = value; + Ok(()) + } + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AuxState { + OperationInProgress = 0, + OperationSuccessful = 1, + OperationFailed = 2, + IdleLearnComponentsReadXfer = 3, +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AuxStateStatus { + AuxStateInProgressOrSuccess = 0x00, + Reserved, + Timeout = 0x09, + GenericError = 0x0a, + VendorDefined, +} + +impl TryFrom for AuxStateStatus { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(AuxStateStatus::AuxStateInProgressOrSuccess), + 0x01..=0x08 => Ok(AuxStateStatus::Reserved), + 0x09 => Ok(AuxStateStatus::Timeout), + 0x0a => Ok(AuxStateStatus::GenericError), + 0x70..=0xef => Ok(AuxStateStatus::VendorDefined), + _ => Err(PldmError::InvalidAuxStateStatus), + } + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GetStatusReasonCode { + Initialization = 0, + ActivateFw = 1, + CancelUpdate = 2, + LearnComponentTimeout = 3, + ReadyXferTimeout = 4, + DownloadTimeout = 5, + VerifyTimeout = 6, + ApplyTimeout = 7, + VendorDefined, +} + +impl TryFrom for GetStatusReasonCode { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(GetStatusReasonCode::Initialization), + 1 => Ok(GetStatusReasonCode::ActivateFw), + 2 => Ok(GetStatusReasonCode::CancelUpdate), + 3 => Ok(GetStatusReasonCode::LearnComponentTimeout), + 4 => Ok(GetStatusReasonCode::ReadyXferTimeout), + 5 => Ok(GetStatusReasonCode::DownloadTimeout), + 6 => Ok(GetStatusReasonCode::VerifyTimeout), + 7 => Ok(GetStatusReasonCode::ApplyTimeout), + 200..=255 => Ok(GetStatusReasonCode::VendorDefined), + _ => Err(PldmError::InvalidGetStatusReasonCode), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct GetStatusRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl GetStatusRequest { + pub fn new(instance_id: InstanceId, msg_type: PldmMsgType) -> GetStatusRequest { + GetStatusRequest { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetStatus as u8, + ), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +#[repr(u8)] +pub enum UpdateOptionResp { + NoForceUpdate = 0, + ForceUpdate = 1, +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct GetStatusResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub current_state: u8, + pub previous_state: u8, + pub aux_state: u8, + pub aux_state_status: u8, + pub progress_percent: u8, + pub reason_code: u8, + pub update_option_flags_enabled: u32, // Assuming bitfield32_t is a 32-bit integer +} + +#[allow(clippy::too_many_arguments)] +impl GetStatusResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + current_state: FirmwareDeviceState, + previous_state: FirmwareDeviceState, + aux_state: AuxState, + aux_state_status: u8, + progress_percent: ProgressPercent, + reason_code: GetStatusReasonCode, + update_option: UpdateOptionResp, + ) -> GetStatusResponse { + GetStatusResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::GetStatus as u8, + ), + completion_code, + current_state: current_state as u8, + previous_state: previous_state as u8, + aux_state: aux_state as u8, + aux_state_status, + progress_percent: progress_percent.value(), + reason_code: reason_code as u8, + update_option_flags_enabled: { + let mut flags = UpdateOptionFlags(0); + flags.set_request_force_update(update_option == UpdateOptionResp::ForceUpdate); + flags.0 + }, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_get_status_request() { + let instance_id = 1; + let msg_type = PldmMsgType::Request; + let request = GetStatusRequest::new(instance_id, msg_type); + let mut buffer = [0u8; 16]; + let encoded_size = request.encode(&mut buffer).unwrap(); + assert_eq!(encoded_size, core::mem::size_of::()); + + let decoded_request = GetStatusRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_get_status_response() { + let response = GetStatusResponse::new( + 1, + 0, + FirmwareDeviceState::Idle, + FirmwareDeviceState::Idle, + AuxState::IdleLearnComponentsReadXfer, + AuxStateStatus::AuxStateInProgressOrSuccess as u8, + ProgressPercent::new(50).unwrap(), + GetStatusReasonCode::Initialization, + UpdateOptionResp::NoForceUpdate, + ); + + let mut buffer = [0u8; 32]; + let encoded_size = response.encode(&mut buffer).unwrap(); + assert_eq!(encoded_size, core::mem::size_of::()); + + let decoded_response = GetStatusResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/pldm-common/src/message/firmware_update/mod.rs b/pldm-common/src/message/firmware_update/mod.rs new file mode 100644 index 0000000..859a174 --- /dev/null +++ b/pldm-common/src/message/firmware_update/mod.rs @@ -0,0 +1,14 @@ +// Licensed under the Apache-2.0 license + +pub mod activate_fw; +pub mod apply_complete; +pub mod get_fw_params; +pub mod get_status; +pub mod pass_component; +pub mod query_devid; +pub mod request_cancel; +pub mod request_fw_data; +pub mod request_update; +pub mod transfer_complete; +pub mod update_component; +pub mod verify_complete; diff --git a/pldm-common/src/message/firmware_update/pass_component.rs b/pldm-common/src/message/firmware_update/pass_component.rs new file mode 100644 index 0000000..a8d2b7c --- /dev/null +++ b/pldm-common/src/message/firmware_update/pass_component.rs @@ -0,0 +1,189 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError}; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, TransferRespFlag, + PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::{ + ComponentClassification, ComponentResponse, ComponentResponseCode, FwUpdateCmd, + PldmFirmwareString, PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN, +}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(C)] +pub struct PassComponentTableRequest { + pub fixed: PassComponentTableRequestFixed, + pub comp_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], +} + +#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct PassComponentTableRequestFixed { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub transfer_flag: u8, + pub comp_classification: u16, + pub comp_identifier: u16, + pub comp_classification_index: u8, + pub comp_comparison_stamp: u32, + pub comp_ver_str_type: u8, + pub comp_ver_str_len: u8, +} + +#[allow(clippy::too_many_arguments)] +impl PassComponentTableRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + transfer_flag: TransferRespFlag, + comp_classification: ComponentClassification, + comp_identifier: u16, + comp_classification_index: u8, + comp_comparison_stamp: u32, + comp_version_string: &PldmFirmwareString, + ) -> PassComponentTableRequest { + PassComponentTableRequest { + fixed: PassComponentTableRequestFixed { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::PassComponentTable as u8, + ), + transfer_flag: transfer_flag as u8, + comp_classification: comp_classification as u16, + comp_identifier, + comp_classification_index, + comp_comparison_stamp, + comp_ver_str_type: comp_version_string.str_type, + comp_ver_str_len: comp_version_string.str_len, + }, + comp_ver_str: { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = comp_version_string.str_data.len(); + arr[..len].copy_from_slice(&comp_version_string.str_data); + arr + }, + } + } + + pub fn codec_size_in_bytes(&self) -> usize { + let mut bytes = core::mem::size_of::(); + bytes += self.fixed.comp_ver_str_len as usize; + bytes + } +} + +impl PldmCodec for PassComponentTableRequest { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + self.fixed + .write_to( + &mut buffer + [offset..offset + core::mem::size_of::()], + ) + .unwrap(); + offset += core::mem::size_of::(); + + let str_len = self.fixed.comp_ver_str_len as usize; + buffer[offset..offset + str_len].copy_from_slice(&self.comp_ver_str[..str_len]); + Ok(offset + str_len) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + let fixed = PassComponentTableRequestFixed::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let str_len = fixed.comp_ver_str_len as usize; + let mut comp_ver_str = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + comp_ver_str[..str_len].copy_from_slice( + &buffer + .get(offset..offset + str_len) + .ok_or(PldmCodecError::BufferTooShort)?[..str_len], + ); + + Ok(PassComponentTableRequest { + fixed, + comp_ver_str, + }) + } +} +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq, Default)] +#[repr(C, packed)] +pub struct PassComponentTableResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub comp_resp: u8, + pub comp_resp_code: u8, +} + +impl PassComponentTableResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + comp_resp: ComponentResponse, + comp_resp_code: ComponentResponseCode, + ) -> PassComponentTableResponse { + PassComponentTableResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::PassComponentTable as u8, + ), + completion_code, + comp_resp: comp_resp as u8, + comp_resp_code: comp_resp_code as u8, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pass_component_table_request() { + let request = PassComponentTableRequest::new( + 1, + PldmMsgType::Request, + TransferRespFlag::StartAndEnd, + ComponentClassification::Firmware, + 2, + 3, + 4, + &PldmFirmwareString::new("UTF-8", "bmc-fw-1.2.0").unwrap(), + ); + + let mut buffer = [0u8; 1024]; + let encoded_size = request.encode(&mut buffer).unwrap(); + let decoded_request = PassComponentTableRequest::decode(&buffer[..encoded_size]).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_pass_component_table_response() { + let response = PassComponentTableResponse::new( + 0, + 0, + ComponentResponse::CompCanBeUpdated, + ComponentResponseCode::CompCanBeUpdated, + ); + + let mut buffer = [0u8; 1024]; + let encoded_size = response.encode(&mut buffer).unwrap(); + let decoded_response = PassComponentTableResponse::decode(&buffer[..encoded_size]).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/pldm-common/src/message/firmware_update/query_devid.rs b/pldm-common/src/message/firmware_update/query_devid.rs new file mode 100644 index 0000000..d17a980 --- /dev/null +++ b/pldm-common/src/message/firmware_update/query_devid.rs @@ -0,0 +1,250 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError}; +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::{Descriptor, FwUpdateCmd}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub const ADDITIONAL_DESCRIPTORS_MAX_COUNT: usize = 4; // Arbitrary limit for static storage + +#[derive(Debug, Clone, FromBytes, IntoBytes, PartialEq, Immutable)] +#[repr(C, packed)] +pub struct QueryDeviceIdentifiersRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl QueryDeviceIdentifiersRequest { + pub fn new(instance_id: InstanceId, message_type: PldmMsgType) -> Self { + QueryDeviceIdentifiersRequest { + hdr: PldmMsgHeader::new( + instance_id, + message_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::QueryDeviceIdentifiers as u8, + ), + } + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +#[repr(C)] +pub struct QueryDeviceIdentifiersResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub device_identifiers_len: u32, + pub descriptor_count: u8, + pub initial_descriptor: Descriptor, + pub additional_descriptors: Option<[Descriptor; ADDITIONAL_DESCRIPTORS_MAX_COUNT]>, +} + +impl QueryDeviceIdentifiersResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + initial_descriptor: &Descriptor, + additional_descriptors: Option<&[Descriptor]>, + ) -> Result { + let descriptor_count = + 1 + additional_descriptors.map_or(0, |descriptors| descriptors.len()); + if descriptor_count > ADDITIONAL_DESCRIPTORS_MAX_COUNT + 1 { + return Err(PldmError::InvalidDescriptorCount); + } + + Ok(QueryDeviceIdentifiersResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::QueryDeviceIdentifiers as u8, + ), + completion_code, + device_identifiers_len: { + let mut len = initial_descriptor.codec_size_in_bytes(); + if let Some(additional) = additional_descriptors { + for descriptor in additional.iter() { + len += descriptor.codec_size_in_bytes(); + } + } + len as u32 + }, + descriptor_count: descriptor_count as u8, + initial_descriptor: *initial_descriptor, + additional_descriptors: if descriptor_count > 1 { + if let Some(additional) = additional_descriptors { + let mut descriptors = + [Descriptor::new_empty(); ADDITIONAL_DESCRIPTORS_MAX_COUNT]; + descriptors[..additional.len()].copy_from_slice(additional); + Some(descriptors) + } else { + None + } + } else { + None + }, + }) + } + + pub fn codec_size_in_bytes(&self) -> usize { + let mut size = PLDM_MSG_HEADER_LEN + + core::mem::size_of::() + + core::mem::size_of::() + + core::mem::size_of::(); + size += self.initial_descriptor.codec_size_in_bytes(); + + if let Some(additional_descriptors) = &self.additional_descriptors { + for descriptor in additional_descriptors + .iter() + .take(self.descriptor_count as usize - 1) + { + size += descriptor.codec_size_in_bytes(); + } + } + size + } +} + +impl PldmCodec for QueryDeviceIdentifiersResponse { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + let mut offset = 0; + self.hdr + .write_to(&mut buffer[offset..offset + PLDM_MSG_HEADER_LEN]) + .unwrap(); + offset += PLDM_MSG_HEADER_LEN; + + self.completion_code + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.device_identifiers_len + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.descriptor_count + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + let bytes = self.initial_descriptor.encode(&mut buffer[offset..])?; + offset += bytes; + + if let Some(additional_descriptors) = &self.additional_descriptors { + for descriptor in additional_descriptors + .iter() + .take(self.descriptor_count as usize - 1) + { + let bytes = descriptor.encode(&mut buffer[offset..])?; + offset += bytes; + } + } + Ok(offset) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + + let hdr = PldmMsgHeader::<[u8; PLDM_MSG_HEADER_LEN]>::read_from_bytes( + buffer + .get(offset..offset + PLDM_MSG_HEADER_LEN) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += PLDM_MSG_HEADER_LEN; + + let completion_code = u8::read_from_bytes( + buffer + .get(offset..offset + 1) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += 1; + + let device_identifiers_len = u32::read_from_bytes( + buffer + .get(offset..offset + 4) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += 4; + + let descriptor_count = u8::read_from_bytes( + buffer + .get(offset..offset + 1) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += 1; + + let initial_descriptor = Descriptor::decode(&buffer[offset..])?; + offset += Descriptor::codec_size_in_bytes(&initial_descriptor); + + let additional_descriptors = if descriptor_count > 1 { + let mut descriptors = [Descriptor::new_empty(); ADDITIONAL_DESCRIPTORS_MAX_COUNT]; + let count = descriptor_count as usize - 1; + for descriptor in descriptors.iter_mut().take(count) { + *descriptor = Descriptor::decode(&buffer[offset..])?; + offset += Descriptor::codec_size_in_bytes(descriptor); + } + Some(descriptors) + } else { + None + }; + + Ok(QueryDeviceIdentifiersResponse { + hdr, + completion_code, + device_identifiers_len, + descriptor_count, + initial_descriptor, + additional_descriptors, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::protocol::firmware_update::{Descriptor, DescriptorType}; + + #[test] + fn test_query_device_identifiers_resp() { + let instance_id = 0; + let completion_code = 0; + + let test_uid = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, + ]; + let initial_descriptor = Descriptor::new(DescriptorType::Uuid, &test_uid).unwrap(); + let additional_descriptor = Descriptor::new(DescriptorType::Uuid, &test_uid).unwrap(); + + let resp = QueryDeviceIdentifiersResponse::new( + instance_id, + completion_code, + &initial_descriptor, + Some(&[additional_descriptor]), + ) + .unwrap(); + + assert_eq!(resp.descriptor_count, 2); + assert_eq!( + resp.device_identifiers_len, + (initial_descriptor.codec_size_in_bytes() + additional_descriptor.codec_size_in_bytes()) + as u32 + ); + + let mut buffer: [u8; 256] = [0; 256]; + let resp_len = resp.encode(&mut buffer).unwrap(); + assert_eq!(resp_len, resp.codec_size_in_bytes()); + + let resp_decoded = QueryDeviceIdentifiersResponse::decode(&buffer).unwrap(); + assert_eq!(resp, resp_decoded); + } +} diff --git a/pldm-common/src/message/firmware_update/request_cancel.rs b/pldm-common/src/message/firmware_update/request_cancel.rs new file mode 100644 index 0000000..905698f --- /dev/null +++ b/pldm-common/src/message/firmware_update/request_cancel.rs @@ -0,0 +1,157 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::FwUpdateCmd; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[repr(u8)] +pub enum NonFunctioningComponentIndication { + ComponentsFunctioning = 0, + ComponentsNotFunctioning = 1, +} + +impl TryFrom for NonFunctioningComponentIndication { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(NonFunctioningComponentIndication::ComponentsFunctioning), + 1 => Ok(NonFunctioningComponentIndication::ComponentsNotFunctioning), + _ => Err(PldmError::InvalidData), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable)] +#[repr(C, packed)] +pub struct CancelUpdateComponentRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl CancelUpdateComponentRequest { + pub fn new(instance_id: InstanceId, msg_type: PldmMsgType) -> Self { + CancelUpdateComponentRequest { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::CancelUpdateComponent as u8, + ), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable)] +#[repr(C, packed)] +pub struct CancelUpdateComponentResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, +} + +impl CancelUpdateComponentResponse { + pub fn new(instance_id: InstanceId, completion_code: u8) -> CancelUpdateComponentResponse { + CancelUpdateComponentResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::CancelUpdateComponent as u8, + ), + completion_code, + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct CancelUpdateRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, +} + +impl CancelUpdateRequest { + pub fn new(instance_id: InstanceId, msg_type: PldmMsgType) -> Self { + CancelUpdateRequest { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::CancelUpdate as u8, + ), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable)] +pub struct NonFunctioningComponentBitmap(u64); + +impl NonFunctioningComponentBitmap { + pub fn new(value: u64) -> Self { + NonFunctioningComponentBitmap(value) + } + + pub fn value(&self) -> u64 { + self.0 + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct CancelUpdateResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub non_functioning_component_indication: u8, + pub non_functioning_component_bitmap: u64, +} + +impl CancelUpdateResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + non_functioning_component_indication: NonFunctioningComponentIndication, + non_functioning_component_bitmap: NonFunctioningComponentBitmap, + ) -> CancelUpdateResponse { + CancelUpdateResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::CancelUpdate as u8, + ), + completion_code, + non_functioning_component_indication: non_functioning_component_indication as u8, + non_functioning_component_bitmap: non_functioning_component_bitmap.value(), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_cancel_update_request() { + let cancel_update_request = CancelUpdateRequest::new(0x01, PldmMsgType::Request); + let mut buffer = [0u8; core::mem::size_of::()]; + cancel_update_request.encode(&mut buffer).unwrap(); + let decoded_request = CancelUpdateRequest::decode(&buffer).unwrap(); + assert_eq!(cancel_update_request, decoded_request); + } + + #[test] + fn test_cancel_update_response() { + let response = CancelUpdateResponse::new( + 0x01, + 0x00, + NonFunctioningComponentIndication::ComponentsFunctioning, + NonFunctioningComponentBitmap::new(0x00), + ); + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = CancelUpdateResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/pldm-common/src/message/firmware_update/request_fw_data.rs b/pldm-common/src/message/firmware_update/request_fw_data.rs new file mode 100644 index 0000000..432b124 --- /dev/null +++ b/pldm-common/src/message/firmware_update/request_fw_data.rs @@ -0,0 +1,129 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError}; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::FwUpdateCmd; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub const MAX_TRANSFER_SIZE: usize = 512; // Define an appropriate size + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct RequestFirmwareDataRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub offset: u32, + pub length: u32, +} + +impl RequestFirmwareDataRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + offset: u32, + length: u32, + ) -> RequestFirmwareDataRequest { + let hdr = PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::RequestFirmwareData as u8, + ); + RequestFirmwareDataRequest { + hdr, + offset, + length, + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct RequestFirmwareDataResponseFixed { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, +} + +#[derive(Debug, Clone, PartialEq)] +#[repr(C)] +pub struct RequestFirmwareDataResponse<'a> { + pub fixed: RequestFirmwareDataResponseFixed, + pub data: &'a [u8], +} + +impl RequestFirmwareDataResponse<'_> { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + data: &[u8], + ) -> RequestFirmwareDataResponse { + let fixed = RequestFirmwareDataResponseFixed { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::RequestFirmwareData as u8, + ), + completion_code, + }; + RequestFirmwareDataResponse { fixed, data } + } + + pub fn codec_size_in_bytes(&self) -> usize { + let mut bytes = core::mem::size_of::(); + bytes += self.data.len(); + bytes + } +} + +impl PldmCodec for RequestFirmwareDataResponse<'_> { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + let bytes = core::mem::size_of::(); + self.fixed + .write_to(&mut buffer[offset..offset + bytes]) + .unwrap(); + offset += bytes; + + let data_len = self.data.len(); + if data_len > MAX_TRANSFER_SIZE { + return Err(PldmCodecError::BufferTooShort); + } + buffer[offset..offset + data_len].copy_from_slice(self.data); + Ok(bytes + data_len) + } + + // 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) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_request_firmware_data_request() { + 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() { + 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()); + } +} diff --git a/pldm-common/src/message/firmware_update/request_update.rs b/pldm-common/src/message/firmware_update/request_update.rs new file mode 100644 index 0000000..9c8b154 --- /dev/null +++ b/pldm-common/src/message/firmware_update/request_update.rs @@ -0,0 +1,268 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError}; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::{ + FwUpdateCmd, PldmFirmwareString, PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN, +}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(C)] +pub struct RequestUpdateRequest { + pub fixed: RequestUpdateRequestFixed, + pub comp_image_set_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], +} + +#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct RequestUpdateRequestFixed { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub max_transfer_size: u32, + pub num_of_comp: u16, + pub max_outstanding_transfer_req: u8, + pub pkg_data_len: u16, + pub comp_image_set_ver_str_type: u8, + pub comp_image_set_ver_str_len: u8, +} + +impl RequestUpdateRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + max_transfer_size: u32, + num_of_comp: u16, + max_outstanding_transfer_req: u8, + pkg_data_len: u16, + comp_image_set_version_string: &PldmFirmwareString, + ) -> Self { + RequestUpdateRequest { + fixed: RequestUpdateRequestFixed { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::RequestUpdate as u8, + ), + max_transfer_size, + num_of_comp, + max_outstanding_transfer_req, + pkg_data_len, + comp_image_set_ver_str_type: comp_image_set_version_string.str_type, + comp_image_set_ver_str_len: comp_image_set_version_string.str_len, + }, + comp_image_set_ver_str: { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = comp_image_set_version_string.str_data.len(); + arr[..len].copy_from_slice(&comp_image_set_version_string.str_data); + arr + }, + } + } + + pub fn get_comp_image_set_ver_str(&self) -> PldmFirmwareString { + PldmFirmwareString { + str_type: self.fixed.comp_image_set_ver_str_type, + str_len: self.fixed.comp_image_set_ver_str_len, + str_data: { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + arr.copy_from_slice( + &self.comp_image_set_ver_str[..self.fixed.comp_image_set_ver_str_len as usize], + ); + arr + }, + } + } + + pub fn codec_size_in_bytes(&self) -> usize { + let mut bytes = core::mem::size_of::(); + bytes += self.fixed.comp_image_set_ver_str_len as usize; + bytes + } +} + +impl PldmCodec for RequestUpdateRequest { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + self.fixed + .write_to( + &mut buffer[offset..offset + core::mem::size_of::()], + ) + .unwrap(); + offset += core::mem::size_of::(); + + let str_len = self.fixed.comp_image_set_ver_str_len as usize; + buffer[offset..offset + str_len].copy_from_slice(&self.comp_image_set_ver_str[..str_len]); + Ok(offset + str_len) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + let fixed = RequestUpdateRequestFixed::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let str_len = fixed.comp_image_set_ver_str_len as usize; + let mut comp_image_set_ver_str = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + comp_image_set_ver_str[..str_len].copy_from_slice( + &buffer + .get(offset..offset + str_len) + .ok_or(PldmCodecError::BufferTooShort)?[..str_len], + ); + + Ok(RequestUpdateRequest { + fixed, + comp_image_set_ver_str, + }) + } +} + +#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, PartialEq, Default)] +#[repr(C, packed)] +pub struct RequestUpdateResponseFixed { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub fd_meta_data_len: u16, + pub fd_will_send_pkg_data_cmd: u8, +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct RequestUpdateResponse { + pub fixed: RequestUpdateResponseFixed, + // This field is only present if FDWillSendGetPackageDataCommand is set to 0x02. + pub get_pkg_data_max_transfer_size: Option, +} + +impl RequestUpdateResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + fd_meta_data_len: u16, + fd_will_send_pkg_data_cmd: u8, + get_pkg_data_max_transfer_size: Option, + ) -> RequestUpdateResponse { + RequestUpdateResponse { + fixed: RequestUpdateResponseFixed { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::RequestUpdate as u8, + ), + completion_code, + fd_meta_data_len, + fd_will_send_pkg_data_cmd, + }, + get_pkg_data_max_transfer_size, + } + } + + pub fn codec_size_in_bytes(&self) -> usize { + let mut bytes = core::mem::size_of::(); + if self.fixed.fd_will_send_pkg_data_cmd == 0x02 { + bytes += core::mem::size_of::(); + } + bytes + } +} + +impl PldmCodec for RequestUpdateResponse { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + self.fixed + .write_to( + &mut buffer[offset..offset + core::mem::size_of::()], + ) + .unwrap(); + offset += core::mem::size_of::(); + + if let Some(size) = self.get_pkg_data_max_transfer_size { + size.write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + } + + Ok(offset) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + let fixed = RequestUpdateResponseFixed::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let get_pkg_data_max_transfer_size = if fixed.fd_will_send_pkg_data_cmd == 0x02 { + Some( + u32::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(), + ) + } else { + None + }; + + Ok(RequestUpdateResponse { + fixed, + get_pkg_data_max_transfer_size, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::protocol::firmware_update::PldmFirmwareString; + + #[test] + fn test_request_update_request() { + let request = RequestUpdateRequest::new( + 0, + PldmMsgType::Request, + 512, + 2, + 1, + 256, + &PldmFirmwareString::new("ASCII", "mcu-1.0.0").unwrap(), + ); + + let mut buffer = [0u8; 512]; + let encoded_size = request.encode(&mut buffer).unwrap(); + assert_eq!(encoded_size, request.codec_size_in_bytes()); + + let decoded_request = RequestUpdateRequest::decode(&buffer[..encoded_size]).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_request_update_response() { + let response = RequestUpdateResponse::new(1, 0, 128, 0x02, Some(2048)); + + let mut buffer = [0u8; 512]; + let encoded_size = response.encode(&mut buffer).unwrap(); + assert_eq!(encoded_size, response.codec_size_in_bytes()); + + let decoded_response = RequestUpdateResponse::decode(&buffer[..encoded_size]).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/pldm-common/src/message/firmware_update/transfer_complete.rs b/pldm-common/src/message/firmware_update/transfer_complete.rs new file mode 100644 index 0000000..fae32c8 --- /dev/null +++ b/pldm-common/src/message/firmware_update/transfer_complete.rs @@ -0,0 +1,124 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::FwUpdateCmd; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum TransferResult { + TransferSuccess = 0x00, + TransferErrorImageCorrupt = 0x01, + TransferErrorVersionMismatch = 0x02, + FdAbortedTransfer = 0x03, + TransferTimeOut = 0x09, + #[default] + TransferGenericError = 0x0a, + FdAbortedTransferLowPowerState = 0x0b, + FdAbortedTransferResetNeeded = 0x0c, + FdAbortedTransferStorageIssue = 0x0d, + FdAbortedTransferInvalidComponentOpaqueData = 0x0e, + FdAbortedTransferDownstreamDeviceIssue = 0x0f, + FdAbortedTransferSecurityRevisionError = 0x10, + VendorDefined, // 0x70..=0x8f +} + +impl TryFrom for TransferResult { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(TransferResult::TransferSuccess), + 0x01 => Ok(TransferResult::TransferErrorImageCorrupt), + 0x02 => Ok(TransferResult::TransferErrorVersionMismatch), + 0x03 => Ok(TransferResult::FdAbortedTransfer), + 0x09 => Ok(TransferResult::TransferTimeOut), + 0x0a => Ok(TransferResult::TransferGenericError), + 0x0b => Ok(TransferResult::FdAbortedTransferLowPowerState), + 0x0c => Ok(TransferResult::FdAbortedTransferResetNeeded), + 0x0d => Ok(TransferResult::FdAbortedTransferStorageIssue), + 0x0e => Ok(TransferResult::FdAbortedTransferInvalidComponentOpaqueData), + 0x0f => Ok(TransferResult::FdAbortedTransferDownstreamDeviceIssue), + 0x10 => Ok(TransferResult::FdAbortedTransferSecurityRevisionError), + 0x70..=0x8f => Ok(TransferResult::VendorDefined), + _ => Err(PldmError::InvalidTransferResult), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct TransferCompleteRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub tranfer_result: u8, +} + +impl TransferCompleteRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + tranfer_result: TransferResult, + ) -> Self { + TransferCompleteRequest { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::TransferComplete as u8, + ), + tranfer_result: tranfer_result as u8, + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct TransferCompleteResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, +} + +impl TransferCompleteResponse { + pub fn new(instance_id: InstanceId, completion_code: u8) -> TransferCompleteResponse { + TransferCompleteResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::TransferComplete as u8, + ), + completion_code, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_transfer_complete_request() { + let request = TransferCompleteRequest::new( + 0x01, + PldmMsgType::Request, + TransferResult::TransferSuccess, + ); + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded_request = TransferCompleteRequest::decode(&buffer).unwrap(); + assert_eq!(decoded_request, request); + } + + #[test] + fn test_transfer_complete_response() { + let response = TransferCompleteResponse::new(0x01, 0x00); + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = TransferCompleteResponse::decode(&buffer).unwrap(); + assert_eq!(decoded_response, response); + } +} diff --git a/pldm-common/src/message/firmware_update/update_component.rs b/pldm-common/src/message/firmware_update/update_component.rs new file mode 100644 index 0000000..30f5384 --- /dev/null +++ b/pldm-common/src/message/firmware_update/update_component.rs @@ -0,0 +1,338 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError}; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::{ + ComponentClassification, ComponentCompatibilityResponse, ComponentCompatibilityResponseCode, + FwUpdateCmd, PldmFirmwareString, UpdateOptionFlags, PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN, +}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct UpdateComponentRequestFixed { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub comp_classification: u16, + pub comp_identifier: u16, + pub comp_classification_index: u8, + pub comp_comparison_stamp: u32, + pub comp_image_size: u32, + pub update_option_flags: u32, + pub comp_ver_str_type: u8, + pub comp_ver_str_len: u8, +} + +#[derive(Debug, Clone, PartialEq)] +#[repr(C)] +pub struct UpdateComponentRequest { + pub fixed: UpdateComponentRequestFixed, + pub comp_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], +} + +#[allow(clippy::too_many_arguments)] +impl UpdateComponentRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + comp_classification: ComponentClassification, + comp_identifier: u16, + comp_classification_index: u8, + comp_comparison_stamp: u32, + comp_image_size: u32, + update_option_flags: UpdateOptionFlags, + comp_version_string: &PldmFirmwareString, + ) -> UpdateComponentRequest { + UpdateComponentRequest { + fixed: UpdateComponentRequestFixed { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::UpdateComponent as u8, + ), + comp_classification: comp_classification as u16, + comp_identifier, + comp_classification_index, + comp_comparison_stamp, + comp_image_size, + update_option_flags: update_option_flags.0, + comp_ver_str_type: comp_version_string.str_type, + comp_ver_str_len: comp_version_string.str_len, + }, + comp_ver_str: { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = comp_version_string.str_data.len(); + arr[..len].copy_from_slice(&comp_version_string.str_data); + arr + }, + } + } + + fn codec_size_in_bytes(&self) -> usize { + let mut bytes = 0; + bytes += core::mem::size_of::(); + bytes += self.fixed.comp_ver_str_len as usize; + bytes + } +} + +impl PldmCodec for UpdateComponentRequest { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + let bytes = core::mem::size_of::(); + self.fixed + .write_to(&mut buffer[offset..offset + bytes]) + .unwrap(); + offset += bytes; + + let str_len = self.fixed.comp_ver_str_len as usize; + buffer[offset..offset + str_len].copy_from_slice(&self.comp_ver_str[..str_len]); + Ok(offset + str_len) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + let bytes = core::mem::size_of::(); + let fixed = UpdateComponentRequestFixed::read_from_bytes( + buffer + .get(offset..offset + bytes) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += bytes; + + let comp_ver_str = { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let str_len = fixed.comp_ver_str_len as usize; + arr[..str_len].copy_from_slice(&buffer[offset..offset + str_len]); + arr + }; + Ok(UpdateComponentRequest { + fixed, + comp_ver_str, + }) + } +} + +#[derive(Debug, Clone, PartialEq)] +#[repr(C)] +pub struct UpdateComponentResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, + pub comp_compatibility_resp: u8, + pub comp_compatibility_resp_code: u8, + pub update_option_flags_enabled: u32, + pub time_before_req_fw_data: u16, + pub get_comp_opaque_data_max_transfer_size: Option, +} + +impl UpdateComponentResponse { + pub fn new( + instance_id: InstanceId, + completion_code: u8, + comp_compatibility_resp: ComponentCompatibilityResponse, + comp_compatibility_resp_code: ComponentCompatibilityResponseCode, + update_option_flags_enabled: UpdateOptionFlags, + time_before_req_fw_data: u16, + get_comp_opaque_data_max_transfer_size: Option, + ) -> UpdateComponentResponse { + UpdateComponentResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::UpdateComponent as u8, + ), + completion_code, + comp_compatibility_resp: comp_compatibility_resp as u8, + comp_compatibility_resp_code: comp_compatibility_resp_code as u8, + update_option_flags_enabled: update_option_flags_enabled.0, + time_before_req_fw_data, + get_comp_opaque_data_max_transfer_size, + } + } + + fn codec_size_in_bytes(&self) -> usize { + let mut bytes = PLDM_MSG_HEADER_LEN + + core::mem::size_of::() * 3 + + core::mem::size_of::() + + core::mem::size_of::(); + if self.get_comp_opaque_data_max_transfer_size.is_some() { + bytes += core::mem::size_of::(); + } + bytes + } +} + +impl PldmCodec for UpdateComponentResponse { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + + let mut offset = 0; + self.hdr + .write_to(&mut buffer[offset..offset + PLDM_MSG_HEADER_LEN]) + .unwrap(); + offset += PLDM_MSG_HEADER_LEN; + + self.completion_code + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.comp_compatibility_resp + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.comp_compatibility_resp_code + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.update_option_flags_enabled + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.time_before_req_fw_data + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + if let Some(size) = self.get_comp_opaque_data_max_transfer_size { + size.write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + } + + Ok(offset) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + let hdr = PldmMsgHeader::read_from_bytes( + buffer + .get(offset..offset + PLDM_MSG_HEADER_LEN) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += PLDM_MSG_HEADER_LEN; + + let completion_code = u8::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let comp_compatibility_resp = u8::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let comp_compatibility_resp_code = u8::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let update_option_flags_enabled = u32::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let time_before_req_fw_data = u16::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let update_option_flags = UpdateOptionFlags(update_option_flags_enabled); + + let get_comp_opaque_data_max_transfer_size = if update_option_flags.component_opaque_data() + { + Some( + u32::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(), + ) + } else { + None + }; + + Ok(UpdateComponentResponse { + hdr, + completion_code, + comp_compatibility_resp, + comp_compatibility_resp_code, + update_option_flags_enabled, + time_before_req_fw_data, + get_comp_opaque_data_max_transfer_size, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_update_component_request() { + let request = UpdateComponentRequest::new( + 0, + PldmMsgType::Request, + ComponentClassification::Firmware, + 0x0001, + 0x01, + 0x00000001, + 0x00000001, + UpdateOptionFlags(0x00000002), + &PldmFirmwareString::new("UTF-8", "mcu-fw-1.2.0").unwrap(), + ); + let mut buffer = [0u8; 512]; + let bytes = request.encode(&mut buffer).unwrap(); + assert_eq!(bytes, request.codec_size_in_bytes()); + let decoded_request = UpdateComponentRequest::decode(&buffer[..bytes]).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_update_component_response() { + let response = UpdateComponentResponse::new( + 0, + 0x00, + ComponentCompatibilityResponse::CompCanBeUpdated, + ComponentCompatibilityResponseCode::NoResponseCode, + UpdateOptionFlags(0x00000002), + 0x0001, + Some(0x00000100), + ); + let mut buffer = [0u8; 512]; + let bytes = response.encode(&mut buffer).unwrap(); + assert_eq!(bytes, response.codec_size_in_bytes()); + let decoded_response = UpdateComponentResponse::decode(&buffer[..bytes]).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/pldm-common/src/message/firmware_update/verify_complete.rs b/pldm-common/src/message/firmware_update/verify_complete.rs new file mode 100644 index 0000000..1e7b0cb --- /dev/null +++ b/pldm-common/src/message/firmware_update/verify_complete.rs @@ -0,0 +1,111 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use crate::protocol::base::{ + InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, +}; +use crate::protocol::firmware_update::FwUpdateCmd; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum VerifyResult { + VerifySuccess = 0x00, + VerifyErrorVerificationFailure = 0x01, + VerifyErrorVersionMismatch = 0x02, + VerifyFailedFdSecurityChecks = 0x03, + VerifyErrorImageIncomplete = 0x04, + VerifyTimeOut = 0x09, + #[default] + VerifyGenericError = 0x0a, + VendorDefined, +} + +impl TryFrom for VerifyResult { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(VerifyResult::VerifySuccess), + 0x01 => Ok(VerifyResult::VerifyErrorVerificationFailure), + 0x02 => Ok(VerifyResult::VerifyErrorVersionMismatch), + 0x03 => Ok(VerifyResult::VerifyFailedFdSecurityChecks), + 0x04 => Ok(VerifyResult::VerifyErrorImageIncomplete), + 0x09 => Ok(VerifyResult::VerifyTimeOut), + 0x0a => Ok(VerifyResult::VerifyGenericError), + 0x90..=0xaf => Ok(VerifyResult::VendorDefined), + _ => Err(PldmError::InvalidVerifyResult), + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct VerifyCompleteRequest { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub verify_result: u8, +} + +impl VerifyCompleteRequest { + pub fn new( + instance_id: InstanceId, + msg_type: PldmMsgType, + verify_result: VerifyResult, + ) -> Self { + VerifyCompleteRequest { + hdr: PldmMsgHeader::new( + instance_id, + msg_type, + PldmSupportedType::FwUpdate, + FwUpdateCmd::VerifyComplete as u8, + ), + verify_result: verify_result as u8, + } + } +} + +#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct VerifyCompleteResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, +} + +impl VerifyCompleteResponse { + pub fn new(instance_id: InstanceId, completion_code: u8) -> VerifyCompleteResponse { + VerifyCompleteResponse { + hdr: PldmMsgHeader::new( + instance_id, + PldmMsgType::Response, + PldmSupportedType::FwUpdate, + FwUpdateCmd::VerifyComplete as u8, + ), + completion_code, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_verify_complete_request() { + let request = + VerifyCompleteRequest::new(0x01, PldmMsgType::Request, VerifyResult::VerifySuccess); + let mut buffer = [0u8; core::mem::size_of::()]; + request.encode(&mut buffer).unwrap(); + let decoded_request = VerifyCompleteRequest::decode(&buffer).unwrap(); + assert_eq!(request, decoded_request); + } + + #[test] + fn test_verify_complete_response() { + let response = VerifyCompleteResponse::new(0x01, 0x00); + let mut buffer = [0u8; core::mem::size_of::()]; + response.encode(&mut buffer).unwrap(); + let decoded_response = VerifyCompleteResponse::decode(&buffer).unwrap(); + assert_eq!(response, decoded_response); + } +} diff --git a/pldm-common/src/message/mod.rs b/pldm-common/src/message/mod.rs new file mode 100644 index 0000000..0fbfacb --- /dev/null +++ b/pldm-common/src/message/mod.rs @@ -0,0 +1,4 @@ +// Licensed under the Apache-2.0 license + +pub mod control; +pub mod firmware_update; diff --git a/pldm-common/src/protocol/base.rs b/pldm-common/src/protocol/base.rs new file mode 100644 index 0000000..ccb757a --- /dev/null +++ b/pldm-common/src/protocol/base.rs @@ -0,0 +1,302 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use bitfield::bitfield; +use core::convert::TryFrom; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub const PLDM_MSG_HEADER_LEN: usize = 3; +pub const PLDM_FAILURE_RESP_LEN: usize = 4; +pub type InstanceId = u8; + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum PldmSupportedType { + Base = 0x00, + Platform = 0x02, + Bios = 0x03, + Fru = 0x04, + FwUpdate = 0x05, + Oem = 0x3F, +} + +impl TryFrom for PldmSupportedType { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(PldmSupportedType::Base), + 0x02 => Ok(PldmSupportedType::Platform), + 0x03 => Ok(PldmSupportedType::Bios), + 0x04 => Ok(PldmSupportedType::Fru), + 0x05 => Ok(PldmSupportedType::FwUpdate), + 0x3F => Ok(PldmSupportedType::Oem), + _ => Err(PldmError::UnsupportedPldmType), + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum PldmControlCmd { + SetTid = 0x1, + GetTid = 0x2, + GetPldmVersion = 0x3, + GetPldmTypes = 0x4, + GetPldmCommands = 0x5, +} + +impl TryFrom for PldmControlCmd { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x1 => Ok(PldmControlCmd::SetTid), + 0x2 => Ok(PldmControlCmd::GetTid), + 0x3 => Ok(PldmControlCmd::GetPldmVersion), + 0x4 => Ok(PldmControlCmd::GetPldmTypes), + 0x5 => Ok(PldmControlCmd::GetPldmCommands), + _ => Err(PldmError::UnsupportedCmd), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum PldmMsgType { + Response = 0x00, + Reserved = 0x01, + Request = 0x02, + AsyncRequestNotify = 0x03, +} + +impl TryFrom for PldmMsgType { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(PldmMsgType::Response), + 0x01 => Ok(PldmMsgType::Reserved), + 0x02 => Ok(PldmMsgType::Request), + 0x03 => Ok(PldmMsgType::AsyncRequestNotify), + _ => Err(PldmError::InvalidMsgType), + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum PldmHeaderVersion { + Version0 = 0x00, +} + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum PldmBaseCompletionCode { + Success = 0x00, + Error = 0x01, + InvalidData = 0x02, + InvalidLength = 0x03, + NotReady = 0x04, + UnsupportedPldmCmd = 0x05, + InvalidPldmType = 0x20, +} + +impl TryFrom for PldmBaseCompletionCode { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x00 => Ok(PldmBaseCompletionCode::Success), + 0x01 => Ok(PldmBaseCompletionCode::Error), + 0x02 => Ok(PldmBaseCompletionCode::InvalidData), + 0x03 => Ok(PldmBaseCompletionCode::InvalidLength), + 0x04 => Ok(PldmBaseCompletionCode::NotReady), + 0x05 => Ok(PldmBaseCompletionCode::UnsupportedPldmCmd), + 0x20 => Ok(PldmBaseCompletionCode::InvalidPldmType), + _ => Err(PldmError::InvalidCompletionCode), + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum PldmControlCompletionCode { + InvalidDataTransferHandle = 0x80, + InvalidTransferOperationFlag = 0x81, + InvalidPldmTypeInRequestData = 0x83, + InvalidPldmVersionInRequestData = 0x84, +} + +impl TryFrom for PldmControlCompletionCode { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x80 => Ok(PldmControlCompletionCode::InvalidDataTransferHandle), + 0x81 => Ok(PldmControlCompletionCode::InvalidTransferOperationFlag), + 0x83 => Ok(PldmControlCompletionCode::InvalidPldmTypeInRequestData), + 0x84 => Ok(PldmControlCompletionCode::InvalidPldmVersionInRequestData), + _ => Err(PldmError::InvalidCompletionCode), + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum TransferOperationFlag { + GetNextPart = 0, + GetFirstPart = 1, +} + +impl TryFrom for TransferOperationFlag { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(TransferOperationFlag::GetNextPart), + 1 => Ok(TransferOperationFlag::GetFirstPart), + _ => Err(PldmError::InvalidTransferOpFlag), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum TransferRespFlag { + Start = 0x01, + Middle = 0x02, + End = 0x04, + StartAndEnd = 0x05, +} + +impl TryFrom for TransferRespFlag { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x01 => Ok(TransferRespFlag::Start), + 0x02 => Ok(TransferRespFlag::Middle), + 0x04 => Ok(TransferRespFlag::End), + 0x05 => Ok(TransferRespFlag::StartAndEnd), + _ => Err(PldmError::InvalidTransferRespFlag), + } + } +} + +bitfield! { + #[repr(C)] + #[derive(Copy, Clone, FromBytes, IntoBytes, Immutable, PartialEq, Default)] + pub struct PldmMsgHeader([u8]); + impl Debug; + pub u8, instance_id, set_instance_id: 4, 0; + pub u8, reserved, _: 5, 5; + pub u8, datagram, set_datagram: 6, 6; + pub u8, rq, set_rq: 7, 7; + pub u8, pldm_type, set_pldm_type: 13, 8; + pub u8, hdr_ver, set_hdr_ver: 15, 14; + pub u8, cmd_code, set_command_code: 23, 16; +} + +impl PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]> { + const DATAGRAM_MASK: u8 = 0x01; + const REQUEST_MASK: u8 = 0x01 << 1; + + pub fn new( + instance_id: InstanceId, + message_type: PldmMsgType, + pldm_type: PldmSupportedType, + cmd_code: u8, + ) -> Self { + let mut header = PldmMsgHeader([0; PLDM_MSG_HEADER_LEN]); + header.set_instance_id(instance_id); + header.set_datagram(message_type as u8 & Self::DATAGRAM_MASK); + header.set_rq((message_type as u8 & Self::REQUEST_MASK) >> 1); + header.set_pldm_type(pldm_type as u8); + header.set_hdr_ver(PldmHeaderVersion::Version0 as u8); + header.set_command_code(cmd_code); + header + } + + pub fn is_request(&self) -> bool { + self.rq() == (PldmMsgType::Request as u8 >> 0x01) + } + + pub fn is_hdr_ver_valid(&self) -> bool { + self.hdr_ver() == PldmHeaderVersion::Version0 as u8 + } + + // switch the message type to response + pub fn into_response(&self) -> Self { + let mut header = *self; + header.set_rq(PldmMsgType::Response as u8); + header + } +} + +#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq)] +#[repr(C, packed)] +pub struct PldmFailureResponse { + pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, + pub completion_code: u8, +} + +impl PldmFailureResponse { + pub fn new( + instance_id: InstanceId, + pldm_type: PldmSupportedType, + cmd_code: u8, + completion_code: u8, + ) -> Self { + let hdr = PldmMsgHeader::new(instance_id, PldmMsgType::Response, pldm_type, cmd_code); + PldmFailureResponse { + hdr, + completion_code, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::codec::PldmCodec; + + #[test] + fn test_pldm_msg_header() { + let header = PldmMsgHeader::new( + 0x01, + PldmMsgType::Request, + PldmSupportedType::Base, + PldmControlCmd::GetTid as u8, + ); + assert_eq!(header.0, [0x81, 0x00, 0x02]); + assert!(header.is_request()); + let response = header.into_response(); + assert_eq!(response.0, [0x01, 0x00, 0x02]); + assert_eq!(response.rq(), PldmMsgType::Response as u8); + + let mut buffer = [0; PLDM_MSG_HEADER_LEN]; + let size = header.encode(&mut buffer).unwrap(); + assert_eq!(size, PLDM_MSG_HEADER_LEN); + + let decoded_header = PldmMsgHeader::decode(&buffer).unwrap(); + assert_eq!(header, decoded_header); + } + + #[test] + fn test_pldm_failure_resp() { + let resp = PldmFailureResponse::new( + 0x01, + PldmSupportedType::Base, + PldmControlCmd::GetTid as u8, + PldmBaseCompletionCode::Success as u8, + ); + + let mut buffer = [0; PLDM_FAILURE_RESP_LEN]; + let size = resp.encode(&mut buffer).unwrap(); + assert_eq!(size, PLDM_FAILURE_RESP_LEN); + + let decoded_resp = PldmFailureResponse::decode(&buffer).unwrap(); + assert_eq!(resp, decoded_resp); + } +} diff --git a/pldm-common/src/protocol/firmware_update.rs b/pldm-common/src/protocol/firmware_update.rs new file mode 100644 index 0000000..f959f50 --- /dev/null +++ b/pldm-common/src/protocol/firmware_update.rs @@ -0,0 +1,884 @@ +// Licensed under the Apache-2.0 license + +use crate::codec::{PldmCodec, PldmCodecError}; +use crate::error::PldmError; +use bitfield::bitfield; +use core::convert::TryFrom; +use core::fmt; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +pub const PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN: usize = 8; +pub const PLDM_FWUP_BASELINE_TRANSFER_SIZE: usize = 32; +pub const PLDM_FWUP_MAX_PADDING_SIZE: usize = PLDM_FWUP_BASELINE_TRANSFER_SIZE; +pub const PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN: usize = 32; +pub const DESCRIPTOR_DATA_MAX_LEN: usize = 64; // Arbitrary limit for static storage +pub const MAX_COMPONENT_COUNT: usize = 8; // Arbitrary limit, change as needed +pub const MAX_DESCRIPTORS_COUNT: usize = 4; // Arbitrary limit, change as needed +pub type PldmFdTime = u64; // Monotonic timestamp in milliseconds + +#[repr(u8)] +pub enum FwUpdateCmd { + QueryDeviceIdentifiers = 0x01, + GetFirmwareParameters = 0x02, + RequestUpdate = 0x10, + PassComponentTable = 0x13, + UpdateComponent = 0x14, + RequestFirmwareData = 0x15, + TransferComplete = 0x16, + VerifyComplete = 0x17, + ApplyComplete = 0x18, + ActivateFirmware = 0x1A, + GetStatus = 0x1B, + CancelUpdateComponent = 0x1C, + CancelUpdate = 0x1D, +} + +impl TryFrom for FwUpdateCmd { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x01 => Ok(FwUpdateCmd::QueryDeviceIdentifiers), + 0x02 => Ok(FwUpdateCmd::GetFirmwareParameters), + 0x10 => Ok(FwUpdateCmd::RequestUpdate), + 0x13 => Ok(FwUpdateCmd::PassComponentTable), + 0x14 => Ok(FwUpdateCmd::UpdateComponent), + 0x15 => Ok(FwUpdateCmd::RequestFirmwareData), + 0x16 => Ok(FwUpdateCmd::TransferComplete), + 0x17 => Ok(FwUpdateCmd::VerifyComplete), + 0x18 => Ok(FwUpdateCmd::ApplyComplete), + 0x1A => Ok(FwUpdateCmd::ActivateFirmware), + 0x1B => Ok(FwUpdateCmd::GetStatus), + 0x1C => Ok(FwUpdateCmd::CancelUpdateComponent), + 0x1D => Ok(FwUpdateCmd::CancelUpdate), + _ => Err(PldmError::UnsupportedCmd), + } + } +} + +#[repr(u8)] +pub enum FwUpdateCompletionCode { + NotInUpdateMode = 0x80, + AlreadyInUpdateMode = 0x81, + DataOutOfRange = 0x82, + InvalidTransferLength = 0x83, + InvalidStateForCommand = 0x84, + IncompleteUpdate = 0x85, + BusyInBackground = 0x86, + CancelPending = 0x87, + CommandNotExpected = 0x88, + RetryRequestFwData = 0x89, + UnableToInitiateUpdate = 0x8A, + ActivationNotRequired = 0x8B, + SelfContainedActivationNotPermitted = 0x8C, + NoDeviceMetadata = 0x8D, + RetryRequestUpdate = 0x8E, + NoPackageData = 0x8F, + InvalidTransferHandle = 0x90, + InvalidTransferOperationFlag = 0x91, + ActivatePendingImageNotPermitted = 0x92, + PackageDataError = 0x93, +} + +impl TryFrom for FwUpdateCompletionCode { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0x80 => Ok(FwUpdateCompletionCode::NotInUpdateMode), + 0x81 => Ok(FwUpdateCompletionCode::AlreadyInUpdateMode), + 0x82 => Ok(FwUpdateCompletionCode::DataOutOfRange), + 0x83 => Ok(FwUpdateCompletionCode::InvalidTransferLength), + 0x84 => Ok(FwUpdateCompletionCode::InvalidStateForCommand), + 0x85 => Ok(FwUpdateCompletionCode::IncompleteUpdate), + 0x86 => Ok(FwUpdateCompletionCode::BusyInBackground), + 0x87 => Ok(FwUpdateCompletionCode::CancelPending), + 0x88 => Ok(FwUpdateCompletionCode::CommandNotExpected), + 0x89 => Ok(FwUpdateCompletionCode::RetryRequestFwData), + 0x8A => Ok(FwUpdateCompletionCode::UnableToInitiateUpdate), + 0x8B => Ok(FwUpdateCompletionCode::ActivationNotRequired), + 0x8C => Ok(FwUpdateCompletionCode::SelfContainedActivationNotPermitted), + 0x8D => Ok(FwUpdateCompletionCode::NoDeviceMetadata), + 0x8E => Ok(FwUpdateCompletionCode::RetryRequestUpdate), + 0x8F => Ok(FwUpdateCompletionCode::NoPackageData), + 0x90 => Ok(FwUpdateCompletionCode::InvalidTransferHandle), + 0x91 => Ok(FwUpdateCompletionCode::InvalidTransferOperationFlag), + 0x92 => Ok(FwUpdateCompletionCode::ActivatePendingImageNotPermitted), + 0x93 => Ok(FwUpdateCompletionCode::PackageDataError), + _ => Err(PldmError::InvalidCompletionCode), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +#[repr(u8)] +pub enum FirmwareDeviceState { + Idle = 0, + LearnComponents = 1, + ReadyXfer = 2, + Download = 3, + Verify = 4, + Apply = 5, + Activate = 6, +} + +impl TryFrom for FirmwareDeviceState { + type Error = PldmError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(FirmwareDeviceState::Idle), + 1 => Ok(FirmwareDeviceState::LearnComponents), + 2 => Ok(FirmwareDeviceState::ReadyXfer), + 3 => Ok(FirmwareDeviceState::Download), + 4 => Ok(FirmwareDeviceState::Verify), + 5 => Ok(FirmwareDeviceState::Apply), + 6 => Ok(FirmwareDeviceState::Activate), + _ => Err(PldmError::InvalidFdState), + } + } +} + +bitfield! { + #[derive(Clone, Copy, PartialEq, FromBytes, IntoBytes)] + pub struct UpdateOptionFlags(u32); + impl Debug; + pub u32, reserved, _: 31, 3; + pub u32, svn_delayed_update, set_svn_delayed_update: 2; + pub u32, component_opaque_data, set_component_opaque_data: 1; + pub u32, request_force_update, set_request_force_update: 0; +} + +#[repr(u8)] +pub enum VersionStringType { + Unspecified = 0, + Ascii = 1, + Utf8 = 2, + Utf16 = 3, + Utf16Le = 4, + Utf16Be = 5, +} + +impl VersionStringType { + fn as_string(&self) -> &str { + match *self { + VersionStringType::Ascii => "ASCII", + VersionStringType::Utf8 => "UTF-8", + VersionStringType::Utf16 => "UTF-16", + VersionStringType::Utf16Le => "UTF-16LE", + VersionStringType::Utf16Be => "UTF-16BE", + VersionStringType::Unspecified => "UNSPECIFIED", + } + } +} + +impl fmt::Display for VersionStringType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_string()) + } +} +impl TryFrom<&str> for VersionStringType { + type Error = PldmError; + + fn try_from(input: &str) -> Result { + match input { + "ASCII" | "ascii" => Ok(VersionStringType::Ascii), + "UTF-8" | "utf-8" => Ok(VersionStringType::Utf8), + "UTF-16" | "utf-16" => Ok(VersionStringType::Utf16), + "UTF-16LE" | "utf-16le" => Ok(VersionStringType::Utf16Le), + "UTF-16BE" | "utf-16be" => Ok(VersionStringType::Utf16Be), + "UNSPECIFIED" | "unspecified" => Ok(VersionStringType::Unspecified), + _ => Err(PldmError::InvalidVersionStringType), + } + } +} + +#[derive(Clone, Copy, PartialEq)] +#[repr(u16)] +pub enum DescriptorType { + PciVendorId = 0x0000, + IanaEnterpriseId = 0x0001, + Uuid = 0x0002, + PnpVendorId = 0x0003, + AcpiVendorId = 0x0004, + IeeeAssignedCompanyId = 0x0005, + ScsiVendorId = 0x0006, + PciDeviceId = 0x0100, + PciSubsystemVendorId = 0x0101, + PciSubsystemId = 0x0102, + PciRevisionId = 0x0103, + PnpProductIdentifier = 0x0104, + AcpiProductIdentifier = 0x0105, + AsciiModelNumberLongString = 0x0106, + AsciiModelNumberShortString = 0x0107, + ScsiProductId = 0x0108, + UbmControllerDeviceCode = 0x0109, + VendorDefined = 0xFFFF, +} + +impl TryFrom for DescriptorType { + type Error = PldmError; + + fn try_from(value: u16) -> Result { + match value { + 0x0000 => Ok(DescriptorType::PciVendorId), + 0x0001 => Ok(DescriptorType::IanaEnterpriseId), + 0x0002 => Ok(DescriptorType::Uuid), + 0x0003 => Ok(DescriptorType::PnpVendorId), + 0x0004 => Ok(DescriptorType::AcpiVendorId), + 0x0005 => Ok(DescriptorType::IeeeAssignedCompanyId), + 0x0006 => Ok(DescriptorType::ScsiVendorId), + 0x0100 => Ok(DescriptorType::PciDeviceId), + 0x0101 => Ok(DescriptorType::PciSubsystemVendorId), + 0x0102 => Ok(DescriptorType::PciSubsystemId), + 0x0103 => Ok(DescriptorType::PciRevisionId), + 0x0104 => Ok(DescriptorType::PnpProductIdentifier), + 0x0105 => Ok(DescriptorType::AcpiProductIdentifier), + 0x0106 => Ok(DescriptorType::AsciiModelNumberLongString), + 0x0107 => Ok(DescriptorType::AsciiModelNumberShortString), + 0x0108 => Ok(DescriptorType::ScsiProductId), + 0x0109 => Ok(DescriptorType::UbmControllerDeviceCode), + 0xFFFF => Ok(DescriptorType::VendorDefined), + _ => Err(PldmError::InvalidDescriptorType), + } + } +} + +pub fn get_descriptor_length(descriptor_type: DescriptorType) -> usize { + match &descriptor_type { + DescriptorType::PciVendorId => 2, + DescriptorType::IanaEnterpriseId => 4, + DescriptorType::Uuid => 16, + DescriptorType::PnpVendorId => 3, + DescriptorType::AcpiVendorId => 5, + DescriptorType::IeeeAssignedCompanyId => 3, + DescriptorType::ScsiVendorId => 8, + DescriptorType::PciDeviceId => 2, + DescriptorType::PciSubsystemVendorId => 2, + DescriptorType::PciSubsystemId => 2, + DescriptorType::PciRevisionId => 1, + DescriptorType::PnpProductIdentifier => 4, + DescriptorType::AcpiProductIdentifier => 4, + DescriptorType::AsciiModelNumberLongString => 40, + DescriptorType::AsciiModelNumberShortString => 10, + DescriptorType::ScsiProductId => 16, + DescriptorType::UbmControllerDeviceCode => 4, + DescriptorType::VendorDefined => DESCRIPTOR_DATA_MAX_LEN, + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(C)] +pub struct Descriptor { + pub descriptor_type: u16, + pub descriptor_length: u16, + pub descriptor_data: [u8; DESCRIPTOR_DATA_MAX_LEN], +} + +impl Default for Descriptor { + fn default() -> Self { + Descriptor { + descriptor_type: 0, + descriptor_length: 0, + descriptor_data: [0; DESCRIPTOR_DATA_MAX_LEN], + } + } +} + +impl Descriptor { + pub fn new_empty() -> Self { + Descriptor { + descriptor_type: 0, + descriptor_length: 0, + descriptor_data: [0; DESCRIPTOR_DATA_MAX_LEN], + } + } + + pub fn new(descriptor_type: DescriptorType, descriptor_data: &[u8]) -> Result { + let descriptor_length = get_descriptor_length(descriptor_type); + if descriptor_data.len() != descriptor_length { + return Err(PldmError::InvalidDescriptorLength); + } + + let mut descriptor_data_array = [0u8; DESCRIPTOR_DATA_MAX_LEN]; + descriptor_data_array[..descriptor_length].copy_from_slice(descriptor_data); + + Ok(Descriptor { + descriptor_type: descriptor_type as u16, + descriptor_length: descriptor_length as u16, + descriptor_data: descriptor_data_array, + }) + } + + pub fn codec_size_in_bytes(&self) -> usize { + core::mem::size_of::() * 2 + self.descriptor_length as usize + } +} + +impl PldmCodec for Descriptor { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + let mut offset = 0; + + self.descriptor_type + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.descriptor_length + .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) + .unwrap(); + offset += core::mem::size_of::(); + + self.descriptor_data[..self.descriptor_length as usize] + .write_to(&mut buffer[offset..offset + self.descriptor_length as usize]) + .unwrap(); + offset += self.descriptor_length as usize; + + Ok(offset) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + + let descriptor_type = u16::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let descriptor_length = u16::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + offset += core::mem::size_of::(); + + let mut descriptor_data = [0u8; DESCRIPTOR_DATA_MAX_LEN]; + descriptor_data[..descriptor_length as usize].copy_from_slice( + buffer + .get(offset..offset + descriptor_length as usize) + .ok_or(PldmCodecError::BufferTooShort)?, + ); + + Ok(Descriptor { + descriptor_type, + descriptor_length, + descriptor_data, + }) + } +} + +bitfield! { + #[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, PartialEq, Eq, Default)] + pub struct FirmwareDeviceCapability(u32); + impl Debug; + pub u32, reserved, _: 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, 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; +} + +bitfield! { + #[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, PartialEq, Eq)] + pub struct ComponentActivationMethods(u16); + impl Debug; + pub u16, reserved, _: 15, 8; + pub u16, activate_pending_comp_image_set, set_activate_pending_comp_image_set: 7; + pub u16, activate_pending_image, set_activate_pending_image: 6; + pub u16, ac_power_cycle, set_ac_power_cycle: 5; + pub u16, dc_power_cycle, set_dc_power_cycle: 4; + pub u16, system_reboot, set_system_reboot: 3; + pub u16, medium_specific_reset, set_medium_specific_reset: 2; + pub u16, self_contained, set_self_contained: 1; + pub u16, automatic, set_automatic: 0; +} + +#[repr(u16)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ComponentClassification { + Unspecified = 0x0000, + Other = 0x0001, + Driver = 0x0002, + ConfigurationSoftware = 0x0003, + ApplicationSoftware = 0x0004, + Instrumentation = 0x0005, + FirmwareOrBios = 0x0006, + DiagnosticSoftware = 0x0007, + OperatingSystem = 0x0008, + Middleware = 0x0009, + Firmware = 0x000A, + BiosOrFcode = 0x000B, + SupportOrServicePack = 0x000C, + SoftwareBundle = 0x000D, + DownstreamDevice = 0xFFFF, +} + +impl TryFrom for ComponentClassification { + type Error = PldmError; + + fn try_from(value: u16) -> Result { + match value { + 0x0000 => Ok(ComponentClassification::Unspecified), + 0x0001 => Ok(ComponentClassification::Other), + 0x0002 => Ok(ComponentClassification::Driver), + 0x0003 => Ok(ComponentClassification::ConfigurationSoftware), + 0x0004 => Ok(ComponentClassification::ApplicationSoftware), + 0x0005 => Ok(ComponentClassification::Instrumentation), + 0x0006 => Ok(ComponentClassification::FirmwareOrBios), + 0x0007 => Ok(ComponentClassification::DiagnosticSoftware), + 0x0008 => Ok(ComponentClassification::OperatingSystem), + 0x0009 => Ok(ComponentClassification::Middleware), + 0x000A => Ok(ComponentClassification::Firmware), + 0x000B => Ok(ComponentClassification::BiosOrFcode), + 0x000C => Ok(ComponentClassification::SupportOrServicePack), + 0x000D => Ok(ComponentClassification::SoftwareBundle), + 0xFFFF => Ok(ComponentClassification::DownstreamDevice), + _ => Err(PldmError::InvalidComponentClassification), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct PldmFirmwareString { + pub str_type: u8, + pub str_len: u8, + pub str_data: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], +} + +impl Default for PldmFirmwareString { + fn default() -> Self { + PldmFirmwareString { + str_type: 0, + str_len: 0, + str_data: [0; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], + } + } +} + +impl PartialOrd for PldmFirmwareString { + fn partial_cmp(&self, other: &Self) -> Option { + if self.str_type != other.str_type { + return None; + } + let self_str = &self.str_data[..self.str_len as usize]; + let other_str = &other.str_data[..other.str_len as usize]; + Some(self_str.cmp(other_str)) + } +} + +impl PldmFirmwareString { + pub fn new(str_type: &str, fw_str: &str) -> Result { + let str_type = VersionStringType::try_from(str_type)?; + + if fw_str.len() > PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN { + return Err(PldmError::InvalidVersionStringLength); + } + + let mut str_data = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + str_data[..fw_str.len()].copy_from_slice(fw_str.as_bytes()); + + Ok(PldmFirmwareString { + str_type: str_type as u8, + str_len: fw_str.len() as u8, + str_data, + }) + } +} + +#[derive(Clone)] +pub struct PldmFirmwareVersion<'a> { + pub comparison_stamp: u32, + pub str: &'a PldmFirmwareString, + pub date: [u8; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], +} + +impl Default for PldmFirmwareVersion<'_> { + fn default() -> Self { + static DEFAULT_FW_STRING: PldmFirmwareString = PldmFirmwareString { + str_type: 0, + str_len: 0, + str_data: [0; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], + }; + PldmFirmwareVersion { + comparison_stamp: 0, + str: &DEFAULT_FW_STRING, + date: [0; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], + } + } +} + +impl<'a> PldmFirmwareVersion<'a> { + pub fn new(comparison_stamp: u32, str: &'a PldmFirmwareString, date_str: Option<&str>) -> Self { + let mut date_array = [0u8; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN]; + if let Some(date_str) = date_str { + let date_bytes = date_str.as_bytes(); + let len = date_bytes.len().min(PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN); + date_array[..len].copy_from_slice(&date_bytes[..len]); + } + PldmFirmwareVersion { + comparison_stamp, + str, + date: date_array, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, FromBytes, IntoBytes, Immutable, Copy)] +#[repr(C, packed)] +pub struct ComponentParameterEntryFixed { + pub comp_classification: u16, + pub comp_identifier: u16, + pub comp_classification_index: u8, + pub active_comp_comparison_stamp: u32, + pub active_comp_ver_str_type: u8, + pub active_comp_ver_str_len: u8, + pub active_comp_release_date: [u8; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], + pub pending_comp_comparison_stamp: u32, + pub pending_comp_ver_str_type: u8, + pub pending_comp_ver_str_len: u8, + pub pending_comp_release_date: [u8; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], + pub comp_activation_methods: ComponentActivationMethods, + pub capabilities_during_update: FirmwareDeviceCapability, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[repr(C)] +pub struct ComponentParameterEntry { + pub comp_param_entry_fixed: ComponentParameterEntryFixed, + pub active_comp_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], + pub pending_comp_ver_str: Option<[u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]>, +} + +impl Default for ComponentParameterEntry { + fn default() -> Self { + ComponentParameterEntry { + comp_param_entry_fixed: ComponentParameterEntryFixed { + comp_classification: 0, + comp_identifier: 0, + comp_classification_index: 0, + active_comp_comparison_stamp: 0, + active_comp_ver_str_type: 0, + active_comp_ver_str_len: 0, + active_comp_release_date: [0; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], + pending_comp_comparison_stamp: 0, + pending_comp_ver_str_type: 0, + pending_comp_ver_str_len: 0, + pending_comp_release_date: [0; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], + comp_activation_methods: ComponentActivationMethods(0), + capabilities_during_update: FirmwareDeviceCapability(0), + }, + active_comp_ver_str: [0; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], + pending_comp_ver_str: None, + } + } +} + +impl ComponentParameterEntry { + pub fn new( + comp_classification: ComponentClassification, + comp_identifier: u16, + comp_classification_index: u8, + active_firmware_version: &PldmFirmwareVersion, + pending_firmware_version: &PldmFirmwareVersion, + comp_activation_methods: ComponentActivationMethods, + capabilities_during_update: FirmwareDeviceCapability, + ) -> Self { + ComponentParameterEntry { + comp_param_entry_fixed: ComponentParameterEntryFixed { + comp_classification: comp_classification as u16, + comp_identifier, + comp_classification_index, + active_comp_comparison_stamp: active_firmware_version.comparison_stamp, + active_comp_ver_str_type: active_firmware_version.str.str_type, + active_comp_ver_str_len: active_firmware_version.str.str_len, + active_comp_release_date: active_firmware_version.date, + pending_comp_comparison_stamp: pending_firmware_version.comparison_stamp, + pending_comp_ver_str_type: pending_firmware_version.str.str_type, + pending_comp_ver_str_len: pending_firmware_version.str.str_len, + pending_comp_release_date: pending_firmware_version.date, + comp_activation_methods, + capabilities_during_update, + }, + active_comp_ver_str: { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = active_firmware_version.str.str_len as usize; + arr[..len].copy_from_slice(&active_firmware_version.str.str_data[..len]); + arr + }, + pending_comp_ver_str: { + if pending_firmware_version.str.str_len > 0 { + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + let len = pending_firmware_version.str.str_len as usize; + arr[..len].copy_from_slice(&pending_firmware_version.str.str_data[..len]); + Some(arr) + } else { + None + } + }, + } + } + + pub fn codec_size_in_bytes(&self) -> usize { + let mut bytes = 0; + bytes += core::mem::size_of::(); + bytes += self.comp_param_entry_fixed.active_comp_ver_str_len as usize; + if self.pending_comp_ver_str.is_some() { + bytes += self.comp_param_entry_fixed.pending_comp_ver_str_len as usize; + } + bytes + } + + pub fn get_active_fw_ver(&self) -> PldmFirmwareString { + PldmFirmwareString { + str_type: self.comp_param_entry_fixed.active_comp_ver_str_type, + str_len: self.comp_param_entry_fixed.active_comp_ver_str_len, + str_data: self.active_comp_ver_str, + } + } +} + +impl PldmCodec for ComponentParameterEntry { + fn encode(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < self.codec_size_in_bytes() { + return Err(PldmCodecError::BufferTooShort); + } + let mut offset = 0; + + self.comp_param_entry_fixed + .write_to( + &mut buffer[offset..offset + core::mem::size_of::()], + ) + .unwrap(); + + offset += core::mem::size_of::(); + + let len = self.comp_param_entry_fixed.active_comp_ver_str_len as usize; + self.active_comp_ver_str[..len] + .write_to(&mut buffer[offset..offset + len]) + .unwrap(); + offset += len; + + if let Some(pending_comp_ver_str) = &self.pending_comp_ver_str { + let len = self.comp_param_entry_fixed.pending_comp_ver_str_len as usize; + pending_comp_ver_str[..len] + .write_to(&mut buffer[offset..offset + len]) + .unwrap(); + offset += len; + } + + Ok(offset) + } + + fn decode(buffer: &[u8]) -> Result { + let mut offset = 0; + + let comp_param_entry_fixed = ComponentParameterEntryFixed::read_from_bytes( + buffer + .get(offset..offset + core::mem::size_of::()) + .ok_or(PldmCodecError::BufferTooShort)?, + ) + .unwrap(); + + offset += core::mem::size_of::(); + + let active_comp_ver_str_len = comp_param_entry_fixed.active_comp_ver_str_len as usize; + let mut active_comp_ver_str = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + active_comp_ver_str[..active_comp_ver_str_len].copy_from_slice( + buffer + .get(offset..offset + active_comp_ver_str_len) + .ok_or(PldmCodecError::BufferTooShort)?, + ); + offset += active_comp_ver_str_len; + + let pending_comp_ver_str = if comp_param_entry_fixed.pending_comp_ver_str_len > 0 { + let len = comp_param_entry_fixed.pending_comp_ver_str_len as usize; + let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; + arr[..len].copy_from_slice( + buffer + .get(offset..offset + len) + .ok_or(PldmCodecError::BufferTooShort)?, + ); + Some(arr) + } else { + None + }; + + Ok(ComponentParameterEntry { + comp_param_entry_fixed, + active_comp_ver_str, + pending_comp_ver_str, + }) + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +pub enum ComponentResponse { + CompCanBeUpdated, + CompCannotBeUpdated, +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ComponentResponseCode { + CompCanBeUpdated = 0x00, + CompComparisonStampIdentical = 0x01, + CompComparisonStampLower = 0x02, + InvalidCompComparisonStamp = 0x03, + CompConflict = 0x04, + CompPrerequisitesNotMet = 0x05, + CompNotSupported = 0x06, + CompSecurityRestrictions = 0x07, + IncompleteCompImageSet = 0x08, + ActiveImageNotUpdateableSubsequently = 0x09, + CompVerStrIdentical = 0x0a, + CompVerStrLower = 0x0b, + VendorDefined, // 0xd0..=0xef +} + +impl TryFrom for ComponentResponseCode { + type Error = PldmError; + + fn try_from(val: u8) -> Result { + match val { + 0x00 => Ok(ComponentResponseCode::CompCanBeUpdated), + 0x01 => Ok(ComponentResponseCode::CompComparisonStampIdentical), + 0x02 => Ok(ComponentResponseCode::CompComparisonStampLower), + 0x03 => Ok(ComponentResponseCode::InvalidCompComparisonStamp), + 0x04 => Ok(ComponentResponseCode::CompConflict), + 0x05 => Ok(ComponentResponseCode::CompPrerequisitesNotMet), + 0x06 => Ok(ComponentResponseCode::CompNotSupported), + 0x07 => Ok(ComponentResponseCode::CompSecurityRestrictions), + 0x08 => Ok(ComponentResponseCode::IncompleteCompImageSet), + 0x09 => Ok(ComponentResponseCode::ActiveImageNotUpdateableSubsequently), + 0x0a => Ok(ComponentResponseCode::CompVerStrIdentical), + 0x0b => Ok(ComponentResponseCode::CompVerStrLower), + 0xd0..=0xef => Ok(ComponentResponseCode::VendorDefined), + _ => Err(PldmError::InvalidComponentResponseCode), + } + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +pub enum ComponentCompatibilityResponse { + CompCanBeUpdated = 0, + CompCannotBeUpdated = 1, +} + +impl TryFrom for ComponentCompatibilityResponse { + type Error = PldmError; + + fn try_from(val: u8) -> Result { + match val { + 0 => Ok(ComponentCompatibilityResponse::CompCanBeUpdated), + 1 => Ok(ComponentCompatibilityResponse::CompCannotBeUpdated), + _ => Err(PldmError::InvalidComponentCompatibilityResponse), + } + } +} + +#[repr(u8)] +pub enum ComponentCompatibilityResponseCode { + NoResponseCode = 0x00, + CompComparisonStampIdentical = 0x01, + CompComparisonStampLower = 0x02, + InvalidCompComparisonStamp = 0x03, + CompConflict = 0x04, + CompPrerequisitesNotMet = 0x05, + CompNotSupported = 0x06, + CompSecurityRestrictions = 0x07, + IncompleteCompImageSet = 0x08, + CompInfoNoMatch = 0x09, + CompVerStrIdentical = 0x0a, + CompVerStrLower = 0x0b, + VendorDefined, +} + +impl TryFrom for ComponentCompatibilityResponseCode { + type Error = PldmError; + + fn try_from(val: u8) -> Result { + match val { + 0x00 => Ok(ComponentCompatibilityResponseCode::NoResponseCode), + 0x01 => Ok(ComponentCompatibilityResponseCode::CompComparisonStampIdentical), + 0x02 => Ok(ComponentCompatibilityResponseCode::CompComparisonStampLower), + 0x03 => Ok(ComponentCompatibilityResponseCode::InvalidCompComparisonStamp), + 0x04 => Ok(ComponentCompatibilityResponseCode::CompConflict), + 0x05 => Ok(ComponentCompatibilityResponseCode::CompPrerequisitesNotMet), + 0x06 => Ok(ComponentCompatibilityResponseCode::CompNotSupported), + 0x07 => Ok(ComponentCompatibilityResponseCode::CompSecurityRestrictions), + 0x08 => Ok(ComponentCompatibilityResponseCode::IncompleteCompImageSet), + 0x09 => Ok(ComponentCompatibilityResponseCode::CompInfoNoMatch), + 0x0a => Ok(ComponentCompatibilityResponseCode::CompVerStrIdentical), + 0x0b => Ok(ComponentCompatibilityResponseCode::CompVerStrLower), + 0xd0..=0xef => Ok(ComponentCompatibilityResponseCode::VendorDefined), + _ => Err(PldmError::InvalidComponentCompatibilityResponseCode), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_descriptor_encode_decode() { + let test_uid = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, + ]; + let descriptor = Descriptor::new(DescriptorType::Uuid, &test_uid).unwrap(); + assert_eq!( + descriptor.descriptor_length, + get_descriptor_length(DescriptorType::Uuid) as u16 + ); + let mut buffer = [0u8; 512]; + descriptor.encode(&mut buffer).unwrap(); + let decoded_descriptor = Descriptor::decode(&buffer).unwrap(); + assert_eq!(descriptor, decoded_descriptor); + } + + #[test] + fn test_component_parameter_entry() { + let active_firmware_string = PldmFirmwareString::new("UTF-8", "mcu-runtime-1.0").unwrap(); + let active_firmware_version = + PldmFirmwareVersion::new(0x12345678, &active_firmware_string, Some("20250210")); + + let pending_firmware_string = PldmFirmwareString::new("UTF-8", "mcu-runtime-1.5").unwrap(); + let pending_firmware_version = + PldmFirmwareVersion::new(0x87654321, &pending_firmware_string, Some("20250213")); + + let comp_activation_methods = ComponentActivationMethods(0x0001); + let capabilities_during_update = FirmwareDeviceCapability(0x0010); + + let component_parameter_entry = ComponentParameterEntry::new( + ComponentClassification::Firmware, + 0x0001, + 0x01, + &active_firmware_version, + &pending_firmware_version, + comp_activation_methods, + capabilities_during_update, + ); + + let mut buffer = [0u8; 512]; + component_parameter_entry.encode(&mut buffer).unwrap(); + let decoded_component_parameter_entry = ComponentParameterEntry::decode(&buffer).unwrap(); + assert_eq!(component_parameter_entry, decoded_component_parameter_entry); + + let active_fw_ver = component_parameter_entry.get_active_fw_ver(); + assert_eq!(active_firmware_string, active_fw_ver); + assert!(active_firmware_string < pending_firmware_string); + } +} diff --git a/pldm-common/src/protocol/mod.rs b/pldm-common/src/protocol/mod.rs new file mode 100644 index 0000000..1622365 --- /dev/null +++ b/pldm-common/src/protocol/mod.rs @@ -0,0 +1,5 @@ +// Licensed under the Apache-2.0 license + +pub mod base; +pub mod firmware_update; +pub mod version; diff --git a/pldm-common/src/protocol/version.rs b/pldm-common/src/protocol/version.rs new file mode 100644 index 0000000..bdbe090 --- /dev/null +++ b/pldm-common/src/protocol/version.rs @@ -0,0 +1,250 @@ +// Licensed under the Apache-2.0 license + +use crate::error::PldmError; +use core::convert::TryFrom; + +pub type Ver32 = u32; +pub type VersionCheckSum = u32; +pub type ProtocolVersionStr = &'static str; + +// The PLDM base protocol version 1.1.0 +pub const PLDM_BASE_PROTOCOL_VERSION: ProtocolVersionStr = "1.1.0"; + +// PLDM firmware update protocol 1.3.0 +pub const PLDM_FW_UPDATE_PROTOCOL_VERSION: ProtocolVersionStr = "1.3.0"; + +/// PLDM version structure. Ver32 encoding +/// +/// The "major," "minor," and "update" bytes are BCD-encoded, and each byte holds two BCD +/// digits. The "alpha" byte holds an optional alphanumeric character extension that is encoded using the +/// ISO/IEC 8859-1 Character Set. The value 0xF in the most-significant nibble of a BCD-encoded value indicates that the most +/// significant nibble should be ignored and the overall field treated as a single-digit value. Software +/// or utilities that display the number should display only a single digit and should not put in a +/// leading "0" when displaying the number. +/// +/// A value of 0xFF in the "update" field indicates that the entire field is not present. 0xFF is not +/// allowed as a value for the "major" or "minor" fields. Software or utilities that display the version +/// number should not display any characters for this field. +/// +/// For example: +/// - Version 3.7.10a → 0xF3F71061 +/// +/// - Version 3.1 → 0xF3F1FF00 +/// - Version 1.0a → 0xF1F0FF61 +#[derive(Debug, Clone, PartialEq, Eq)] +#[repr(C)] +pub struct PldmVersion { + pub alpha: u8, + pub update: u8, + pub minor: u8, + pub major: u8, +} + +impl TryFrom for PldmVersion { + type Error = PldmError; + + fn try_from(version: ProtocolVersionStr) -> Result { + let mut parts = version.split('.'); + let major_str = parts.next().ok_or(PldmError::InvalidProtocolVersion)?; + let minor_str = parts.next().ok_or(PldmError::InvalidProtocolVersion)?; + let update_str = parts.next().unwrap_or("FF"); + + if parts.next().is_some() { + return Err(PldmError::InvalidProtocolVersion); + } + + let (alpha, minor_str) = if let Some(last_char) = minor_str.chars().last() { + if last_char.is_alphabetic() { + (last_char as u8, &minor_str[..minor_str.len() - 1]) + } else { + (0, minor_str) + } + } else { + (0, minor_str) + }; + + let (alpha, update_str) = if update_str != "FF" { + if let Some(last_char) = update_str.chars().last() { + if last_char.is_alphabetic() { + (last_char as u8, &update_str[..update_str.len() - 1]) + } else { + (alpha, update_str) + } + } else { + (alpha, update_str) + } + } else { + (alpha, update_str) + }; + + let major = major_str + .parse::() + .map_err(|_| PldmError::InvalidProtocolVersion)?; + let minor = minor_str + .parse::() + .map_err(|_| PldmError::InvalidProtocolVersion)?; + let update = if update_str == "FF" { + 0xFF + } else { + update_str + .parse::() + .map_err(|_| PldmError::InvalidProtocolVersion)? + }; + + Ok(PldmVersion { + alpha, + update, + minor, + major, + }) + } +} + +impl PldmVersion { + pub fn new(alpha: u8, update: u8, minor: u8, major: u8) -> Self { + PldmVersion { + alpha, + update, + minor, + major, + } + } + + pub fn bcd_encode_to_ver32(&self) -> Ver32 { + let major_bcd = if self.major < 10 { + 0xF0 | self.major + } else { + ((self.major / 10) << 4) | (self.major % 10) + }; + let minor_bcd = if self.minor < 10 { + 0xF0 | self.minor + } else { + ((self.minor / 10) << 4) | (self.minor % 10) + }; + let update_bcd = if self.update == 0xFF { + 0xFF + } else if self.update < 10 { + 0xF0 | self.update + } else { + ((self.update / 10) << 4) | (self.update % 10) + }; + let alpha_bcd = self.alpha; // Alpha is directly used as it's already in the correct format or 0x00 if not present + + (major_bcd as u32) << 24 + | (minor_bcd as u32) << 16 + | (update_bcd as u32) << 8 + | alpha_bcd as u32 + } + + pub fn bcd_decode_from_ver32(encoded_ver32: Ver32) -> Self { + let major_bcd = ((encoded_ver32 >> 24) & 0xFF) as u8; + let minor_bcd = ((encoded_ver32 >> 16) & 0xFF) as u8; + let update_bcd = ((encoded_ver32 >> 8) & 0xFF) as u8; + let alpha = (encoded_ver32 & 0xFF) as u8; + + let major = if major_bcd >> 4 == 0xF { + major_bcd & 0x0F + } else { + ((major_bcd >> 4) * 10) + (major_bcd & 0x0F) + }; + let minor = if minor_bcd >> 4 == 0xF { + minor_bcd & 0x0F + } else { + ((minor_bcd >> 4) * 10) + (minor_bcd & 0x0F) + }; + let update = if update_bcd == 0xFF { + update_bcd + } else if update_bcd >> 4 == 0xF { + update_bcd & 0x0F + } else { + ((update_bcd >> 4) * 10) + (update_bcd & 0x0F) + }; + + PldmVersion { + alpha, + update, + minor, + major, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_pldm_version_try_from_str() { + let test_version = PldmVersion::try_from("3.7.10").unwrap(); + assert_eq!(test_version, PldmVersion::new(0, 10, 7, 3)); + + let test_version = PldmVersion::try_from("3.7").unwrap(); + assert_eq!(test_version, PldmVersion::new(0, 0xFF, 7, 3)); + + let test_version = PldmVersion::try_from("1.1.0").unwrap(); + assert_eq!(test_version, PldmVersion::new(0, 0, 1, 1)); + + let test_version = PldmVersion::try_from("1.3.0").unwrap(); + assert_eq!(test_version, PldmVersion::new(0, 0, 3, 1)); + + let test_version = PldmVersion::try_from("1.5.18a").unwrap(); + assert_eq!(test_version, PldmVersion::new(0x61, 18, 5, 1)); + + let test_version = PldmVersion::try_from("1.5a").unwrap(); + assert_eq!(test_version, PldmVersion::new(0x61, 0xFF, 5, 1)); + } + + #[test] + fn test_pldm_version_try_from_str_error() { + let test_version = PldmVersion::try_from("3.FF.10"); + assert!(test_version.is_err()); + + let test_version = PldmVersion::try_from("3.7.10a.1"); + assert!(test_version.is_err()); + + let test_version = PldmVersion::try_from("3.7.10a.1.1"); + assert!(test_version.is_err()); + + let test_version = PldmVersion::try_from("3a.7.10"); + assert!(test_version.is_err()); + } + + #[test] + fn test_pldm_version_bcd_encode() { + let test_version1 = PldmVersion::new(0x61, 0x10, 0x7, 0x3); + assert_eq!(test_version1.bcd_encode_to_ver32(), 0xF3F71661); + + let test_version2 = PldmVersion::new(0x61, 0xFF, 0x1, 0x3); + assert_eq!(test_version2.bcd_encode_to_ver32(), 0xF3F1FF61); + + let test_version3 = PldmVersion::new(0x61, 0xFF, 0xa, 0x1); + assert_eq!(test_version3.bcd_encode_to_ver32(), 0xF110FF61); + } + + #[test] + fn test_pldm_version_bcd_decode_from_ver32() { + let test_version1 = PldmVersion::bcd_decode_from_ver32(0xF3F71661); + assert_eq!(test_version1, PldmVersion::new(0x61, 0x10, 0x7, 0x3)); + + let test_version2 = PldmVersion::bcd_decode_from_ver32(0xF3F1FF61); + assert_eq!(test_version2, PldmVersion::new(0x61, 0xFF, 0x1, 0x3)); + + let test_version3 = PldmVersion::bcd_decode_from_ver32(0xF1F0FF62); + assert_eq!(test_version3, PldmVersion::new(0x62, 0xFF, 0x0, 0x1)); + } + + #[test] + fn test_pldm_version_str_to_ver32() { + let test_version1 = "1.3.0"; + let test_version1_ver32 = PldmVersion::try_from(test_version1) + .unwrap() + .bcd_encode_to_ver32(); + assert_eq!(test_version1_ver32, 0xF1F3F000); + + let test_version2 = "1.1.0"; + let test_version2_ver32 = PldmVersion::try_from(test_version2) + .unwrap() + .bcd_encode_to_ver32(); + assert_eq!(test_version2_ver32, 0xF1F1F000); + } +} diff --git a/pldm-common/src/util/fw_component.rs b/pldm-common/src/util/fw_component.rs new file mode 100644 index 0000000..17ff3ff --- /dev/null +++ b/pldm-common/src/util/fw_component.rs @@ -0,0 +1,200 @@ +// Licensed under the Apache-2.0 license + +use crate::message::firmware_update::get_fw_params::FirmwareParameters; +use crate::protocol::firmware_update::{ + ComponentResponseCode, PldmFirmwareString, UpdateOptionFlags, +}; + +// An entry for Pass Component Table or Update Component +#[derive(Clone, Default, PartialEq)] +pub struct FirmwareComponent { + pub comp_classification: u16, + pub comp_identifier: u16, + pub comp_classification_index: u8, + pub comp_comparison_stamp: u32, + pub comp_version: PldmFirmwareString, + pub comp_image_size: Option, + pub update_option_flags: Option, +} + +impl FirmwareComponent { + pub fn new( + comp_classification: u16, + comp_identifier: u16, + comp_classification_index: u8, + comp_comparison_stamp: u32, + comp_version: PldmFirmwareString, + comp_image_size: Option, + update_option_flags: Option, + ) -> Self { + Self { + comp_classification, + comp_identifier, + comp_classification_index, + comp_comparison_stamp, + comp_version, + comp_image_size, + update_option_flags, + } + } + + // Determines if the component is eligible for an update based on the firmware parameters and returns the appropriate ComponentResponseCode + // defined in the PLDM firmware update specification. + pub fn evaluate_update_eligibility( + &self, + fw_params: &FirmwareParameters, + ) -> ComponentResponseCode { + if let Some(entry) = fw_params.comp_param_table.iter().find(|entry| { + entry.comp_param_entry_fixed.comp_classification == self.comp_classification + && entry.comp_param_entry_fixed.comp_identifier == self.comp_identifier + && entry.comp_param_entry_fixed.comp_classification_index + == self.comp_classification_index + }) { + if self.comp_comparison_stamp + == entry.comp_param_entry_fixed.active_comp_comparison_stamp + { + ComponentResponseCode::CompComparisonStampIdentical + } else if self.comp_comparison_stamp + < entry.comp_param_entry_fixed.active_comp_comparison_stamp + { + ComponentResponseCode::CompComparisonStampLower + } else if self.comp_version == entry.get_active_fw_ver() { + ComponentResponseCode::CompVerStrIdentical + } else if self.comp_version < entry.get_active_fw_ver() { + ComponentResponseCode::CompVerStrLower + } else { + ComponentResponseCode::CompCanBeUpdated + } + } else { + ComponentResponseCode::CompNotSupported + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::protocol::firmware_update::{ + ComponentActivationMethods, ComponentClassification, ComponentParameterEntry, + FirmwareDeviceCapability, PldmFirmwareVersion, + }; + + fn construct_firmware_params() -> FirmwareParameters { + let active_firmware_string = PldmFirmwareString::new("UTF-8", "mcu-runtime-1.0").unwrap(); + let active_firmware_version = + PldmFirmwareVersion::new(0x12345678, &active_firmware_string, Some("20250210")); + let pending_firmware_string = PldmFirmwareString::new("UTF-8", "mcu-runtime-1.5").unwrap(); + let pending_firmware_version = + PldmFirmwareVersion::new(0x87654321, &pending_firmware_string, Some("20250213")); + let comp_activation_methods = ComponentActivationMethods(0x0001); + let capabilities_during_update = FirmwareDeviceCapability(0x0010); + let component_parameter_entry = ComponentParameterEntry::new( + ComponentClassification::Firmware, + 0x0001, + 0x01, + &active_firmware_version, + &pending_firmware_version, + comp_activation_methods, + capabilities_during_update, + ); + + const COMP_COUNT: usize = 8; + let comp_param_table: [ComponentParameterEntry; COMP_COUNT] = + core::array::from_fn(|_| component_parameter_entry.clone()); + FirmwareParameters::new( + capabilities_during_update, + COMP_COUNT as u16, + &active_firmware_string, + &pending_firmware_string, + &comp_param_table, + ) + } + + #[test] + fn test_check_update_component() { + let fw_params = construct_firmware_params(); + let comp = FirmwareComponent::new( + ComponentClassification::Firmware as u16, + 0x0001, + 0x01, + 0x12345680, + PldmFirmwareString::new("UTF-8", "mcu-runtime-1.2").unwrap(), + None, + None, + ); + assert_eq!( + comp.evaluate_update_eligibility(&fw_params), + ComponentResponseCode::CompCanBeUpdated + ); + + let comp = FirmwareComponent::new( + ComponentClassification::Firmware as u16, + 0x0001, + 0x01, + 0x12345670, + PldmFirmwareString::new("UTF-8", "mcu-runtime-1.2").unwrap(), + None, + None, + ); + assert_eq!( + comp.evaluate_update_eligibility(&fw_params), + ComponentResponseCode::CompComparisonStampLower + ); + + let comp = FirmwareComponent::new( + ComponentClassification::Firmware as u16, + 0x0001, + 0x01, + 0x12345678, + PldmFirmwareString::new("UTF-8", "mcu-runtime-1.5").unwrap(), + None, + None, + ); + assert_eq!( + comp.evaluate_update_eligibility(&fw_params), + ComponentResponseCode::CompComparisonStampIdentical + ); + + let comp = FirmwareComponent::new( + ComponentClassification::Firmware as u16, + 0x0001, + 0x01, + 0x12345680, + PldmFirmwareString::new("UTF-8", "mcu-runtime-0.5").unwrap(), + None, + None, + ); + assert_eq!( + comp.evaluate_update_eligibility(&fw_params), + ComponentResponseCode::CompVerStrLower + ); + + let comp = FirmwareComponent::new( + ComponentClassification::Firmware as u16, + 0x0001, + 0x01, + 0x12345680, + PldmFirmwareString::new("UTF-8", "mcu-runtime-1.0").unwrap(), + None, + None, + ); + assert_eq!( + comp.evaluate_update_eligibility(&fw_params), + ComponentResponseCode::CompVerStrIdentical + ); + + let comp = FirmwareComponent::new( + ComponentClassification::Firmware as u16, + 0x0001, + 0x05, + 0x12345680, + PldmFirmwareString::new("UTF-8", "mcu-runtime-1.0").unwrap(), + None, + None, + ); + assert_eq!( + comp.evaluate_update_eligibility(&fw_params), + ComponentResponseCode::CompNotSupported + ); + } +} diff --git a/pldm-common/src/util/mctp_transport.rs b/pldm-common/src/util/mctp_transport.rs new file mode 100644 index 0000000..166163a --- /dev/null +++ b/pldm-common/src/util/mctp_transport.rs @@ -0,0 +1,110 @@ +// Licensed under the Apache-2.0 license + +use crate::error::UtilError; +use crate::protocol::base::PLDM_MSG_HEADER_LEN; +use bitfield::bitfield; + +pub const MCTP_PLDM_MSG_TYPE: u8 = 0x01; +pub const MCTP_COMMON_HEADER_OFFSET: usize = 0; +pub const PLDM_MSG_OFFSET: usize = 1; + +bitfield! { + #[derive(Copy, Clone, PartialEq)] + pub struct MctpCommonHeader(u8); + impl Debug; + pub u8, ic, set_ic: 7, 7; + pub u8, msg_type, set_msg_type: 6, 0; +} + +// +/// Extracts the PLDM message from the given MCTP payload. +/// +/// # Arguments +/// +/// * `mctp_payload` - A mutable reference to the MCTP payload. +/// +/// # Returns +/// +/// * `Result<&mut [u8], UtilError>` - A result containing a mutable reference to the PLDM message slice +/// if successful, or a `UtilError` if the payload length is invalid or the message type is incorrect. +pub fn extract_pldm_msg(mctp_payload: &mut [u8]) -> Result<&mut [u8], UtilError> { + // Check if the payload length is sufficient to contain the MCTP common header and PLDM message header. + if mctp_payload.len() < 1 + PLDM_MSG_HEADER_LEN { + return Err(UtilError::InvalidMctpPayloadLength); + } + + // Extract the MCTP common header from the payload. + let mctp_common_header = MctpCommonHeader(mctp_payload[MCTP_COMMON_HEADER_OFFSET]); + + // Validate the integrity check (IC) and message type fields. + if mctp_common_header.ic() != 0 || mctp_common_header.msg_type() != MCTP_PLDM_MSG_TYPE { + return Err(UtilError::InvalidMctpMsgType); + } + + // Return a mutable reference to the PLDM message slice. + Ok(&mut mctp_payload[PLDM_MSG_OFFSET..]) +} + +/// Constructs an MCTP payload with a PLDM message. +/// +/// # Arguments +/// +/// * `mctp_payload` - A mutable reference to the MCTP payload. +/// +/// # Returns +/// +/// * `Result<&mut [u8], UtilError>` - A result containing a mutable reference to the PLDM message slice +/// if successful, or a `UtilError` if the payload length is invalid. +pub fn construct_mctp_pldm_msg(mctp_payload: &mut [u8]) -> Result<&mut [u8], UtilError> { + // Check if the payload length is sufficient to contain the MCTP common header and PLDM message header. + if mctp_payload.len() < 1 + PLDM_MSG_HEADER_LEN { + return Err(UtilError::InvalidMctpPayloadLength); + } + + // Initialize the MCTP common header. + let mut mctp_common_header = MctpCommonHeader(0); + mctp_common_header.set_ic(0); + mctp_common_header.set_msg_type(MCTP_PLDM_MSG_TYPE); + + // Set the MCTP common header in the payload. + mctp_payload[MCTP_COMMON_HEADER_OFFSET] = mctp_common_header.0; + + // Return a mutable reference to the PLDM message slice. + Ok(&mut mctp_payload[PLDM_MSG_OFFSET..]) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_retrieve_pldm_msg() { + let mut mctp_payload = [0u8; 8]; + assert_eq!( + extract_pldm_msg(&mut mctp_payload), + Err(UtilError::InvalidMctpMsgType) + ); + mctp_payload[0] = 0x01; + assert_eq!(extract_pldm_msg(&mut mctp_payload).unwrap(), &mut [0u8; 7]); + let mut mctp_payload = [0u8; 3]; + assert_eq!( + extract_pldm_msg(&mut mctp_payload), + Err(UtilError::InvalidMctpPayloadLength) + ); + } + + #[test] + fn test_construct_mctp_pldm_msg() { + let mut mctp_payload = [0u8; 10]; + assert_eq!( + construct_mctp_pldm_msg(&mut mctp_payload).unwrap(), + &mut [0u8; 9] + ); + assert_eq!(mctp_payload[0], 0x01); + let mut mctp_payload = [0u8; 3]; + assert_eq!( + construct_mctp_pldm_msg(&mut mctp_payload), + Err(UtilError::InvalidMctpPayloadLength) + ); + } +} diff --git a/pldm-common/src/util/mod.rs b/pldm-common/src/util/mod.rs new file mode 100644 index 0000000..e435fb3 --- /dev/null +++ b/pldm-common/src/util/mod.rs @@ -0,0 +1,4 @@ +// Licensed under the Apache-2.0 license + +pub mod fw_component; +pub mod mctp_transport; diff --git a/pldm-service/Cargo.toml b/pldm-service/Cargo.toml new file mode 100644 index 0000000..403f5d5 --- /dev/null +++ b/pldm-service/Cargo.toml @@ -0,0 +1,12 @@ +# Licensed under the Apache-2.0 license + +[package] +name = "pldm-service" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[dependencies] +zerocopy.workspace = true +bitfield.workspace = true +pldm-common.workspace = true diff --git a/pldm-service/src/cmd_interface.rs b/pldm-service/src/cmd_interface.rs new file mode 100644 index 0000000..02ed640 --- /dev/null +++ b/pldm-service/src/cmd_interface.rs @@ -0,0 +1,226 @@ +// Licensed under the Apache-2.0 license + +use crate::control_context::{ControlContext, CtrlCmdResponder, ProtocolCapability}; +use crate::error::MsgHandlerError; +use crate::firmware_device::fd_context::FirmwareDeviceContext; +use crate::transport::{self, MctpTransport}; +use core::sync::atomic::{AtomicBool, Ordering}; +use pldm_common::codec::PldmCodec; +use pldm_common::protocol::base::{ + PldmBaseCompletionCode, PldmControlCmd, PldmFailureResponse, PldmMsgHeader, PldmSupportedType, +}; +use pldm_common::protocol::firmware_update::FwUpdateCmd; +use pldm_common::util::mctp_transport::{ + construct_mctp_pldm_msg, extract_pldm_msg, PLDM_MSG_OFFSET, +}; + +pub type PldmCompletionErrorCode = u8; + +// Helper function to write a failure response message into payload +pub(crate) fn generate_failure_response( + payload: &mut [u8], + completion_code: u8, +) -> Result { + let header = PldmMsgHeader::decode(payload).map_err(MsgHandlerError::Codec)?; + let resp = PldmFailureResponse { + hdr: header.into_response(), + completion_code, + }; + resp.encode(payload).map_err(MsgHandlerError::Codec) +} + +pub struct CmdInterface<'a> { + ctrl_ctx: ControlContext<'a>, + fd_ctx: FirmwareDeviceContext, + transport: &'a mut dyn MctpTransport, + busy: AtomicBool, +} + +impl<'a> CmdInterface<'a> { + pub fn new( + protocol_capabilities: &'a [ProtocolCapability], + fd_ctx: FirmwareDeviceContext, + transport: &'a mut dyn MctpTransport, + ) -> Self { + let ctrl_ctx = ControlContext::new(protocol_capabilities); + Self { + ctrl_ctx, + fd_ctx, + transport, + busy: AtomicBool::new(false), + } + } + + pub fn handle_responder_msg( + &mut self, + msg_buf: &mut [u8], + ) -> Result<(), MsgHandlerError> { + // Receive msg from mctp transport + self.transport + .receive_request(msg_buf) + .map_err(MsgHandlerError::Transport)?; + + // Process the request + let resp_len = self.process_request(msg_buf)?; + + // Send the response + self.transport + .send_response(&msg_buf[..resp_len]) + .map_err(MsgHandlerError::Transport) + } + + pub fn handle_initiator_msg( + &mut self, + msg_buf: &mut [u8], + ) -> Result<(), MsgHandlerError> { + // Retrieve the UA EID from the configuration + let ua_eid: u8 = crate::config::UA_EID; + + // Prepare the request payload + let payload = construct_mctp_pldm_msg(msg_buf).map_err(MsgHandlerError::Util)?; + let reserved_len = PLDM_MSG_OFFSET; + + // Generate the request + let req_len = self.fd_ctx.fd_progress(payload)?; + if req_len == 0 { + return Ok(()); + } + + // Send the request + self.transport + .send_request(ua_eid, &msg_buf[..req_len + reserved_len]) + .map_err(MsgHandlerError::Transport)?; + + // Wait for and process the response + self.transport + .receive_response(msg_buf) + .map_err(MsgHandlerError::Transport)?; + + let payload = extract_pldm_msg(msg_buf).map_err(MsgHandlerError::Util)?; + + // Handle the response + self.fd_ctx.handle_response(payload)?; + + Ok(()) + } + + pub fn should_start_initiator_mode(&self) -> bool { + self.fd_ctx.should_start_initiator_mode() + } + + pub fn should_stop_initiator_mode(&self) -> bool { + self.fd_ctx.should_stop_initiator_mode() + } + + fn process_request(&self, msg_buf: &mut [u8]) -> Result { + // Check if the handler is busy processing a request + if self.busy.load(Ordering::SeqCst) { + return Err(MsgHandlerError::NotReady); + } + + self.busy.store(true, Ordering::SeqCst); + + // Get the pldm payload from msg_buf + let payload = &mut msg_buf[PLDM_MSG_OFFSET..]; + let reserved_len = PLDM_MSG_OFFSET; + + let (pldm_type, cmd_opcode) = match self.preprocess_request(payload) { + Ok(result) => result, + Err(e) => { + self.busy.store(false, Ordering::SeqCst); + return Ok(reserved_len + generate_failure_response(payload, e)?); + } + }; + + let resp_len = match pldm_type { + PldmSupportedType::Base => self.process_control_cmd(cmd_opcode, payload), + PldmSupportedType::FwUpdate => self.process_fw_update_cmd(cmd_opcode, payload), + _ => { + unreachable!() + } + }; + + self.busy.store(false, Ordering::SeqCst); + + match resp_len { + Ok(bytes) => Ok(reserved_len + bytes), + Err(e) => Err(e), + } + } + + fn process_control_cmd( + &self, + cmd_opcode: u8, + payload: &mut [u8], + ) -> Result { + match PldmControlCmd::try_from(cmd_opcode) { + Ok(cmd) => match cmd { + PldmControlCmd::GetTid => self.ctrl_ctx.get_tid_rsp(payload), + PldmControlCmd::SetTid => self.ctrl_ctx.set_tid_rsp(payload), + PldmControlCmd::GetPldmTypes => self.ctrl_ctx.get_pldm_types_rsp(payload), + PldmControlCmd::GetPldmCommands => self.ctrl_ctx.get_pldm_commands_rsp(payload), + PldmControlCmd::GetPldmVersion => self.ctrl_ctx.get_pldm_version_rsp(payload), + }, + Err(_) => { + generate_failure_response(payload, PldmBaseCompletionCode::UnsupportedPldmCmd as u8) + } + } + } + + fn process_fw_update_cmd( + &self, + cmd_opcode: u8, + payload: &mut [u8], + ) -> Result { + match FwUpdateCmd::try_from(cmd_opcode) { + Ok(cmd) => match cmd { + FwUpdateCmd::QueryDeviceIdentifiers => self.fd_ctx.query_devid_rsp(payload), + FwUpdateCmd::GetFirmwareParameters => { + self.fd_ctx.get_firmware_parameters_rsp(payload) + } + FwUpdateCmd::RequestUpdate => self.fd_ctx.request_update_rsp(payload), + FwUpdateCmd::PassComponentTable => self.fd_ctx.pass_component_rsp(payload), + FwUpdateCmd::UpdateComponent => self.fd_ctx.update_component_rsp(payload), + + FwUpdateCmd::ActivateFirmware => self.fd_ctx.activate_firmware_rsp(payload), + FwUpdateCmd::CancelUpdateComponent => { + self.fd_ctx.cancel_update_component_rsp(payload) + } + FwUpdateCmd::CancelUpdate => self.fd_ctx.cancel_update_rsp(payload), + FwUpdateCmd::GetStatus => self.fd_ctx.get_status_rsp(payload), + _ => generate_failure_response( + payload, + PldmBaseCompletionCode::UnsupportedPldmCmd as u8, + ), + }, + Err(_) => { + generate_failure_response(payload, PldmBaseCompletionCode::UnsupportedPldmCmd as u8) + } + } + } + + fn preprocess_request( + &self, + payload: &[u8], + ) -> Result<(PldmSupportedType, u8), PldmCompletionErrorCode> { + let header = PldmMsgHeader::decode(payload) + .map_err(|_| PldmBaseCompletionCode::InvalidData as u8)?; + if !(header.is_request() && header.is_hdr_ver_valid()) { + Err(PldmBaseCompletionCode::InvalidData as u8)?; + } + + let pldm_type = PldmSupportedType::try_from(header.pldm_type()) + .map_err(|_| PldmBaseCompletionCode::InvalidPldmType as u8)?; + + if !self.ctrl_ctx.is_supported_type(pldm_type) { + Err(PldmBaseCompletionCode::InvalidPldmType as u8)?; + } + + let cmd_opcode = header.cmd_code(); + if self.ctrl_ctx.is_supported_command(pldm_type, cmd_opcode) { + Ok((pldm_type, cmd_opcode)) + } else { + Err(PldmBaseCompletionCode::UnsupportedPldmCmd as u8) + } + } +} diff --git a/pldm-service/src/config.rs b/pldm-service/src/config.rs new file mode 100644 index 0000000..f338071 --- /dev/null +++ b/pldm-service/src/config.rs @@ -0,0 +1,50 @@ +// Licensed under the Apache-2.0 license + +use crate::control_context::ProtocolCapability; +use std::sync::LazyLock; +use pldm_common::protocol::base::{PldmControlCmd, PldmSupportedType}; +use pldm_common::protocol::firmware_update::{FwUpdateCmd, PldmFdTime}; + +pub const PLDM_PROTOCOL_CAP_COUNT: usize = 2; +pub const FD_MAX_XFER_SIZE: usize = 512; // Arbitrary limit and change as needed. +pub const DEFAULT_FD_T1_TIMEOUT: PldmFdTime = 120000; // FD_T1 update mode idle timeout, range is [60s, 120s]. +pub const DEFAULT_FD_T2_RETRY_TIME: PldmFdTime = 5000; // FD_T2 retry request for firmware data, range is [1s, 5s]. +pub const INSTANCE_ID_COUNT: u8 = 32; +pub const UA_EID: u8 = 8; // Update Agent Endpoint ID for testing. + +pub static PLDM_PROTOCOL_CAPABILITIES: LazyLock< + [ProtocolCapability<'static>; PLDM_PROTOCOL_CAP_COUNT], +> = LazyLock::new(|| { + [ + ProtocolCapability { + pldm_type: PldmSupportedType::Base, + protocol_version: 0xF1F1F000, //"1.1.0" + supported_commands: &[ + PldmControlCmd::SetTid as u8, + PldmControlCmd::GetTid as u8, + PldmControlCmd::GetPldmCommands as u8, + PldmControlCmd::GetPldmVersion as u8, + PldmControlCmd::GetPldmTypes as u8, + ], + }, + ProtocolCapability { + pldm_type: PldmSupportedType::FwUpdate, + protocol_version: 0xF1F3F000, // "1.3.0" + supported_commands: &[ + FwUpdateCmd::QueryDeviceIdentifiers as u8, + FwUpdateCmd::GetFirmwareParameters as u8, + FwUpdateCmd::RequestUpdate as u8, + FwUpdateCmd::PassComponentTable as u8, + FwUpdateCmd::UpdateComponent as u8, + FwUpdateCmd::RequestFirmwareData as u8, + FwUpdateCmd::TransferComplete as u8, + FwUpdateCmd::VerifyComplete as u8, + FwUpdateCmd::ApplyComplete as u8, + FwUpdateCmd::ActivateFirmware as u8, + FwUpdateCmd::GetStatus as u8, + FwUpdateCmd::CancelUpdateComponent as u8, + FwUpdateCmd::CancelUpdate as u8, + ], + }, + ] +}); diff --git a/pldm-service/src/control_context.rs b/pldm-service/src/control_context.rs new file mode 100644 index 0000000..ae381c5 --- /dev/null +++ b/pldm-service/src/control_context.rs @@ -0,0 +1,309 @@ +// Licensed under the Apache-2.0 license + +use crate::cmd_interface::generate_failure_response; +use crate::error::MsgHandlerError; +use core::sync::atomic::{AtomicUsize, Ordering}; +use pldm_common::codec::PldmCodec; +use pldm_common::error::PldmError; +use pldm_common::message::control::{ + GetPldmCommandsRequest, GetPldmCommandsResponse, GetPldmTypeRequest, GetPldmTypeResponse, + GetPldmVersionRequest, GetPldmVersionResponse, GetTidRequest, GetTidResponse, SetTidRequest, + SetTidResponse, +}; +use pldm_common::protocol::base::{ + PldmBaseCompletionCode, PldmControlCompletionCode, PldmSupportedType, TransferOperationFlag, + TransferRespFlag, +}; +use pldm_common::protocol::version::{PldmVersion, ProtocolVersionStr, Ver32}; + +pub type Tid = u8; +pub type CmdOpCode = u8; +pub const UNASSIGNED_TID: Tid = 0; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct ProtocolCapability<'a> { + pub pldm_type: PldmSupportedType, + pub protocol_version: Ver32, + pub supported_commands: &'a [CmdOpCode], +} + +impl<'a> ProtocolCapability<'a> { + pub fn new( + pldm_type: PldmSupportedType, + protocol_version: ProtocolVersionStr, + supported_commands: &'a [CmdOpCode], + ) -> Result { + Ok(Self { + pldm_type, + protocol_version: match PldmVersion::try_from(protocol_version) { + Ok(ver) => ver.bcd_encode_to_ver32(), + Err(_) => return Err(PldmError::InvalidProtocolVersion), + }, + supported_commands, + }) + } +} + +/// `ControlContext` is a structure that holds the control context for the PLDM library. +/// +/// # Fields +/// +/// * `tid` - An atomic unsigned size integer representing the transaction ID. +/// * `capabilities` - A reference to a slice of `ProtocolCapability` which represents the protocol capabilities. +pub struct ControlContext<'a> { + tid: AtomicUsize, + capabilities: &'a [ProtocolCapability<'a>], +} + +impl<'a> ControlContext<'a> { + pub fn new(capabilities: &'a [ProtocolCapability<'a>]) -> Self { + Self { + tid: AtomicUsize::new(UNASSIGNED_TID as usize), + capabilities, + } + } + + pub fn get_tid(&self) -> Tid { + self.tid.load(Ordering::SeqCst) as Tid + } + + pub fn set_tid(&self, tid: Tid) { + self.tid.store(tid as usize, Ordering::SeqCst); + } + + pub fn get_supported_commands( + &self, + pldm_type: PldmSupportedType, + protocol_version: Ver32, + ) -> Option<&[CmdOpCode]> { + self.capabilities + .iter() + .find(|cap| cap.pldm_type == pldm_type && cap.protocol_version == protocol_version) + .map(|cap| cap.supported_commands) + } + + pub fn get_protocol_versions( + &self, + pldm_type: PldmSupportedType, + versions: &mut [Ver32], + ) -> usize { + let mut count = 0; + for cap in self + .capabilities + .iter() + .filter(|cap| cap.pldm_type == pldm_type) + { + if count < versions.len() { + versions[count] = cap.protocol_version; + count += 1; + } else { + break; + } + } + count + } + + pub fn get_supported_types(&self, types: &mut [u8]) -> usize { + let mut count = 0; + for cap in self.capabilities.iter() { + let pldm_type = cap.pldm_type as u8; + if !types[..count].contains(&pldm_type) { + if count < types.len() { + types[count] = pldm_type; + count += 1; + } else { + break; + } + } + } + count + } + + pub fn is_supported_type(&self, pldm_type: PldmSupportedType) -> bool { + self.capabilities + .iter() + .any(|cap| cap.pldm_type == pldm_type) + } + + pub fn is_supported_version( + &self, + pldm_type: PldmSupportedType, + protocol_version: Ver32, + ) -> bool { + self.capabilities + .iter() + .any(|cap| cap.pldm_type == pldm_type && cap.protocol_version == protocol_version) + } + + pub fn is_supported_command(&self, pldm_type: PldmSupportedType, cmd: u8) -> bool { + self.capabilities + .iter() + .find(|cap| cap.pldm_type == pldm_type) + .is_some_and(|cap| cap.supported_commands.contains(&cmd)) + } +} + +/// Trait representing a responder for control commands in the PLDM protocol. +/// Implementors of this trait are responsible for handling various control commands +/// and generating appropriate responses. +/// +/// # Methods +/// +/// - `get_tid_rsp`: Generates a response for the "Get TID" command. +/// - `set_tid_rsp`: Generates a response for the "Set TID" command. +/// - `get_pldm_types_rsp`: Generates a response for the "Get PLDM Types" command. +/// - `get_pldm_commands_rsp`: Generates a response for the "Get PLDM Commands" command. +/// - `get_pldm_version_rsp`: Generates a response for the "Get PLDM Version" command. +/// +/// Each method takes a mutable reference to a payload buffer and returns a `Result` +/// containing the size of the response or a `MsgHandlerError` if an error occurs. +pub trait CtrlCmdResponder { + fn get_tid_rsp(&self, payload: &mut [u8]) -> Result; + fn set_tid_rsp(&self, payload: &mut [u8]) -> Result; + fn get_pldm_types_rsp(&self, payload: &mut [u8]) -> Result; + fn get_pldm_commands_rsp(&self, payload: &mut [u8]) -> Result; + fn get_pldm_version_rsp(&self, payload: &mut [u8]) -> Result; +} + +impl CtrlCmdResponder for ControlContext<'_> { + fn get_tid_rsp(&self, payload: &mut [u8]) -> Result { + let req = GetTidRequest::decode(payload).map_err(MsgHandlerError::Codec)?; + let resp = GetTidResponse::new( + req.hdr.instance_id(), + self.get_tid(), + PldmBaseCompletionCode::Success as u8, + ); + resp.encode(payload).map_err(MsgHandlerError::Codec) + } + + fn set_tid_rsp(&self, payload: &mut [u8]) -> Result { + let req = SetTidRequest::decode(payload).map_err(MsgHandlerError::Codec)?; + self.set_tid(req.tid); + let resp = + SetTidResponse::new(req.hdr.instance_id(), PldmBaseCompletionCode::Success as u8); + resp.encode(payload).map_err(MsgHandlerError::Codec) + } + + fn get_pldm_types_rsp(&self, payload: &mut [u8]) -> Result { + let req = GetPldmTypeRequest::decode(payload).map_err(MsgHandlerError::Codec)?; + let mut types = [0x0u8; 6]; + let num_types = self.get_supported_types(&mut types); + let resp = GetPldmTypeResponse::new( + req.hdr.instance_id(), + PldmBaseCompletionCode::Success as u8, + &types[..num_types], + ); + resp.encode(payload).map_err(MsgHandlerError::Codec) + } + + fn get_pldm_commands_rsp(&self, payload: &mut [u8]) -> Result { + let req = match GetPldmCommandsRequest::decode(payload) { + Ok(req) => req, + Err(_) => { + return generate_failure_response( + payload, + PldmBaseCompletionCode::InvalidLength as u8, + ) + } + }; + + let pldm_type_in_req = match PldmSupportedType::try_from(req.pldm_type) { + Ok(pldm_type) => pldm_type, + Err(_) => { + return generate_failure_response( + payload, + PldmControlCompletionCode::InvalidPldmTypeInRequestData as u8, + ) + } + }; + + if !self.is_supported_type(pldm_type_in_req) { + return generate_failure_response( + payload, + PldmControlCompletionCode::InvalidPldmTypeInRequestData as u8, + ); + } + + let version_in_req = req.protocol_version; + if !self.is_supported_version(pldm_type_in_req, version_in_req) { + return generate_failure_response( + payload, + PldmControlCompletionCode::InvalidPldmVersionInRequestData as u8, + ); + } + + let cmds = self + .get_supported_commands(pldm_type_in_req, version_in_req) + .unwrap(); + + let resp = GetPldmCommandsResponse::new( + req.hdr.instance_id(), + PldmBaseCompletionCode::Success as u8, + cmds, + ); + + match resp.encode(payload) { + Ok(bytes) => Ok(bytes), + Err(_) => { + generate_failure_response(payload, PldmBaseCompletionCode::InvalidLength as u8) + } + } + } + + fn get_pldm_version_rsp(&self, payload: &mut [u8]) -> Result { + let req = match GetPldmVersionRequest::decode(payload) { + Ok(req) => req, + Err(_) => { + return generate_failure_response( + payload, + PldmBaseCompletionCode::InvalidLength as u8, + ) + } + }; + + let pldm_type_in_req = match PldmSupportedType::try_from(req.pldm_type) { + Ok(pldm_type) => pldm_type, + Err(_) => { + return generate_failure_response( + payload, + PldmControlCompletionCode::InvalidPldmTypeInRequestData as u8, + ) + } + }; + + if !self.is_supported_type(pldm_type_in_req) { + return generate_failure_response( + payload, + PldmControlCompletionCode::InvalidPldmTypeInRequestData as u8, + ); + } + + if req.transfer_op_flag != TransferOperationFlag::GetFirstPart as u8 { + return generate_failure_response( + payload, + PldmControlCompletionCode::InvalidTransferOperationFlag as u8, + ); + } + + let mut versions = [0u32; 2]; + if self.get_protocol_versions(pldm_type_in_req, &mut versions) == 0 { + return generate_failure_response(payload, PldmBaseCompletionCode::Error as u8); + } + + // Only one version is supported for now + let resp = GetPldmVersionResponse { + hdr: req.hdr.into_response(), + completion_code: PldmBaseCompletionCode::Success as u8, + next_transfer_handle: 0, + transfer_rsp_flag: TransferRespFlag::StartAndEnd as u8, + version_data: versions[0], + }; + + match resp.encode(payload) { + Ok(bytes) => Ok(bytes), + Err(_) => { + generate_failure_response(payload, PldmBaseCompletionCode::InvalidLength as u8) + } + } + } +} \ No newline at end of file diff --git a/pldm-service/src/error.rs b/pldm-service/src/error.rs new file mode 100644 index 0000000..8f42a18 --- /dev/null +++ b/pldm-service/src/error.rs @@ -0,0 +1,18 @@ +// Licensed under the Apache-2.0 license + +use crate::firmware_device::fd_ops::FdOpsError; +use crate::transport::TransportError; +use pldm_common::codec::PldmCodecError; +use pldm_common::error::{PldmError, UtilError}; + +/// Handle non-protocol specific error conditions. +#[derive(Debug)] +pub enum MsgHandlerError { + Codec(PldmCodecError), + Transport(TransportError), + PldmCommon(PldmError), + Util(UtilError), + FdOps(FdOpsError), + FdInitiatorModeError, + NotReady, +} diff --git a/pldm-service/src/firmware_device/fd_context.rs b/pldm-service/src/firmware_device/fd_context.rs new file mode 100644 index 0000000..b84aedd --- /dev/null +++ b/pldm-service/src/firmware_device/fd_context.rs @@ -0,0 +1,1046 @@ +// Licensed under the Apache-2.0 license + +use crate::cmd_interface::generate_failure_response; +use crate::error::MsgHandlerError; +use crate::firmware_device::fd_internal::{FdInternal, FdReqState}; +use crate::firmware_device::fd_ops::{ComponentOperation, FdOps}; +use pldm_common::codec::PldmCodec; +use pldm_common::message::firmware_update::activate_fw::{ + ActivateFirmwareRequest, ActivateFirmwareResponse, +}; +use pldm_common::message::firmware_update::get_fw_params::{ + FirmwareParameters, GetFirmwareParametersRequest, GetFirmwareParametersResponse, +}; +use pldm_common::message::firmware_update::get_status::ProgressPercent; +use pldm_common::message::firmware_update::pass_component::{ + PassComponentTableRequest, PassComponentTableResponse, +}; +use pldm_common::message::firmware_update::query_devid::{ + QueryDeviceIdentifiersRequest, QueryDeviceIdentifiersResponse, +}; +use pldm_common::message::firmware_update::request_cancel::{ + CancelUpdateComponentRequest, CancelUpdateComponentResponse, CancelUpdateRequest, + CancelUpdateResponse, +}; +use pldm_common::message::firmware_update::request_update::{ + RequestUpdateRequest, RequestUpdateResponse, +}; +use pldm_common::message::firmware_update::transfer_complete::{ + TransferCompleteRequest, TransferResult, +}; +use pldm_common::message::firmware_update::update_component::{ + UpdateComponentRequest, UpdateComponentResponse, +}; + +use pldm_common::codec::PldmCodecError; +use pldm_common::message::firmware_update::apply_complete::{ApplyCompleteRequest, ApplyResult}; +use pldm_common::message::firmware_update::get_status::{ + AuxState, AuxStateStatus, GetStatusReasonCode, GetStatusRequest, GetStatusResponse, + UpdateOptionResp, +}; +use pldm_common::message::firmware_update::request_fw_data::{ + RequestFirmwareDataRequest, RequestFirmwareDataResponseFixed, +}; +use pldm_common::message::firmware_update::verify_complete::{VerifyCompleteRequest, VerifyResult}; +use pldm_common::protocol::base::{ + PldmBaseCompletionCode, PldmMsgHeader, PldmMsgType, TransferRespFlag, +}; +use pldm_common::protocol::firmware_update::{ + ComponentActivationMethods, ComponentCompatibilityResponse, ComponentCompatibilityResponseCode, + ComponentResponse, ComponentResponseCode, Descriptor, FirmwareDeviceState, FwUpdateCmd, + FwUpdateCompletionCode, PldmFirmwareString, UpdateOptionFlags, MAX_DESCRIPTORS_COUNT, + PLDM_FWUP_BASELINE_TRANSFER_SIZE, +}; +use pldm_common::util::fw_component::FirmwareComponent; + +use crate::firmware_device::fd_internal::{ + ApplyState, DownloadState, InitiatorModeState, VerifyState, +}; + +pub struct FirmwareDeviceContext { + ops: FdOps, + internal: FdInternal, +} + +impl FirmwareDeviceContext { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { + ops: FdOps::new(), + internal: FdInternal::new() + } + } + + pub fn query_devid_rsp(&self, payload: &mut [u8]) -> Result { + // Decode the request message + let req = QueryDeviceIdentifiersRequest::decode(payload).map_err(MsgHandlerError::Codec)?; + + let mut device_identifiers: [Descriptor; MAX_DESCRIPTORS_COUNT] = + [Descriptor::default(); MAX_DESCRIPTORS_COUNT]; + + // Get the device identifiers + let descriptor_cnt = self + .ops + .get_device_identifiers(&mut device_identifiers) //CAD Driver Call + .map_err(MsgHandlerError::FdOps)?; + + // Create the response message + let resp = QueryDeviceIdentifiersResponse::new( + req.hdr.instance_id(), + PldmBaseCompletionCode::Success as u8, + &device_identifiers[0], + device_identifiers.get(1..descriptor_cnt), + ) + .map_err(MsgHandlerError::PldmCommon)?; + + match resp.encode(payload) { + Ok(bytes) => Ok(bytes), + Err(_) => { + generate_failure_response(payload, PldmBaseCompletionCode::InvalidLength as u8) + } + } + } + + pub fn get_firmware_parameters_rsp( + &self, + payload: &mut [u8], + ) -> Result { + // Decode the request message + let req = GetFirmwareParametersRequest::decode(payload).map_err(MsgHandlerError::Codec)?; + + let mut firmware_params = FirmwareParameters::default(); + self.ops + .get_firmware_parms(&mut firmware_params) + .map_err(MsgHandlerError::FdOps)?; + + // Construct response + let resp = GetFirmwareParametersResponse::new( + req.hdr.instance_id(), + PldmBaseCompletionCode::Success as u8, + &firmware_params, + ); + + match resp.encode(payload) { + Ok(bytes) => Ok(bytes), + Err(_) => { + generate_failure_response(payload, PldmBaseCompletionCode::InvalidLength as u8) + } + } + } + + pub fn request_update_rsp(&self, payload: &mut [u8]) -> Result { + // Check if FD is in idle state. Otherwise returns 'ALREADY_IN_UPDATE_MODE' completion code + if self.internal.is_update_mode() { + return generate_failure_response( + payload, + FwUpdateCompletionCode::AlreadyInUpdateMode as u8, + ); + } + + // Set timestamp for FD T1 timeout + self.set_fd_t1_ts(); + + // Decode the request message + let req = RequestUpdateRequest::decode(payload).map_err(MsgHandlerError::Codec)?; + let ua_transfer_size = req.fixed.max_transfer_size as usize; + if ua_transfer_size < PLDM_FWUP_BASELINE_TRANSFER_SIZE { + return generate_failure_response( + payload, + FwUpdateCompletionCode::InvalidTransferLength as u8, + ); + } + + // Get the transfer size for the firmware update operation + let fd_transfer_size = self + .ops + .get_xfer_size(ua_transfer_size) + .map_err(MsgHandlerError::FdOps)?; + + // Set transfer size to the internal state + self.internal.set_xfer_size(fd_transfer_size); + + // Construct response, no metadata or package data. + let resp = RequestUpdateResponse::new( + req.fixed.hdr.instance_id(), + PldmBaseCompletionCode::Success as u8, + 0, + 0, + None, + ); + + match resp.encode(payload) { + Ok(bytes) => { + // Move FD state to 'LearnComponents' + self.internal + .set_fd_state(FirmwareDeviceState::LearnComponents); + Ok(bytes) + } + Err(_) => { + generate_failure_response(payload, PldmBaseCompletionCode::InvalidLength as u8) + } + } + } + + pub fn pass_component_rsp(&self, payload: &mut [u8]) -> Result { + // Check if FD is in 'LearnComponents' state. Otherwise returns 'INVALID_STATE' completion code + if self.internal.get_fd_state() != FirmwareDeviceState::LearnComponents { + return generate_failure_response( + payload, + FwUpdateCompletionCode::InvalidStateForCommand as u8, + ); + } + + // Set timestamp for FD T1 timeout + self.set_fd_t1_ts(); + + // Decode the request message + let req = PassComponentTableRequest::decode(payload).map_err(MsgHandlerError::Codec)?; + let transfer_flag = match TransferRespFlag::try_from(req.fixed.transfer_flag) { + Ok(flag) => flag, + Err(_) => { + return generate_failure_response( + payload, + PldmBaseCompletionCode::InvalidData as u8, + ) + } + }; + + // Construct temporary storage for the component + let pass_comp = FirmwareComponent::new( + req.fixed.comp_classification, + req.fixed.comp_identifier, + req.fixed.comp_classification_index, + req.fixed.comp_comparison_stamp, + PldmFirmwareString { + str_type: req.fixed.comp_ver_str_type, + str_len: req.fixed.comp_ver_str_len, + str_data: req.comp_ver_str, + }, + None, + None, + ); + + let mut firmware_params = FirmwareParameters::default(); + self.ops + .get_firmware_parms(&mut firmware_params) + .map_err(MsgHandlerError::FdOps)?; + + let comp_resp_code = self + .ops + .handle_component( + &pass_comp, + &firmware_params, + ComponentOperation::PassComponent, + ) + .map_err(MsgHandlerError::FdOps)?; + + // Construct response + let resp = PassComponentTableResponse::new( + req.fixed.hdr.instance_id(), + PldmBaseCompletionCode::Success as u8, + if comp_resp_code == ComponentResponseCode::CompCanBeUpdated { + ComponentResponse::CompCanBeUpdated + } else { + ComponentResponse::CompCannotBeUpdated + }, + comp_resp_code, + ); + + match resp.encode(payload) { + Ok(bytes) => { + // Move FD state to 'ReadyTransfer' when the last component is passed + if transfer_flag == TransferRespFlag::End + || transfer_flag == TransferRespFlag::StartAndEnd + { + self.internal + .set_fd_state(FirmwareDeviceState::ReadyXfer); + } + Ok(bytes) + } + Err(_) => { + generate_failure_response(payload, PldmBaseCompletionCode::InvalidLength as u8) + } + } + } + + pub fn update_component_rsp(&self, payload: &mut [u8]) -> Result { + // Check if FD is in 'ReadyTransfer' state. Otherwise returns 'INVALID_STATE' completion code + if self.internal.get_fd_state() != FirmwareDeviceState::ReadyXfer { + return generate_failure_response( + payload, + FwUpdateCompletionCode::InvalidStateForCommand as u8, + ); + } + + // Set timestamp for FD T1 timeout + self.set_fd_t1_ts(); + + // Decode the request message + let req = UpdateComponentRequest::decode(payload).map_err(MsgHandlerError::Codec)?; + + // Construct temporary storage for the component + let update_comp = FirmwareComponent::new( + req.fixed.comp_classification, + req.fixed.comp_identifier, + req.fixed.comp_classification_index, + req.fixed.comp_comparison_stamp, + PldmFirmwareString { + str_type: req.fixed.comp_ver_str_type, + str_len: req.fixed.comp_ver_str_len, + str_data: req.comp_ver_str, + }, + Some(req.fixed.comp_image_size), + Some(UpdateOptionFlags(req.fixed.update_option_flags)), + ); + + // Store the component info into the internal state. + self.internal.set_component(&update_comp); + + // Adjust the update flags based on the device's capabilities if needed. Currently, the flags are set as received from the UA. + self.internal + .set_update_flags(UpdateOptionFlags(req.fixed.update_option_flags)); + + let mut firmware_params = FirmwareParameters::default(); + self.ops + .get_firmware_parms(&mut firmware_params) + .map_err(MsgHandlerError::FdOps)?; + + let comp_resp_code = self + .ops + .handle_component( + &update_comp, + &firmware_params, + ComponentOperation::UpdateComponent, /* This indicates this is an update request */ + ) + .map_err(MsgHandlerError::FdOps)?; + + // Construct response + let resp = UpdateComponentResponse::new( + req.fixed.hdr.instance_id(), + PldmBaseCompletionCode::Success as u8, + if comp_resp_code == ComponentResponseCode::CompCanBeUpdated { + ComponentCompatibilityResponse::CompCanBeUpdated + } else { + ComponentCompatibilityResponse::CompCannotBeUpdated + }, + ComponentCompatibilityResponseCode::try_from(comp_resp_code as u8).unwrap(), + UpdateOptionFlags(req.fixed.update_option_flags), + 0, + None, + ); + + match resp.encode(payload) { + Ok(bytes) => { + if comp_resp_code == ComponentResponseCode::CompCanBeUpdated { + self.internal + .set_initiator_mode(InitiatorModeState::Download(DownloadState::default())); + // Set up the req for download. + self.internal + .set_fd_req(FdReqState::Ready, false, None, None, None, None); + + // Move FD state machine to download state. + self.internal + .set_fd_state(FirmwareDeviceState::Download); + } + Ok(bytes) + } + Err(_) => { + generate_failure_response(payload, PldmBaseCompletionCode::InvalidLength as u8) + } + } + } + + pub fn activate_firmware_rsp( + &self, + payload: &mut [u8], + ) -> Result { + // Check if FD is in 'ReadyTransfer' state. Otherwise returns 'INVALID_STATE' completion code + if self.internal.get_fd_state() != FirmwareDeviceState::ReadyXfer { + return generate_failure_response( + payload, + FwUpdateCompletionCode::InvalidStateForCommand as u8, + ); + } + + // Decode the request message + let req = ActivateFirmwareRequest::decode(payload).map_err(MsgHandlerError::Codec)?; + let self_contained = req.self_contained_activation_req; + + // Validate self_contained value + match self_contained { + 0 | 1 => {} + _ => { + return generate_failure_response( + payload, + PldmBaseCompletionCode::InvalidData as u8, + ) + } + } + + let mut estimated_time = 0u16; + let completion_code = self + .ops + .activate(self_contained, &mut estimated_time) + .map_err(MsgHandlerError::FdOps)?; + + // Construct response + let resp = + ActivateFirmwareResponse::new(req.hdr.instance_id(), completion_code, estimated_time); + + match resp.encode(payload) { + Ok(bytes) => { + if completion_code == PldmBaseCompletionCode::Success as u8 + || completion_code == FwUpdateCompletionCode::ActivationNotRequired as u8 + { + self.internal + .set_fd_state(FirmwareDeviceState::Activate); + self.internal + .set_fd_idle(GetStatusReasonCode::ActivateFw); + } + Ok(bytes) + } + Err(_) => { + generate_failure_response(payload, PldmBaseCompletionCode::InvalidLength as u8) + } + } + } + + pub fn cancel_update_component_rsp( + &self, + payload: &mut [u8], + ) -> Result { + // If FD is not in update mode, return 'NOT_IN_UPDATE_MODE' completion code + if !self.internal.is_update_mode() { + return generate_failure_response( + payload, + FwUpdateCompletionCode::NotInUpdateMode as u8, + ); + } + + let fd_state = self.internal.get_fd_state(); + let should_cancel = match fd_state { + FirmwareDeviceState::Download | FirmwareDeviceState::Verify => true, + FirmwareDeviceState::Apply => { + // In apply state, only cancel if not completed successfully + !(self.internal.is_fd_req_complete() + && self.internal.get_fd_req_result() + == Some(ApplyResult::ApplySuccess as u8)) + } + _ => { + return generate_failure_response( + payload, + FwUpdateCompletionCode::InvalidStateForCommand as u8, + ); + } + }; + + if should_cancel { + self.ops + .cancel_update_component(&self.internal.get_component()) + .map_err(MsgHandlerError::FdOps)?; + } + + // Decode the request message + let req = CancelUpdateComponentRequest::decode(payload).map_err(MsgHandlerError::Codec)?; + let completion_code = if should_cancel { + PldmBaseCompletionCode::Success as u8 + } else { + PldmBaseCompletionCode::Error as u8 + }; + + let resp = CancelUpdateComponentResponse::new(req.hdr.instance_id(), completion_code); + match resp.encode(payload) { + Ok(bytes) => { + if should_cancel { + // Set FD state to 'ReadyTransfer' + self.internal + .set_fd_state(FirmwareDeviceState::ReadyXfer); + } + Ok(bytes) + } + Err(_) => { + generate_failure_response(payload, PldmBaseCompletionCode::InvalidLength as u8) + } + } + } + + pub fn cancel_update_rsp(&self, payload: &mut [u8]) -> Result { + // If FD is not in update mode, return 'NOT_IN_UPDATE_MODE' completion code + if !self.internal.is_update_mode() { + return generate_failure_response( + payload, + FwUpdateCompletionCode::NotInUpdateMode as u8, + ); + } + + // Set timestamp for FD T1 timeout + self.set_fd_t1_ts(); + + let fd_state = self.internal.get_fd_state(); + let should_cancel = match fd_state { + FirmwareDeviceState::Download | FirmwareDeviceState::Verify => true, + FirmwareDeviceState::Apply => { + // In apply state, only cancel if not completed successfully + !(self.internal.is_fd_req_complete() + && self.internal.get_fd_req().result + == Some(ApplyResult::ApplySuccess as u8)) + } + _ => false, + }; + + if should_cancel { + self.ops + .cancel_update_component(&self.internal.get_component()) + .map_err(MsgHandlerError::FdOps)?; + } + + // Decode the request message + let req = CancelUpdateRequest::decode(payload).map_err(MsgHandlerError::Codec)?; + let completion_code = if should_cancel { + PldmBaseCompletionCode::Success as u8 + } else { + PldmBaseCompletionCode::Error as u8 + }; + + let (non_functioning_component_indication, non_functioning_component_bitmap) = self + .ops + .get_non_functional_component_info() + .map_err(MsgHandlerError::FdOps)?; + + let resp = CancelUpdateResponse::new( + req.hdr.instance_id(), + completion_code, + non_functioning_component_indication, + non_functioning_component_bitmap, + ); + + match resp.encode(payload) { + Ok(bytes) => { + if should_cancel { + // Set FD state to 'Idle' + self.internal + .set_fd_idle(GetStatusReasonCode::CancelUpdate); + } + Ok(bytes) + } + Err(_) => { + generate_failure_response(payload, PldmBaseCompletionCode::InvalidLength as u8) + } + } + } + + pub fn get_status_rsp(&self, payload: &mut [u8]) -> Result { + let req = GetStatusRequest::decode(payload).map_err(MsgHandlerError::Codec)?; + + let cur_state = self.internal.get_fd_state(); + let prev_state = self.internal.get_fd_prev_state(); + let (progress_percent, update_flags) = match cur_state { + FirmwareDeviceState::Download => { + let mut progress = ProgressPercent::default(); + let _ = self + .ops + .query_download_progress(&self.internal.get_component(), &mut progress); + let update_flags = self.internal.get_update_flags(); + (progress, update_flags) + } + FirmwareDeviceState::Verify => { + let progress = if let Some(percent) = self.internal.get_fd_verify_progress() { + ProgressPercent::new(percent).unwrap() + } else { + ProgressPercent::default() + }; + let update_flags = self.internal.get_update_flags(); + (progress, update_flags) + } + FirmwareDeviceState::Apply => { + let progress = if let Some(percent) = self.internal.get_fd_apply_progress() { + ProgressPercent::new(percent).unwrap() + } else { + ProgressPercent::default() + }; + let update_flags = self.internal.get_update_flags(); + (progress, update_flags) + } + _ => ( + ProgressPercent::default(), + self.internal.get_update_flags(), + ), + }; + + let (aux_state, aux_state_status) = match self.internal.get_fd_req_state() { + FdReqState::Unused => ( + AuxState::IdleLearnComponentsReadXfer, + AuxStateStatus::AuxStateInProgressOrSuccess as u8, + ), + FdReqState::Sent => ( + AuxState::OperationInProgress, + AuxStateStatus::AuxStateInProgressOrSuccess as u8, + ), + FdReqState::Ready => { + if self.internal.is_fd_req_complete() { + ( + AuxState::OperationSuccessful, + AuxStateStatus::AuxStateInProgressOrSuccess as u8, + ) + } else { + ( + AuxState::OperationInProgress, + AuxStateStatus::AuxStateInProgressOrSuccess as u8, + ) + } + } + FdReqState::Failed => { + let status = self + .internal + .get_fd_req_result() + .unwrap_or(AuxStateStatus::GenericError as u8); + (AuxState::OperationFailed, status) + } + }; + + let resp = GetStatusResponse::new( + req.hdr.instance_id(), + PldmBaseCompletionCode::Success as u8, + cur_state, + prev_state, + aux_state, + aux_state_status, + progress_percent, + self.internal + .get_fd_reason() + .unwrap_or(GetStatusReasonCode::Initialization), + if update_flags.request_force_update() { + UpdateOptionResp::ForceUpdate + } else { + UpdateOptionResp::NoForceUpdate + }, + ); + + match resp.encode(payload) { + Ok(bytes) => Ok(bytes), + Err(_) => { + generate_failure_response(payload, PldmBaseCompletionCode::InvalidLength as u8) + } + } + } + + pub fn set_fd_t1_ts(&self) { + self.internal + .set_fd_t1_update_ts(self.ops.now()); + } + + pub fn should_start_initiator_mode(&self) -> bool { + self.internal.get_fd_state() == FirmwareDeviceState::Download + } + + pub fn should_stop_initiator_mode(&self) -> bool { + !matches!( + self.internal.get_fd_state(), + FirmwareDeviceState::Download + | FirmwareDeviceState::Verify + | FirmwareDeviceState::Apply + ) + } + + pub fn fd_progress(&self, payload: &mut [u8]) -> Result { + let fd_state = self.internal.get_fd_state(); + + let result = match fd_state { + FirmwareDeviceState::Download => self.fd_progress_download(payload), + FirmwareDeviceState::Verify => self.pldm_fd_progress_verify(payload), + FirmwareDeviceState::Apply => self.pldm_fd_progress_apply(payload), + _ => Err(MsgHandlerError::FdInitiatorModeError), + }?; + + // If a response is not received within T1 in FD-driven states, cancel the update and transition to idle state. + if (fd_state == FirmwareDeviceState::Download + || fd_state == FirmwareDeviceState::Verify + || fd_state == FirmwareDeviceState::Apply) + && self.internal.get_fd_req_state() == FdReqState::Sent + && self.ops.now() - self.internal.get_fd_t1_update_ts() + > self.internal.get_fd_t1_timeout() + { + self.ops + .cancel_update_component(&self.internal.get_component()) + .map_err(MsgHandlerError::FdOps)?; + self.internal.fd_idle_timeout(); + return Ok(0); + } + + Ok(result) + } + + pub fn handle_response(&self, payload: &mut [u8]) -> Result<(), MsgHandlerError> { + let rsp_header = + PldmMsgHeader::<[u8; 3]>::decode(payload).map_err(MsgHandlerError::Codec)?; + let (cmd_code, instance_id) = (rsp_header.cmd_code(), rsp_header.instance_id()); + + let fd_req = self.internal.get_fd_req(); + if fd_req.state != FdReqState::Sent + || fd_req.instance_id != Some(instance_id) + || fd_req.command != Some(cmd_code) + { + // Unexpected response + return Err(MsgHandlerError::FdInitiatorModeError); + } + + self.set_fd_t1_ts(); + + match FwUpdateCmd::try_from(cmd_code) { + Ok(FwUpdateCmd::RequestFirmwareData) => self.process_request_fw_data_rsp(payload), + Ok(FwUpdateCmd::TransferComplete) => self.process_transfer_complete_rsp(payload), + Ok(FwUpdateCmd::VerifyComplete) => self.process_verify_complete_rsp(payload), + Ok(FwUpdateCmd::ApplyComplete) => self.process_apply_complete_rsp(payload), + _ => Err(MsgHandlerError::FdInitiatorModeError), + } + } + + fn process_request_fw_data_rsp(&self, payload: &mut [u8]) -> Result<(), MsgHandlerError> { + let fd_state = self.internal.get_fd_state(); + if fd_state != FirmwareDeviceState::Download { + return Err(MsgHandlerError::FdInitiatorModeError); + } + + let fd_req = self.internal.get_fd_req(); + if fd_req.complete { + // Received data after completion + return Err(MsgHandlerError::FdInitiatorModeError); + } + + // Decode the response message fixed + let fw_data_rsp_fixed: RequestFirmwareDataResponseFixed = + RequestFirmwareDataResponseFixed::decode(payload).map_err(MsgHandlerError::Codec)?; + + match fw_data_rsp_fixed.completion_code { + code if code == PldmBaseCompletionCode::Success as u8 => {} + code if code == FwUpdateCompletionCode::RetryRequestFwData as u8 => return Ok(()), + _ => { + self.internal + .set_fd_req( + FdReqState::Ready, + true, + Some(TransferResult::FdAbortedTransfer as u8), + None, + None, + None, + ); + return Ok(()); + } + } + + let (offset, length) = self.internal.get_fd_download_state().unwrap(); + + let fw_data = payload[core::mem::size_of::()..] + .get(..length as usize) + .ok_or(MsgHandlerError::Codec(PldmCodecError::BufferTooShort))?; + + let fw_component = &self.internal.get_component(); + let res = self + .ops + .download_fw_data(offset as usize, fw_data, fw_component) + .map_err(MsgHandlerError::FdOps)?; + + if res == TransferResult::TransferSuccess { + if self.ops.is_download_complete(fw_component) { + // Mark as complete, next progress() call will send the TransferComplete request + self.internal + .set_fd_req( + FdReqState::Ready, + true, + Some(TransferResult::TransferSuccess as u8), + None, + None, + None, + ); + } else { + // Invoke another request if there is more data to download + self.internal + .set_fd_req(FdReqState::Ready, false, None, None, None, None); + } + } else { + // Pass the callback error as the TransferResult + self.internal + .set_fd_req(FdReqState::Ready, true, Some(res as u8), None, None, None); + } + Ok(()) + } + + fn process_transfer_complete_rsp( + &self, + _payload: &mut [u8], + ) -> Result<(), MsgHandlerError> { + let fd_state = self.internal.get_fd_state(); + if fd_state != FirmwareDeviceState::Download { + return Err(MsgHandlerError::FdInitiatorModeError); + } + + let fd_req = self.internal.get_fd_req(); + if fd_req.state != FdReqState::Sent || !fd_req.complete { + return Err(MsgHandlerError::FdInitiatorModeError); + } + + /* Next state depends whether the transfer succeeded */ + if fd_req.result == Some(TransferResult::TransferSuccess as u8) { + // Switch to Verify + self.internal + .set_initiator_mode(InitiatorModeState::Verify(VerifyState::default())); + self.internal + .set_fd_req(FdReqState::Ready, false, None, None, None, None); + self.internal + .set_fd_state(FirmwareDeviceState::Verify); + } else { + // Wait for UA to cancel + self.internal + .set_fd_req(FdReqState::Failed, true, fd_req.result, None, None, None); + } + + Ok(()) + } + + fn process_verify_complete_rsp( + &self, + _payload: &mut [u8], + ) -> Result<(), MsgHandlerError> { + let fd_state = self.internal.get_fd_state(); + if fd_state != FirmwareDeviceState::Verify { + return Err(MsgHandlerError::FdInitiatorModeError); + } + + let fd_req = self.internal.get_fd_req(); + if fd_req.state != FdReqState::Sent || !fd_req.complete { + return Err(MsgHandlerError::FdInitiatorModeError); + } + + /* Next state depends whether the verify succeeded */ + if fd_req.result == Some(VerifyResult::VerifySuccess as u8) { + // Switch to Apply + self.internal + .set_initiator_mode(InitiatorModeState::Apply(ApplyState::default())); + self.internal + .set_fd_req(FdReqState::Ready, false, None, None, None, None); + self.internal.set_fd_state(FirmwareDeviceState::Apply); + } else { + // Wait for UA to cancel + self.internal + .set_fd_req(FdReqState::Failed, true, fd_req.result, None, None, None); + } + + Ok(()) + } + + fn process_apply_complete_rsp(&self, _payload: &mut [u8]) -> Result<(), MsgHandlerError> { + let fd_state = self.internal.get_fd_state(); + if fd_state != FirmwareDeviceState::Apply { + return Err(MsgHandlerError::FdInitiatorModeError); + } + + let fd_req = self.internal.get_fd_req(); + if fd_req.state != FdReqState::Sent || !fd_req.complete { + return Err(MsgHandlerError::FdInitiatorModeError); + } + + if fd_req.result == Some(ApplyResult::ApplySuccess as u8) { + // Switch to Xfer + self.internal + .set_fd_req(FdReqState::Unused, false, None, None, None, None); + self.internal + .set_fd_state(FirmwareDeviceState::ReadyXfer); + } else { + // Wait for UA to cancel + self.internal + .set_fd_req(FdReqState::Failed, true, fd_req.result, None, None, None); + } + + Ok(()) + } + + fn fd_progress_download(&self, payload: &mut [u8]) -> Result { + if !self.should_send_fd_request() { + return Err(MsgHandlerError::FdInitiatorModeError); + } + + let instance_id = self.internal.alloc_next_instance_id().unwrap(); + // If the request is complete, send TransferComplete + if self.internal.is_fd_req_complete() { + let result = self + .internal + .get_fd_req_result() + .ok_or(MsgHandlerError::FdInitiatorModeError)?; + + let msg_len = TransferCompleteRequest::new( + instance_id, + PldmMsgType::Request, + TransferResult::try_from(result).unwrap(), + ) + .encode(payload) + .map_err(MsgHandlerError::Codec)?; + + // Set fd req state to sent + let req_sent_timestamp = self.ops.now(); + self.internal + .set_fd_req( + FdReqState::Sent, + true, + Some(result), + Some(instance_id), + Some(FwUpdateCmd::TransferComplete as u8), + Some(req_sent_timestamp), + ); + + Ok(msg_len) + } else { + let (requested_offset, requested_length) = self + .ops + .query_download_offset_and_length(&self.internal.get_component()) + .map_err(MsgHandlerError::FdOps)?; + + if let Some((chunk_offset, chunk_length)) = self + .internal + .get_fd_download_chunk(requested_offset as u32, requested_length as u32) + { + let msg_len = RequestFirmwareDataRequest::new( + instance_id, + PldmMsgType::Request, + chunk_offset, + chunk_length, + ) + .encode(payload) + .map_err(MsgHandlerError::Codec)?; + + // Store offset and length into the internal state + self.internal + .set_fd_download_state(chunk_offset, chunk_length); + + // Set fd req state to sent + let req_sent_timestamp = self.ops.now(); + self.internal + .set_fd_req( + FdReqState::Sent, + false, + None, + Some(instance_id), + Some(FwUpdateCmd::RequestFirmwareData as u8), + Some(req_sent_timestamp), + ); + Ok(msg_len) + } else { + Err(MsgHandlerError::FdInitiatorModeError) + } + } + } + + fn pldm_fd_progress_verify(&self, _payload: &mut [u8]) -> Result { + if !self.should_send_fd_request() { + return Err(MsgHandlerError::FdInitiatorModeError); + } + + let mut res = VerifyResult::default(); + if !self.internal.is_fd_req_complete() { + let mut progress_percent = ProgressPercent::default(); + res = self + .ops + .verify(&self.internal.get_component(), &mut progress_percent) + .map_err(MsgHandlerError::FdOps)?; + + // Set the progress percent to VerifyState + self.internal + .set_fd_verify_progress(progress_percent.value()); + + if res == VerifyResult::VerifySuccess && progress_percent.value() < 100 { + // doing nothing and wait for the next call + return Ok(0); + } + } + + let instance_id = self.internal.alloc_next_instance_id().unwrap(); + let verify_complete_req = + VerifyCompleteRequest::new(instance_id, PldmMsgType::Request, res); + + // Encode the request message + let msg_len = verify_complete_req + .encode(_payload) + .map_err(MsgHandlerError::Codec)?; + + self.internal + .set_fd_req( + FdReqState::Sent, + true, + Some(res as u8), + Some(instance_id), + Some(FwUpdateCmd::VerifyComplete as u8), + Some(self.ops.now()), + ); + + Ok(msg_len) + } + + fn pldm_fd_progress_apply(&self, _payload: &mut [u8]) -> Result { + if !self.should_send_fd_request() { + return Err(MsgHandlerError::FdInitiatorModeError); + } + + let mut res = ApplyResult::default(); + if !self.internal.is_fd_req_complete() { + let mut progress_percent = ProgressPercent::default(); + res = self + .ops + .apply(&self.internal.get_component(), &mut progress_percent) + .map_err(MsgHandlerError::FdOps)?; + + // Set the progress percent to ApplyState + self.internal + .set_fd_apply_progress(progress_percent.value()); + + if res == ApplyResult::ApplySuccess && progress_percent.value() < 100 { + // doing nothing and wait for the next call + return Ok(0); + } + } + + // Allocate the next instance ID + let instance_id = self.internal.alloc_next_instance_id().unwrap(); + let apply_complete_req = ApplyCompleteRequest::new( + instance_id, + PldmMsgType::Request, + res, + ComponentActivationMethods(0), + ); + // Encode the request message + let msg_len = apply_complete_req + .encode(_payload) + .map_err(MsgHandlerError::Codec)?; + + self.internal + .set_fd_req( + FdReqState::Sent, + true, + Some(res as u8), + Some(instance_id), + Some(FwUpdateCmd::ApplyComplete as u8), + Some(self.ops.now()), + ); + + Ok(msg_len) + } + + fn should_send_fd_request(&self) -> bool { + let now = self.ops.now(); + + let fd_req_state = self.internal.get_fd_req_state(); + match fd_req_state { + FdReqState::Unused => false, + FdReqState::Ready => true, + FdReqState::Failed => false, + FdReqState::Sent => { + let fd_req_sent_time = self.internal.get_fd_sent_time().unwrap(); + if now < fd_req_sent_time { + // Time went backwards + return false; + } + + // Send if retry time has elapsed + return (now - fd_req_sent_time) >= self.internal.get_fd_t2_retry_time(); + } + } + } +} diff --git a/pldm-service/src/firmware_device/fd_internal.rs b/pldm-service/src/firmware_device/fd_internal.rs new file mode 100644 index 0000000..d4d1325 --- /dev/null +++ b/pldm-service/src/firmware_device/fd_internal.rs @@ -0,0 +1,194 @@ +// Licensed under the Apache-2.0 license + +use crate::control_context::Tid; +use pldm_common::message::firmware_update::get_status::GetStatusReasonCode; +use pldm_common::protocol::firmware_update::{ + FirmwareDeviceState, PldmFdTime, UpdateOptionFlags, PLDM_FWUP_MAX_PADDING_SIZE, +}; +use pldm_common::util::fw_component::FirmwareComponent; + +pub struct FdInternal { + req: FdReq +} + +impl FdInternal { + pub fn new() -> Self { + Self { + req: FdReq::default() + } + } + // Request details used for download/verify/apply operations. + pub fn is_update_mode(&self) -> bool { + false + } + + pub fn set_fd_state(&self, state: FirmwareDeviceState) { } + + pub fn set_fd_idle(&self, reason_code: GetStatusReasonCode) { } + + pub fn fd_idle_timeout(&self) { } + + pub fn get_fd_reason(&self) -> Option { + Some(GetStatusReasonCode::CancelUpdate) + } + + pub fn get_fd_state(&self) -> FirmwareDeviceState { + FirmwareDeviceState::Idle + } + + pub fn get_fd_prev_state(&self) -> FirmwareDeviceState { + FirmwareDeviceState::Idle + } + + pub fn set_xfer_size(&self, transfer_size: usize) { } + + pub fn get_xfer_size(&self) -> usize { 0 } + + pub fn set_component(&self, comp: &FirmwareComponent) { } + + pub fn get_component(&self) -> FirmwareComponent { FirmwareComponent::default() } + + pub fn set_update_flags(&self, flags: UpdateOptionFlags) { } + + pub fn get_update_flags(&self) -> UpdateOptionFlags { UpdateOptionFlags((0)) } + + pub fn set_fd_req( + &self, + req_state: FdReqState, + complete: bool, + result: Option, + instance_id: Option, + command: Option, + sent_time: Option, + ) { } + + + pub fn alloc_next_instance_id(&self) -> Option { Some(0xff) } + + pub fn get_fd_req(&self) -> FdReq { self.req.clone() } + + pub fn get_fd_req_state(&self) -> FdReqState { FdReqState::Failed } + + pub fn set_fd_req_state(&self, state: FdReqState) { } + + pub fn get_fd_sent_time(&self) -> Option { Some(0xbaddbadd) } + + pub fn is_fd_req_complete(&self) -> bool { false } + + pub fn get_fd_req_result(&self) -> Option { Some(0xff) } + + pub fn get_fd_download_chunk( + &self, + requested_offset: u32, + requested_length: u32, + ) -> Option<(u32, u32)> { + Some((0xFFFFFFFF, 0xFFFFFFFF)) + } + + pub fn get_fd_download_state(&self) -> Option<(u32, u32)> { + Some((0xFFFFFFFF, 0xFFFFFFFF)) + } + + pub fn set_fd_download_state(&self, offset: u32, length: u32) { } + + pub fn set_initiator_mode(&self, mode: InitiatorModeState) { } + + pub fn set_fd_verify_progress(&self, progress: u8) { } + + pub fn set_fd_apply_progress(&self, progress: u8) { } + + pub fn get_fd_verify_progress(&self) -> Option { + Some(0xff) + } + + pub fn get_fd_apply_progress(&self) -> Option { + Some(0xff) + } + + pub fn set_fd_t1_update_ts(&self, timestamp: PldmFdTime) { } + + pub fn get_fd_t1_update_ts(&self) -> PldmFdTime { 0xbaddbadd } + + pub fn set_fd_t1_timeout(&self, timeout: PldmFdTime) { } + + pub fn get_fd_t1_timeout(&self) -> PldmFdTime { 0xbaddbadd } + + pub fn set_fd_t2_retry_time(&self, retry_time: PldmFdTime) { } + + pub fn get_fd_t2_retry_time(&self) -> PldmFdTime { 0xbaddbadd } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FdReqState { + // The `pldm_fd_req` instance is unused. + Unused, + // Ready to send a request. + Ready, + // Waiting for a response. + Sent, + // Completed and failed; will not send more requests. + Failed, +} + +#[derive(Debug, Clone)] +pub struct FdReq { + // The current state of the request. + pub state: FdReqState, + + // Indicates if the request is complete and ready to transition to the next state. + // This is relevant for TransferComplete, VerifyComplete, and ApplyComplete requests. + pub complete: bool, + + pub result: Option, + + // The instance ID of the request, only valid in the `SENT` state. + pub instance_id: Option, + + // The command associated with the request, only valid in the `SENT` state. + pub command: Option, + + // The time when the request was sent, only valid in the `SENT` state. + pub sent_time: Option, +} + +impl Default for FdReq { + fn default() -> Self { + Self::new() + } +} + +impl FdReq { + fn new() -> Self { + Self { + state: FdReqState::Unused, + complete: false, + result: None, + instance_id: None, + command: None, + sent_time: None, + } + } +} + +#[derive(Debug)] +pub enum InitiatorModeState { + Download(DownloadState), + Verify(VerifyState), + Apply(ApplyState), +} + +#[derive(Debug, Default)] +pub struct DownloadState { + offset: u32, + length: u32, +} + +#[derive(Debug, Default)] +pub struct VerifyState { + progress_percent: u8, +} + +#[derive(Debug, Default)] +pub struct ApplyState { + progress_percent: u8, +} diff --git a/pldm-service/src/firmware_device/fd_ops.rs b/pldm-service/src/firmware_device/fd_ops.rs new file mode 100644 index 0000000..2a64cc9 --- /dev/null +++ b/pldm-service/src/firmware_device/fd_ops.rs @@ -0,0 +1,272 @@ +// Licensed under the Apache-2.0 license + +extern crate alloc; +use alloc::boxed::Box; +use pldm_common::message::firmware_update::apply_complete::ApplyResult; +use pldm_common::message::firmware_update::get_status::ProgressPercent; +use pldm_common::message::firmware_update::request_cancel::{ + NonFunctioningComponentBitmap, NonFunctioningComponentIndication, +}; +use pldm_common::message::firmware_update::transfer_complete::TransferResult; +use pldm_common::message::firmware_update::verify_complete::VerifyResult; +use pldm_common::util::fw_component::FirmwareComponent; +use pldm_common::{ + message::firmware_update::get_fw_params::FirmwareParameters, + protocol::firmware_update::{ComponentResponseCode, Descriptor, PldmFdTime}, +}; + +#[derive(Debug)] +pub enum FdOpsError { + DeviceIdentifiersError, + FirmwareParametersError, + TransferSizeError, + ComponentError, + FwDownloadError, + VerifyError, + ApplyError, + ActivateError, + CancelUpdateError, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ComponentOperation { + PassComponent, + UpdateComponent, +} + +pub struct FdOps { } + +/// Trait for firmware device-specific operations. +/// +/// This trait defines asynchronous methods for performing various firmware device operations, +/// including retrieving device identifiers, firmware parameters, and transfer sizes. It also +/// provides methods for handling firmware components, managing firmware data downloads, verifying +/// and applying firmware, activating new firmware, and obtaining the current timestamp. +impl FdOps { + + pub fn new() -> Self { + Self { } + } + + /// Asynchronously retrieves device identifiers. + /// + /// # Arguments + /// + /// * `device_identifiers` - A mutable slice of `Descriptor` to store the retrieved device identifiers. + /// + /// # Returns + /// + /// * `Result` - On success, returns the number of device identifiers retrieved. + /// On failure, returns an `FdOpsError`. + pub fn get_device_identifiers( + &self, + device_identifiers: &mut [Descriptor], + ) -> Result { Ok(0xff) } + + /// Asynchronously retrieves firmware parameters. + /// + /// # Arguments + /// + /// * `firmware_params` - A mutable reference to `FirmwareParameters` to store the retrieved firmware parameters. + /// + /// # Returns + /// + /// * `Result<(), FdOpsError>` - On success, returns `Ok(())`. On failure, returns an `FdOpsError`. + pub fn get_firmware_parms( + &self, + firmware_params: &mut FirmwareParameters, + ) -> Result<(), FdOpsError> { Result::Ok(()) } + + /// Retrieves the transfer size for the firmware update operation. + /// + /// # Arguments + /// + /// * `ua_transfer_size` - The requested transfer size in bytes. + /// + /// # Returns + /// + /// * `Result` - On success, returns the transfer size in bytes. + /// On failure, returns an `FdOpsError`. + pub fn get_xfer_size(&self, ua_transfer_size: usize) -> Result { Ok(0xff) } + + /// Handles firmware component operations such as passing or updating components. + /// + /// # Arguments + /// + /// * `component` - A reference to the `FirmwareComponent` to be processed. + /// * `fw_params` - A reference to the `FirmwareParameters` associated with the operation. + /// * `op` - The `ComponentOperation` to be performed (e.g., pass or update). + /// + /// # Returns + /// + /// * `Result` - On success, returns a `ComponentResponseCode`. + /// On failure, returns an `FdOpsError`. + pub fn handle_component( + &self, + component: &FirmwareComponent, + fw_params: &FirmwareParameters, + op: ComponentOperation, + ) -> Result { Ok(ComponentResponseCode::CompNotSupported) } + + /// Queries the download offset and length for a given firmware component. + /// + /// # Arguments + /// + /// * `component` - A reference to the `FirmwareComponent` for which the download offset and length are queried. + /// + /// # Returns + /// + /// * `Result<(usize, usize), FdOpsError>` - On success, returns a tuple containing the offset and length in bytes. + /// On failure, returns an `FdOpsError`. + pub fn query_download_offset_and_length( + &self, + component: &FirmwareComponent, + ) -> Result<(usize, usize), FdOpsError> { Ok((0xff, 0xff)) } + + /// Handles firmware data downloading operations. + /// + /// # Arguments + /// + /// * `offset` - The offset in bytes where the firmware data should be written or processed. + /// * `data` - A slice of bytes representing the firmware data to be handled. + /// * `component` - A reference to the `FirmwareComponent` associated with the firmware data. + /// + /// # Returns + /// + /// * `Result` - On success, returns a `TransferResult` indicating the outcome of the operation. + /// On failure, returns an `FdOpsError`. + pub fn download_fw_data( + &self, + offset: usize, + data: &[u8], + component: &FirmwareComponent, + ) -> Result { Ok(TransferResult::TransferTimeOut) } + + /// Checks if the firmware download for a given component is complete. + /// + /// # Arguments + /// + /// * `component` - A reference to the `FirmwareComponent` for which the download completion status is checked. + /// + /// # Returns + /// + /// * `bool` - Returns `true` if the download is complete, otherwise `false`. + pub fn is_download_complete(&self, component: &FirmwareComponent) -> bool { false } + + /// Queries the download progress for a given firmware component. + /// + /// # Arguments + /// + /// * `component` - A reference to the `FirmwareComponent` for which the download progress is queried. + /// * `progress_percent` - A mutable reference to `ProgressPercent` to track the download progress. + /// + /// # Returns + /// + /// * `Result<(), FdOpsError>` - On success, returns `Ok(())`. On failure, returns an `FdOpsError`. + pub fn query_download_progress( + &self, + component: &FirmwareComponent, + progress_percent: &mut ProgressPercent, + ) -> Result<(), FdOpsError> { Ok(()) } + + /// Verifies the firmware component. + /// + /// # Arguments + /// + /// * `component` - A reference to the `FirmwareComponent` to be verified. + /// * `progress_percent` - A mutable reference to `ProgressPercent` to track the verification progress. + /// + /// # Returns + /// + /// * `Result` - On success, returns a `VerifyResult` indicating the outcome of the verification. + /// * On failure, returns an `FdOpsError`. + pub fn verify( + &self, + component: &FirmwareComponent, + progress_percent: &mut ProgressPercent, + ) -> Result { Ok(VerifyResult::VerifyGenericError) } + + /// Applies the firmware component. + /// + /// # Arguments + /// + /// * `component` - A reference to the `FirmwareComponent` to be applied. + /// * `progress_percent` - A mutable reference to `ProgressPercent` to track the application progress. + /// + /// # Returns + /// + /// * `Result` - On success, returns an `ApplyResult` indicating the outcome of the application. + /// * On failure, returns an `FdOpsError`. + pub fn apply( + &self, + component: &FirmwareComponent, + progress_percent: &mut ProgressPercent, + ) -> Result { Ok(ApplyResult::ApplyGenericError) } + + /// Activates new firmware. + /// + /// # Arguments + /// + /// * `self_contained_activation` - Indicates if self-contained activation is requested. + /// * `estimated_time` - A mutable reference to store the estimated time (in seconds) + /// required to perform self-activation. This may be left as `None` if not needed. + /// + /// # Returns + /// + /// * `Result` - On success, returns a PLDM completion code. + /// On failure, returns an `FdOpsError`. + /// + /// The device implementation is responsible for verifying that the expected components + /// have been updated. If not, it should return `PLDM_FWUP_INCOMPLETE_UPDATE`. + pub fn activate( + &self, + self_contained_activation: u8, + estimated_time: &mut u16, + ) -> Result { Ok(0xff) } + + /// Cancels the update operation for a specific firmware component. + /// + /// # Arguments + /// + /// * `component` - A reference to the `FirmwareComponent` for which the update operation should be canceled. + /// + /// # Returns + /// + /// * `Result<(), FdOpsError>` - On success, returns `Ok(())`. On failure, returns an `FdOpsError`. + pub fn cancel_update_component( + &self, + component: &FirmwareComponent, + ) -> Result<(), FdOpsError> { Ok(())} + + /// Indicates which components will be in a non-functioning state upon exiting update mode + /// due to cancel update request from UA. + /// + /// # Returns + /// + /// * `Result<(NonFunctioningComponentIndication, NonFunctioningComponentBitmap), FdOpsError>` - + /// On success, returns a tuple containing: + /// - `NonFunctioningComponentIndication`: Indicates whether components are functioning or not. + /// - `NonFunctioningComponentBitmap`: A bitmap representing non-functioning components. + /// On failure, returns an `FdOpsError`. + pub fn get_non_functional_component_info( + &self, + ) -> Result< + ( + NonFunctioningComponentIndication, + NonFunctioningComponentBitmap, + ), + FdOpsError, + > { + Ok(( + NonFunctioningComponentIndication::ComponentsFunctioning, + NonFunctioningComponentBitmap::new(0), + )) + } + + /// Retrieves the current timestamp in milliseconds. + /// + /// # Returns + /// + /// * `PldmFdTime` - The current timestamp in milliseconds. + pub fn now(&self) -> PldmFdTime { 0xbaddbadd } +} diff --git a/pldm-service/src/firmware_device/mod.rs b/pldm-service/src/firmware_device/mod.rs new file mode 100644 index 0000000..f1eb8b4 --- /dev/null +++ b/pldm-service/src/firmware_device/mod.rs @@ -0,0 +1,5 @@ +// Licensed under the Apache-2.0 license + +pub mod fd_context; +pub mod fd_internal; +pub mod fd_ops; diff --git a/pldm-service/src/lib.rs b/pldm-service/src/lib.rs new file mode 100644 index 0000000..9793a31 --- /dev/null +++ b/pldm-service/src/lib.rs @@ -0,0 +1,9 @@ +// Licensed under the Apache-2.0 license + +pub mod cmd_interface; +pub mod config; +pub mod control_context; +pub mod error; +pub mod firmware_device; +//pub mod timer; +pub mod transport; diff --git a/pldm-service/src/timer.rs b/pldm-service/src/timer.rs new file mode 100644 index 0000000..b6f1ad4 --- /dev/null +++ b/pldm-service/src/timer.rs @@ -0,0 +1,83 @@ +// Licensed under the Apache-2.0 license + +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::mutex::Mutex; +use libsyscall_caliptra::DefaultSyscalls; +use libtock_alarm::{Convert, Hz, Milliseconds}; +use libtock_platform::{self as platform}; +use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; +use libtockasync::TockSubscribe; + +pub struct AsyncAlarm( + S, + C, +); + +static ALARM_MUTEX: Mutex = Mutex::new(()); + +impl AsyncAlarm { + /// Run a check against the console capsule to ensure it is present. + #[inline(always)] + #[allow(dead_code)] + pub fn exists() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, command::EXISTS, 0, 0).to_result() + } + + pub fn get_frequency() -> Result { + S::command(DRIVER_NUM, command::FREQUENCY, 0, 0) + .to_result() + .map(Hz) + } + + #[allow(dead_code)] + pub fn get_ticks() -> Result { + S::command(DRIVER_NUM, command::TIME, 0, 0).to_result() + } + + pub fn get_milliseconds() -> Result { + let ticks = Self::get_ticks()? as u64; + let freq = (Self::get_frequency()?).0 as u64; + + Ok(ticks.saturating_div(freq / 1000)) + } + + pub async fn sleep_for(time: T) -> Result<(), ErrorCode> { + let freq = Self::get_frequency()?; + let ticks = time.to_ticks(freq).0; + let sub = TockSubscribe::subscribe::(DRIVER_NUM, 0); + S::command(DRIVER_NUM, command::SET_RELATIVE, ticks, 0) + .to_result() + .map(|_when: u32| ())?; + sub.await.map(|_| ()) + } + + pub async fn sleep(time: Milliseconds) { + // bad things happen if multiple tasks try to use the alarm at once + let guard = ALARM_MUTEX.lock().await; + let _ = AsyncAlarm::::sleep_for(time).await; + drop(guard); + } +} + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0; + +// Command IDs +#[allow(unused)] +mod command { + pub const EXISTS: u32 = 0; + pub const FREQUENCY: u32 = 1; + pub const TIME: u32 = 2; + pub const STOP: u32 = 3; + + pub const SET_RELATIVE: u32 = 5; + pub const SET_ABSOLUTE: u32 = 6; +} + +#[allow(unused)] +mod subscribe { + pub const CALLBACK: u32 = 0; +} diff --git a/pldm-service/src/transport.rs b/pldm-service/src/transport.rs new file mode 100644 index 0000000..736fe19 --- /dev/null +++ b/pldm-service/src/transport.rs @@ -0,0 +1,31 @@ +// Licensed under the Apache-2.0 license +use pldm_common::util::mctp_transport::{ + MctpCommonHeader, MCTP_COMMON_HEADER_OFFSET, MCTP_PLDM_MSG_TYPE, +}; + +pub enum PldmTransportType { + Mctp, +} + +#[derive(Debug)] +pub enum TransportError { + DriverError, + BufferTooSmall, + UnexpectedMessageType, + ReceiveError, + SendError, + ResponseNotExpected, + NoRequestInFlight, +} + +pub trait MctpTransport { + //fn new(drv_num: u32) -> Self; + + fn send_request(&mut self, dest_eid: u8, req: &[u8]) -> Result<(), TransportError>; + + fn receive_response(&mut self, rsp: &mut [u8]) -> Result<(), TransportError>; + + fn receive_request(&mut self, req: &mut [u8]) -> Result<(), TransportError>; + + fn send_response(&mut self, resp: &[u8]) -> Result<(), TransportError>; +} From 066416e71fe01f817e40ebbcf73def6047cdf74f Mon Sep 17 00:00:00 2001 From: CourtneyDrant Date: Wed, 8 Oct 2025 15:08:57 -0700 Subject: [PATCH 3/5] Reconstructed package and contents --- Cargo.lock | 29 +- Cargo.toml | 20 +- pldm-service/src/error.rs | 18 - pldm-service/src/lib.rs | 9 - {pldm-service => src}/Cargo.toml | 0 {pldm-service/src => src}/cmd_interface.rs | 0 src/codec.rs | 56 -- {pldm-service/src => src}/config.rs | 0 {pldm-service/src => src}/control_context.rs | 0 src/error.rs | 46 +- .../src => src}/firmware_device/fd_context.rs | 0 .../firmware_device/fd_internal.rs | 0 .../src => src}/firmware_device/fd_ops.rs | 0 .../src => src}/firmware_device/mod.rs | 0 src/lib.rs | 12 +- src/message/control.rs | 388 -------- src/message/firmware_update/activate_fw.rs | 98 -- src/message/firmware_update/apply_complete.rs | 117 --- src/message/firmware_update/get_fw_params.rs | 362 ------- src/message/firmware_update/get_status.rs | 225 ----- src/message/firmware_update/mod.rs | 14 - src/message/firmware_update/pass_component.rs | 189 ---- src/message/firmware_update/query_devid.rs | 250 ----- src/message/firmware_update/request_cancel.rs | 157 ---- .../firmware_update/request_fw_data.rs | 129 --- src/message/firmware_update/request_update.rs | 268 ------ .../firmware_update/transfer_complete.rs | 124 --- .../firmware_update/update_component.rs | 338 ------- .../firmware_update/verify_complete.rs | 111 --- src/message/mod.rs | 4 - src/protocol/base.rs | 302 ------ src/protocol/firmware_update.rs | 884 ------------------ src/protocol/mod.rs | 5 - src/protocol/version.rs | 250 ----- {pldm-service/src => src}/timer.rs | 0 {pldm-service/src => src}/transport.rs | 0 src/util/fw_component.rs | 200 ---- src/util/mctp_transport.rs | 110 --- src/util/mod.rs | 4 - 39 files changed, 55 insertions(+), 4664 deletions(-) delete mode 100644 pldm-service/src/error.rs delete mode 100644 pldm-service/src/lib.rs rename {pldm-service => src}/Cargo.toml (100%) rename {pldm-service/src => src}/cmd_interface.rs (100%) delete mode 100644 src/codec.rs rename {pldm-service/src => src}/config.rs (100%) rename {pldm-service/src => src}/control_context.rs (100%) rename {pldm-service/src => src}/firmware_device/fd_context.rs (100%) rename {pldm-service/src => src}/firmware_device/fd_internal.rs (100%) rename {pldm-service/src => src}/firmware_device/fd_ops.rs (100%) rename {pldm-service/src => src}/firmware_device/mod.rs (100%) delete mode 100644 src/message/control.rs delete mode 100644 src/message/firmware_update/activate_fw.rs delete mode 100644 src/message/firmware_update/apply_complete.rs delete mode 100644 src/message/firmware_update/get_fw_params.rs delete mode 100644 src/message/firmware_update/get_status.rs delete mode 100644 src/message/firmware_update/mod.rs delete mode 100644 src/message/firmware_update/pass_component.rs delete mode 100644 src/message/firmware_update/query_devid.rs delete mode 100644 src/message/firmware_update/request_cancel.rs delete mode 100644 src/message/firmware_update/request_fw_data.rs delete mode 100644 src/message/firmware_update/request_update.rs delete mode 100644 src/message/firmware_update/transfer_complete.rs delete mode 100644 src/message/firmware_update/update_component.rs delete mode 100644 src/message/firmware_update/verify_complete.rs delete mode 100644 src/message/mod.rs delete mode 100644 src/protocol/base.rs delete mode 100644 src/protocol/firmware_update.rs delete mode 100644 src/protocol/mod.rs delete mode 100644 src/protocol/version.rs rename {pldm-service/src => src}/timer.rs (100%) rename {pldm-service/src => src}/transport.rs (100%) delete mode 100644 src/util/fw_component.rs delete mode 100644 src/util/mctp_transport.rs delete mode 100644 src/util/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 75e58c4..e819989 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,18 +9,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" [[package]] -name = "pldm-lib" +name = "pldm-common" version = "0.1.0" dependencies = [ "bitfield", "zerocopy", ] +[[package]] +name = "pldm-lib" +version = "0.1.0" +dependencies = [ + "pldm-common", +] + [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -36,9 +43,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -47,24 +54,24 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index d798a4b..afc0e6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,22 @@ name = "pldm-lib" version = "0.1.0" authors = ["Caliptra contributors", "OpenPRoT contributors"] -edition = "2024" +edition = "2021" -[dependencies] -zerocopy = {version = "0.8.17", features = ["derive"]} +[workspace] +members = [ + "pldm-common" +] + +[workspace.package] +version = "0.1.0" +authors = ["Caliptra contributors", "OpenPRoT contributors"] +edition = "2021" + +[workspace.dependencies] +pldm-common = { path = "pldm-common" } bitfield = "0.14.0" +zerocopy = { version = "0.8.17", features = ["derive"] } + +[dependencies] +pldm-common.workspace = true diff --git a/pldm-service/src/error.rs b/pldm-service/src/error.rs deleted file mode 100644 index 8f42a18..0000000 --- a/pldm-service/src/error.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::firmware_device::fd_ops::FdOpsError; -use crate::transport::TransportError; -use pldm_common::codec::PldmCodecError; -use pldm_common::error::{PldmError, UtilError}; - -/// Handle non-protocol specific error conditions. -#[derive(Debug)] -pub enum MsgHandlerError { - Codec(PldmCodecError), - Transport(TransportError), - PldmCommon(PldmError), - Util(UtilError), - FdOps(FdOpsError), - FdInitiatorModeError, - NotReady, -} diff --git a/pldm-service/src/lib.rs b/pldm-service/src/lib.rs deleted file mode 100644 index 9793a31..0000000 --- a/pldm-service/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Licensed under the Apache-2.0 license - -pub mod cmd_interface; -pub mod config; -pub mod control_context; -pub mod error; -pub mod firmware_device; -//pub mod timer; -pub mod transport; diff --git a/pldm-service/Cargo.toml b/src/Cargo.toml similarity index 100% rename from pldm-service/Cargo.toml rename to src/Cargo.toml diff --git a/pldm-service/src/cmd_interface.rs b/src/cmd_interface.rs similarity index 100% rename from pldm-service/src/cmd_interface.rs rename to src/cmd_interface.rs diff --git a/src/codec.rs b/src/codec.rs deleted file mode 100644 index 15df19f..0000000 --- a/src/codec.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Licensed under the Apache-2.0 license - -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -#[derive(Debug, PartialEq)] -pub enum PldmCodecError { - BufferTooShort, - Unsupported, -} - -/// A trait for encoding and decoding PLDM (Platform Level Data Model) messages. -/// -/// This trait provides methods for encoding a PLDM message into a byte buffer -/// and decoding a PLDM message from a byte buffer. Implementers of this trait -/// must also implement the `Debug` trait and be `Sized`. -pub trait PldmCodec: 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. - /// - /// # Returns - /// - /// A `Result` containing the decoded message on success, or a `PldmCodecError` on failure. - fn decode(buffer: &[u8]) -> Result; -} - -// Default implementation of PldmCodec for types that can leverage zerocopy. -impl PldmCodec for T -where - T: core::fmt::Debug + Sized + FromBytes + IntoBytes + Immutable, -{ - fn encode(&self, buffer: &mut [u8]) -> Result { - self.write_to_prefix(buffer) - .map_err(|_| PldmCodecError::BufferTooShort) - .map(|_| core::mem::size_of::()) - } - - fn decode(buffer: &[u8]) -> Result { - Ok(Self::read_from_prefix(buffer) - .map_err(|_| PldmCodecError::BufferTooShort)? - .0) - } -} diff --git a/pldm-service/src/config.rs b/src/config.rs similarity index 100% rename from pldm-service/src/config.rs rename to src/config.rs diff --git a/pldm-service/src/control_context.rs b/src/control_context.rs similarity index 100% rename from pldm-service/src/control_context.rs rename to src/control_context.rs diff --git a/src/error.rs b/src/error.rs index 17dd6a2..8f42a18 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,36 +1,18 @@ // Licensed under the Apache-2.0 license -#[derive(Debug, Clone, PartialEq)] -pub enum PldmError { - InvalidData, - InvalidLength, - InvalidMsgType, - InvalidProtocolVersion, - UnsupportedCmd, - UnsupportedPldmType, - InvalidCompletionCode, - InvalidTransferOpFlag, - InvalidTransferRespFlag, +use crate::firmware_device::fd_ops::FdOpsError; +use crate::transport::TransportError; +use pldm_common::codec::PldmCodecError; +use pldm_common::error::{PldmError, UtilError}; - InvalidVersionStringType, - InvalidVersionStringLength, - InvalidFdState, - InvalidDescriptorType, - InvalidDescriptorLength, - InvalidDescriptorCount, - InvalidComponentClassification, - InvalidComponentResponseCode, - InvalidComponentCompatibilityResponse, - InvalidComponentCompatibilityResponseCode, - InvalidTransferResult, - InvalidVerifyResult, - InvalidApplyResult, - InvalidGetStatusReasonCode, - InvalidAuxStateStatus, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum UtilError { - InvalidMctpPayloadLength, - InvalidMctpMsgType, +/// Handle non-protocol specific error conditions. +#[derive(Debug)] +pub enum MsgHandlerError { + Codec(PldmCodecError), + Transport(TransportError), + PldmCommon(PldmError), + Util(UtilError), + FdOps(FdOpsError), + FdInitiatorModeError, + NotReady, } diff --git a/pldm-service/src/firmware_device/fd_context.rs b/src/firmware_device/fd_context.rs similarity index 100% rename from pldm-service/src/firmware_device/fd_context.rs rename to src/firmware_device/fd_context.rs diff --git a/pldm-service/src/firmware_device/fd_internal.rs b/src/firmware_device/fd_internal.rs similarity index 100% rename from pldm-service/src/firmware_device/fd_internal.rs rename to src/firmware_device/fd_internal.rs diff --git a/pldm-service/src/firmware_device/fd_ops.rs b/src/firmware_device/fd_ops.rs similarity index 100% rename from pldm-service/src/firmware_device/fd_ops.rs rename to src/firmware_device/fd_ops.rs diff --git a/pldm-service/src/firmware_device/mod.rs b/src/firmware_device/mod.rs similarity index 100% rename from pldm-service/src/firmware_device/mod.rs rename to src/firmware_device/mod.rs diff --git a/src/lib.rs b/src/lib.rs index 660c84c..9793a31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,9 @@ // Licensed under the Apache-2.0 license -#![cfg_attr(target_arch = "riscv32", no_std)] - -pub mod codec; +pub mod cmd_interface; +pub mod config; +pub mod control_context; pub mod error; -pub mod message; -pub mod protocol; -pub mod util; +pub mod firmware_device; +//pub mod timer; +pub mod transport; diff --git a/src/message/control.rs b/src/message/control.rs deleted file mode 100644 index e836b08..0000000 --- a/src/message/control.rs +++ /dev/null @@ -1,388 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::error::PldmError; -use crate::protocol::base::{ - InstanceId, PldmControlCmd, PldmMsgHeader, PldmMsgType, PldmSupportedType, - TransferOperationFlag, TransferRespFlag, PLDM_MSG_HEADER_LEN, -}; -use crate::protocol::version::{PldmVersion, ProtocolVersionStr, Ver32}; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -pub const PLDM_CMDS_BITMAP_LEN: usize = 32; -pub const PLDM_TYPES_BITMAP_LEN: usize = 8; - -#[repr(C, packed)] -#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] -pub struct GetTidRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, -} - -impl GetTidRequest { - pub fn new(instance_id: InstanceId, message_type: PldmMsgType) -> Self { - Self { - hdr: PldmMsgHeader::new( - instance_id, - message_type, - PldmSupportedType::Base, - PldmControlCmd::GetTid as u8, - ), - } - } -} - -#[repr(C, packed)] -#[derive(Debug, PartialEq, FromBytes, IntoBytes, Immutable, Clone)] -pub struct GetTidResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, - pub tid: u8, -} - -impl GetTidResponse { - pub fn new(instance_id: InstanceId, tid: u8, completion_code: u8) -> Self { - Self { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::Base, - PldmControlCmd::GetTid as u8, - ), - completion_code, - tid, - } - } -} - -#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] -#[repr(C, packed)] -pub struct SetTidRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub tid: u8, -} -impl SetTidRequest { - pub fn new(instance_id: InstanceId, message_type: PldmMsgType, tid: u8) -> Self { - Self { - hdr: PldmMsgHeader::new( - instance_id, - message_type, - PldmSupportedType::Base, - PldmControlCmd::SetTid as u8, - ), - tid, - } - } -} - -#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] -#[repr(C, packed)] -pub struct SetTidResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, -} - -impl SetTidResponse { - pub fn new(instance_id: InstanceId, ompletion_code: u8) -> Self { - SetTidResponse { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::Base, - PldmControlCmd::SetTid as u8, - ), - completion_code: ompletion_code, - } - } -} - -#[repr(C, packed)] -#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] -pub struct GetPldmCommandsRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub pldm_type: u8, - pub protocol_version: Ver32, -} - -impl GetPldmCommandsRequest { - pub fn new( - instance_id: InstanceId, - message_type: PldmMsgType, - pldm_type: u8, - version_str: ProtocolVersionStr, - ) -> Self { - Self { - hdr: PldmMsgHeader::new( - instance_id, - message_type, - PldmSupportedType::Base, - PldmControlCmd::GetPldmCommands as u8, - ), - pldm_type, - protocol_version: PldmVersion::try_from(version_str) - .unwrap() - .bcd_encode_to_ver32(), - } - } -} - -#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] -#[repr(C, packed)] -pub struct GetPldmCommandsResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, - pub supported_cmds: [u8; PLDM_CMDS_BITMAP_LEN], -} - -impl GetPldmCommandsResponse { - pub fn new(instance_id: InstanceId, completion_code: u8, supported_cmds: &[u8]) -> Self { - Self { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::Base, - PldmControlCmd::GetPldmCommands as u8, - ), - completion_code, - supported_cmds: construct_bitmap::(supported_cmds), - } - } -} - -#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] -#[repr(C, packed)] -pub struct GetPldmTypeRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, -} - -impl GetPldmTypeRequest { - pub fn new(instance_id: InstanceId, message_type: PldmMsgType) -> Self { - Self { - hdr: PldmMsgHeader::new( - instance_id, - message_type, - PldmSupportedType::Base, - PldmControlCmd::GetPldmTypes as u8, - ), - } - } -} - -fn construct_bitmap(items: &[u8]) -> [u8; N] { - let mut bitmap = [0u8; N]; - for &item in items.iter().take(N * 8) { - let byte_index = (item / 8) as usize; - let bit_index = (item % 8) as usize; - bitmap[byte_index] |= 1 << bit_index; - } - bitmap -} - -pub fn is_bit_set(bitmap: &[u8], item: u8) -> bool { - let byte_index = (item / 8) as usize; - let bit_index = (item % 8) as usize; - bitmap[byte_index] & (1 << bit_index) != 0 -} - -#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] -#[repr(C, packed)] -pub struct GetPldmTypeResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, - pub pldm_types: [u8; PLDM_TYPES_BITMAP_LEN], -} - -impl GetPldmTypeResponse { - pub fn new(instance_id: InstanceId, completion_code: u8, supported_types: &[u8]) -> Self { - Self { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::Base, - PldmControlCmd::GetPldmTypes as u8, - ), - completion_code, - pldm_types: construct_bitmap::(supported_types), - } - } -} - -#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] -#[repr(C, packed)] -pub struct GetPldmVersionRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub data_transfer_handle: u32, - pub transfer_op_flag: u8, - pub pldm_type: u8, -} - -impl GetPldmVersionRequest { - pub fn new( - instance_id: InstanceId, - message_type: PldmMsgType, - data_transfer_handle: u32, - transfer_op_flag: TransferOperationFlag, - pldm_type: PldmSupportedType, - ) -> Self { - Self { - hdr: PldmMsgHeader::new( - instance_id, - message_type, - PldmSupportedType::Base, - PldmControlCmd::GetPldmVersion as u8, - ), - data_transfer_handle, - transfer_op_flag: transfer_op_flag as u8, - pldm_type: pldm_type as u8, - } - } -} - -#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq, Clone)] -#[repr(C, packed)] -pub struct GetPldmVersionResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, - pub next_transfer_handle: u32, // next portion of PLDM version data transfer - pub transfer_rsp_flag: u8, // PLDM GetVersion transfer flag - pub version_data: Ver32, // PLDM GetVersion version field. Support only 1 version field. Version data is version and checksum -} - -impl GetPldmVersionResponse { - pub fn new( - instance_id: InstanceId, - completion_code: u8, - next_transfer_handle: u32, - transfer_rsp_flag: TransferRespFlag, - version_str: ProtocolVersionStr, - ) -> Result { - Ok(Self { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::Base, - PldmControlCmd::GetPldmVersion as u8, - ), - completion_code, - next_transfer_handle, - transfer_rsp_flag: transfer_rsp_flag as u8, - version_data: PldmVersion::try_from(version_str) - .map_err(|_| PldmError::InvalidProtocolVersion)? - .bcd_encode_to_ver32(), - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::codec::{PldmCodec, PldmCodecError}; - - #[test] - fn test_get_tid_request() { - let request = GetTidRequest::new(0x01, PldmMsgType::Request); - let mut buffer = [0u8; core::mem::size_of::()]; - request.encode(&mut buffer).unwrap(); - let decoded_request = GetTidRequest::decode(&buffer).unwrap(); - assert_eq!(request, decoded_request); - } - - #[test] - fn test_get_tid_response() { - let response = GetTidResponse::new(0x01, 42, 0); - let mut buffer = [0u8; core::mem::size_of::()]; - response.encode(&mut buffer).unwrap(); - let decoded_response = GetTidResponse::decode(&buffer).unwrap(); - assert_eq!(response, decoded_response); - } - - #[test] - fn test_set_tid_request() { - let request = SetTidRequest::new(0x01, PldmMsgType::Request, 42); - let mut buffer = [0u8; core::mem::size_of::()]; - request.encode(&mut buffer).unwrap(); - let decoded_request = SetTidRequest::decode(&buffer).unwrap(); - assert_eq!(request, decoded_request); - } - - #[test] - fn test_set_tid_response() { - let response = SetTidResponse::new(0x01, 0); - let mut buffer = [0u8; core::mem::size_of::()]; - response.encode(&mut buffer).unwrap(); - let decoded_response = SetTidResponse::decode(&buffer).unwrap(); - assert_eq!(response, decoded_response); - } - - #[test] - fn test_get_pldm_commands_request() { - let request = GetPldmCommandsRequest::new(0x01, PldmMsgType::Request, 1, "1.0.0"); - let mut buffer = [0u8; core::mem::size_of::()]; - request.encode(&mut buffer).unwrap(); - let decoded_request = GetPldmCommandsRequest::decode(&buffer).unwrap(); - assert_eq!(request, decoded_request); - } - - #[test] - fn test_get_pldm_commands_response() { - let response = GetPldmCommandsResponse::new(0x01, 0, &[1, 2, 3]); - let mut buffer = [0u8; core::mem::size_of::()]; - response.encode(&mut buffer).unwrap(); - let decoded_response = GetPldmCommandsResponse::decode(&buffer).unwrap(); - assert_eq!(response, decoded_response); - } - - #[test] - fn test_get_pldm_type_request() { - let request = GetPldmTypeRequest::new(0x01, PldmMsgType::Request); - let mut buffer = [0u8; core::mem::size_of::()]; - request.encode(&mut buffer).unwrap(); - let decoded_request = GetPldmTypeRequest::decode(&buffer).unwrap(); - assert_eq!(request, decoded_request); - } - - #[test] - fn test_get_pldm_type_response() { - let response = GetPldmTypeResponse::new(0x01, 0, &[0, 5]); - // Check bit map - let mut expected_bitmap = [0u8; PLDM_TYPES_BITMAP_LEN]; - expected_bitmap[0] = 0b00100001; - assert_eq!(response.pldm_types, expected_bitmap); - - let mut buffer = [0u8; core::mem::size_of::()]; - response.encode(&mut buffer).unwrap(); - let decoded_response = GetPldmTypeResponse::decode(&buffer).unwrap(); - assert_eq!(response, decoded_response); - } - - #[test] - fn test_get_pldm_version_request() { - let request = GetPldmVersionRequest::new( - 0x01, - PldmMsgType::Request, - 0, - TransferOperationFlag::GetFirstPart, - PldmSupportedType::Base, - ); - let mut buffer = [0u8; core::mem::size_of::()]; - request.encode(&mut buffer).unwrap(); - let decoded_request = GetPldmVersionRequest::decode(&buffer).unwrap(); - assert_eq!(request, decoded_request); - } - - #[test] - fn test_get_pldm_version_response() { - let response = - GetPldmVersionResponse::new(0x01, 0, 0, TransferRespFlag::StartAndEnd, "1.3.0") - .unwrap(); - let mut buffer = [0u8; core::mem::size_of::()]; - response.encode(&mut buffer).unwrap(); - let decoded_response = GetPldmVersionResponse::decode(&buffer).unwrap(); - assert_eq!(response, decoded_response); - } - - #[test] - fn test_buffer_too_short() { - let buffer = [0u8; 2]; - let result = GetTidRequest::decode(&buffer); - assert_eq!(result, Err(PldmCodecError::BufferTooShort)); - } -} diff --git a/src/message/firmware_update/activate_fw.rs b/src/message/firmware_update/activate_fw.rs deleted file mode 100644 index b7b083a..0000000 --- a/src/message/firmware_update/activate_fw.rs +++ /dev/null @@ -1,98 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::protocol::base::{ - InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, -}; -use crate::protocol::firmware_update::FwUpdateCmd; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -#[derive(Debug, Copy, Clone, PartialEq)] -#[repr(u8)] -pub enum SelfContainedActivationRequest { - NotActivateSelfContainedComponents = 0, - ActivateSelfContainedComponents = 1, -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct ActivateFirmwareRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub self_contained_activation_req: u8, -} - -impl ActivateFirmwareRequest { - pub fn new( - instance_id: InstanceId, - msg_type: PldmMsgType, - self_contained_activation_req: SelfContainedActivationRequest, - ) -> ActivateFirmwareRequest { - ActivateFirmwareRequest { - hdr: PldmMsgHeader::new( - instance_id, - msg_type, - PldmSupportedType::FwUpdate, - FwUpdateCmd::ActivateFirmware as u8, - ), - self_contained_activation_req: self_contained_activation_req as u8, - } - } -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct ActivateFirmwareResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, - pub estimated_time_activation: u16, -} - -impl ActivateFirmwareResponse { - pub fn new( - instance_id: InstanceId, - completion_code: u8, - estimated_time_activation: u16, - ) -> ActivateFirmwareResponse { - ActivateFirmwareResponse { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::FwUpdate, - FwUpdateCmd::ActivateFirmware as u8, - ), - completion_code, - estimated_time_activation, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::codec::PldmCodec; - - #[test] - fn test_activate_firmware_request() { - let request = ActivateFirmwareRequest::new( - 1, - PldmMsgType::Request, - SelfContainedActivationRequest::ActivateSelfContainedComponents, - ); - - let mut buffer = [0u8; core::mem::size_of::()]; - request.encode(&mut buffer).unwrap(); - - let decoded_request = ActivateFirmwareRequest::decode(&buffer).unwrap(); - assert_eq!(request, decoded_request); - } - - #[test] - fn test_activate_firmware_response() { - let response = ActivateFirmwareResponse::new(1, 0, 10); - - let mut buffer = [0u8; core::mem::size_of::()]; - response.encode(&mut buffer).unwrap(); - - let decoded_response = ActivateFirmwareResponse::decode(&buffer).unwrap(); - assert_eq!(response, decoded_response); - } -} diff --git a/src/message/firmware_update/apply_complete.rs b/src/message/firmware_update/apply_complete.rs deleted file mode 100644 index 8c6d6b0..0000000 --- a/src/message/firmware_update/apply_complete.rs +++ /dev/null @@ -1,117 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::error::PldmError; -use crate::protocol::base::{ - InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, -}; -use crate::protocol::firmware_update::{ComponentActivationMethods, FwUpdateCmd}; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum ApplyResult { - ApplySuccess = 0x00, - ApplySuccessWithActivationMethod = 0x01, - ApplyFailureMemoryIssue = 0x02, - ApplyTimeOut = 0x09, - #[default] - ApplyGenericError = 0x0a, - VendorDefined, -} - -impl TryFrom for ApplyResult { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0x00 => Ok(ApplyResult::ApplySuccess), - 0x01 => Ok(ApplyResult::ApplySuccessWithActivationMethod), - 0x02 => Ok(ApplyResult::ApplyFailureMemoryIssue), - 0x09 => Ok(ApplyResult::ApplyTimeOut), - 0x0a => Ok(ApplyResult::ApplyGenericError), - 0xb0..=0xcf => Ok(ApplyResult::VendorDefined), - _ => Err(PldmError::InvalidApplyResult), - } - } -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct ApplyCompleteRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub apply_result: u8, - pub comp_activation_methods_modification: u16, -} - -impl ApplyCompleteRequest { - pub fn new( - instance_id: InstanceId, - msg_type: PldmMsgType, - apply_result: ApplyResult, - comp_activation_methods: ComponentActivationMethods, - ) -> Self { - ApplyCompleteRequest { - hdr: PldmMsgHeader::new( - instance_id, - msg_type, - PldmSupportedType::FwUpdate, - FwUpdateCmd::ApplyComplete as u8, - ), - apply_result: apply_result as u8, - comp_activation_methods_modification: comp_activation_methods.0, - } - } -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct ApplyCompleteResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, -} - -impl ApplyCompleteResponse { - pub fn new(instance_id: InstanceId, completion_code: u8) -> ApplyCompleteResponse { - ApplyCompleteResponse { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::FwUpdate, - FwUpdateCmd::ApplyComplete as u8, - ), - completion_code, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::codec::PldmCodec; - - #[test] - fn test_apply_complete_request() { - let request = ApplyCompleteRequest::new( - 1, - PldmMsgType::Request, - ApplyResult::ApplySuccess, - ComponentActivationMethods(0x0001), - ); - - let mut buffer = [0u8; 64]; - let bytes_written = request.encode(&mut buffer).unwrap(); - assert_eq!(bytes_written, core::mem::size_of::()); - let decoded_request = ApplyCompleteRequest::decode(&buffer[..bytes_written]).unwrap(); - assert_eq!(request, decoded_request); - } - - #[test] - fn test_apply_complete_response() { - let response = ApplyCompleteResponse::new(1, 0); - let mut buffer = [0u8; 64]; - let bytes_written = response.encode(&mut buffer).unwrap(); - assert_eq!(bytes_written, core::mem::size_of::()); - let decoded_response = ApplyCompleteResponse::decode(&buffer[..bytes_written]).unwrap(); - assert_eq!(response, decoded_response); - } -} diff --git a/src/message/firmware_update/get_fw_params.rs b/src/message/firmware_update/get_fw_params.rs deleted file mode 100644 index c18d92d..0000000 --- a/src/message/firmware_update/get_fw_params.rs +++ /dev/null @@ -1,362 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::codec::{PldmCodec, PldmCodecError}; -use crate::protocol::base::{ - InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, -}; -use crate::protocol::firmware_update::{ - ComponentParameterEntry, FirmwareDeviceCapability, FwUpdateCmd, PldmFirmwareString, - MAX_COMPONENT_COUNT, PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN, -}; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct GetFirmwareParametersRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, -} - -impl GetFirmwareParametersRequest { - pub fn new(instance_id: InstanceId, message_type: PldmMsgType) -> Self { - GetFirmwareParametersRequest { - hdr: PldmMsgHeader::new( - instance_id, - message_type, - PldmSupportedType::FwUpdate, - FwUpdateCmd::GetFirmwareParameters as u8, - ), - } - } -} - -#[derive(Debug, Clone, PartialEq, FromBytes, IntoBytes, Immutable)] -#[repr(C, packed)] -pub struct FirmwareParamFixed { - pub capabilities_during_update: FirmwareDeviceCapability, - pub comp_count: u16, - pub active_comp_image_set_ver_str_type: u8, - pub active_comp_image_set_ver_str_len: u8, - pub pending_comp_image_set_ver_str_type: u8, - pub pending_comp_image_set_ver_str_len: u8, -} - -impl Default for FirmwareParamFixed { - fn default() -> Self { - FirmwareParamFixed { - capabilities_during_update: FirmwareDeviceCapability(0), - comp_count: 0, - active_comp_image_set_ver_str_type: 0, - active_comp_image_set_ver_str_len: 0, - pending_comp_image_set_ver_str_type: 0, - pending_comp_image_set_ver_str_len: 0, - } - } -} - -#[derive(Debug, Clone, PartialEq, Default)] -#[repr(C)] -pub struct FirmwareParameters { - pub params_fixed: FirmwareParamFixed, - pub active_comp_image_set_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], - pub pending_comp_image_set_ver_str: Option<[u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]>, - pub comp_param_table: [ComponentParameterEntry; MAX_COMPONENT_COUNT], -} - -impl FirmwareParameters { - pub fn new( - capabilities_during_update: FirmwareDeviceCapability, - comp_count: u16, - active_comp_image_set_version: &PldmFirmwareString, - pending_comp_image_set_version: &PldmFirmwareString, - comp_param_table: &[ComponentParameterEntry], - ) -> Self { - FirmwareParameters { - params_fixed: FirmwareParamFixed { - capabilities_during_update, - comp_count, - active_comp_image_set_ver_str_type: active_comp_image_set_version.str_type, - active_comp_image_set_ver_str_len: active_comp_image_set_version.str_len, - pending_comp_image_set_ver_str_type: pending_comp_image_set_version.str_type, - pending_comp_image_set_ver_str_len: pending_comp_image_set_version.str_len, - }, - pending_comp_image_set_ver_str: if pending_comp_image_set_version.str_len > 0 { - let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - let len = pending_comp_image_set_version.str_data.len(); - arr[..len].copy_from_slice(&pending_comp_image_set_version.str_data); - Some(arr) - } else { - None - }, - active_comp_image_set_ver_str: { - let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - let len = active_comp_image_set_version.str_data.len(); - arr[..len].copy_from_slice(&active_comp_image_set_version.str_data); - arr - }, - comp_param_table: { - let count = comp_param_table.len().min(MAX_COMPONENT_COUNT); - core::array::from_fn(|i| { - if i < count { - comp_param_table[i].clone() - } else { - ComponentParameterEntry::default() - } - }) - }, - } - } - - fn codec_size_in_bytes(&self) -> usize { - let mut bytes = core::mem::size_of::(); - if self.pending_comp_image_set_ver_str.is_some() { - bytes += self.params_fixed.pending_comp_image_set_ver_str_len as usize; - } - bytes += self.params_fixed.active_comp_image_set_ver_str_len as usize; - - for i in 0..self.params_fixed.comp_count as usize { - bytes += self.comp_param_table[i].codec_size_in_bytes(); - } - bytes - } -} - -impl PldmCodec for FirmwareParameters { - fn encode(&self, buffer: &mut [u8]) -> Result { - let mut offset = 0; - if buffer.len() < self.codec_size_in_bytes() { - return Err(PldmCodecError::BufferTooShort); - } - self.params_fixed - .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) - .unwrap(); - - offset += core::mem::size_of::(); - - let len = self.params_fixed.active_comp_image_set_ver_str_len as usize; - buffer[offset..offset + len].copy_from_slice(&self.active_comp_image_set_ver_str[..len]); - offset += len; - - if let Some(pending_comp_image_set_ver_str) = &self.pending_comp_image_set_ver_str { - let len = self.params_fixed.pending_comp_image_set_ver_str_len as usize; - buffer[offset..offset + len].copy_from_slice(&pending_comp_image_set_ver_str[..len]); - offset += len; - } - - for i in 0..self.params_fixed.comp_count as usize { - let bytes = self.comp_param_table[i].encode(&mut buffer[offset..])?; - offset += bytes; - } - - Ok(offset) - } - - fn decode(buffer: &[u8]) -> Result { - let mut offset = 0; - - let params_fixed = FirmwareParamFixed::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += core::mem::size_of::(); - - let mut active_comp_image_set_ver_str = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - let len = params_fixed.active_comp_image_set_ver_str_len as usize; - active_comp_image_set_ver_str[..len].copy_from_slice( - buffer - .get(offset..offset + len) - .ok_or(PldmCodecError::BufferTooShort)?, - ); - - offset += len; - - let pending_comp_image_set_ver_str = if params_fixed.pending_comp_image_set_ver_str_len > 0 - { - let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - let len = params_fixed.pending_comp_image_set_ver_str_len as usize; - arr[..len].copy_from_slice( - buffer - .get(offset..offset + len) - .ok_or(PldmCodecError::BufferTooShort)?, - ); - Some(arr) - } else { - None - }; - offset += params_fixed.pending_comp_image_set_ver_str_len as usize; - - let mut index = 0; - let comp_param_table: [ComponentParameterEntry; MAX_COMPONENT_COUNT] = - core::array::from_fn(|_| { - if index < params_fixed.comp_count as usize { - let comp_param_table_entry = - ComponentParameterEntry::decode(&buffer[offset..]).unwrap(); - offset += comp_param_table_entry.codec_size_in_bytes(); - index += 1; - comp_param_table_entry - } else { - ComponentParameterEntry::default() // Fill remaining slots with default values - } - }); - - Ok(FirmwareParameters { - params_fixed, - active_comp_image_set_ver_str, - pending_comp_image_set_ver_str, - comp_param_table, - }) - } -} - -#[derive(Debug, Clone, PartialEq, Default)] -#[repr(C)] -pub struct GetFirmwareParametersResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, - pub parms: FirmwareParameters, -} - -impl GetFirmwareParametersResponse { - pub fn new(instance_id: InstanceId, completion_code: u8, parms: &FirmwareParameters) -> Self { - GetFirmwareParametersResponse { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::FwUpdate, - FwUpdateCmd::GetFirmwareParameters as u8, - ), - completion_code, - parms: parms.clone(), - } - } - - // Calculate the size of the response in bytes for encoding - pub fn codec_size_in_bytes(&self) -> usize { - let mut bytes = 0; - bytes += - PLDM_MSG_HEADER_LEN + core::mem::size_of::() + self.parms.codec_size_in_bytes(); - bytes - } -} - -impl PldmCodec for GetFirmwareParametersResponse { - fn encode(&self, buffer: &mut [u8]) -> Result { - let mut offset = 0; - if buffer.len() < self.codec_size_in_bytes() { - return Err(PldmCodecError::BufferTooShort); - } - self.hdr - .write_to(&mut buffer[offset..offset + PLDM_MSG_HEADER_LEN]) - .unwrap(); - offset += PLDM_MSG_HEADER_LEN; - - self.completion_code - .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) - .unwrap(); - offset += core::mem::size_of::(); - - let bytes = self.parms.encode(&mut buffer[offset..])?; - offset += bytes; - Ok(offset) - } - - fn decode(buffer: &[u8]) -> Result { - let mut offset = 0; - - let hdr = PldmMsgHeader::read_from_bytes( - buffer - .get(offset..offset + PLDM_MSG_HEADER_LEN) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - - offset += PLDM_MSG_HEADER_LEN; - let completion_code = u8::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += core::mem::size_of::(); - - let parms = FirmwareParameters::decode(&buffer[offset..])?; - Ok(GetFirmwareParametersResponse { - hdr, - completion_code, - parms, - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - - use crate::protocol::firmware_update::{ - ComponentActivationMethods, ComponentClassification, FirmwareDeviceCapability, - PldmFirmwareString, PldmFirmwareVersion, - }; - - fn construct_firmware_params() -> FirmwareParameters { - // Construct firmware params - let active_firmware_string = PldmFirmwareString::new("ASCII", "mcu-runtime-1.0").unwrap(); - let active_firmware_version = - PldmFirmwareVersion::new(0x12345678, &active_firmware_string, Some("20250210")); - let pending_firmware_string = PldmFirmwareString::new("ASCII", "mcu-runtime-1.5").unwrap(); - let pending_firmware_version = - PldmFirmwareVersion::new(0x87654321, &pending_firmware_string, Some("20250213")); - let comp_activation_methods = ComponentActivationMethods(0x0001); - let capabilities_during_update = FirmwareDeviceCapability(0x0010); - let component_parameter_entry = ComponentParameterEntry::new( - ComponentClassification::Firmware, - 0x0001, - 0x01, - &active_firmware_version, - &pending_firmware_version, - comp_activation_methods, - capabilities_during_update, - ); - - const COMP_COUNT: usize = 8; - let comp_param_table: [ComponentParameterEntry; COMP_COUNT] = - core::array::from_fn(|_| component_parameter_entry.clone()); - FirmwareParameters::new( - capabilities_during_update, - COMP_COUNT as u16, - &active_firmware_string, - &pending_firmware_string, - &comp_param_table, - ) - } - - #[test] - fn test_get_firmware_parameters_request() { - 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] - fn test_get_firmware_parameters() { - 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] - fn test_get_firmware_parameters_response() { - 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); - } -} diff --git a/src/message/firmware_update/get_status.rs b/src/message/firmware_update/get_status.rs deleted file mode 100644 index 42c5baa..0000000 --- a/src/message/firmware_update/get_status.rs +++ /dev/null @@ -1,225 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::error::PldmError; -use crate::protocol::base::{ - InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, -}; -use crate::protocol::firmware_update::{FirmwareDeviceState, FwUpdateCmd, UpdateOptionFlags}; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -pub const PROGRESS_PERCENT_NOT_SUPPORTED: u8 = 101; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ProgressPercent(u8); - -impl Default for ProgressPercent { - fn default() -> Self { - ProgressPercent::new(PROGRESS_PERCENT_NOT_SUPPORTED).unwrap() - } -} -impl ProgressPercent { - pub fn new(value: u8) -> Result { - if value > PROGRESS_PERCENT_NOT_SUPPORTED { - Err(PldmError::InvalidData) - } else { - Ok(ProgressPercent(value)) - } - } - - pub fn value(&self) -> u8 { - self.0 - } - - pub fn set_value(&mut self, value: u8) -> Result<(), PldmError> { - if value > 100 { - Err(PldmError::InvalidData) - } else { - self.0 = value; - Ok(()) - } - } -} - -#[repr(u8)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum AuxState { - OperationInProgress = 0, - OperationSuccessful = 1, - OperationFailed = 2, - IdleLearnComponentsReadXfer = 3, -} - -#[repr(u8)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum AuxStateStatus { - AuxStateInProgressOrSuccess = 0x00, - Reserved, - Timeout = 0x09, - GenericError = 0x0a, - VendorDefined, -} - -impl TryFrom for AuxStateStatus { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0x00 => Ok(AuxStateStatus::AuxStateInProgressOrSuccess), - 0x01..=0x08 => Ok(AuxStateStatus::Reserved), - 0x09 => Ok(AuxStateStatus::Timeout), - 0x0a => Ok(AuxStateStatus::GenericError), - 0x70..=0xef => Ok(AuxStateStatus::VendorDefined), - _ => Err(PldmError::InvalidAuxStateStatus), - } - } -} - -#[repr(u8)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum GetStatusReasonCode { - Initialization = 0, - ActivateFw = 1, - CancelUpdate = 2, - LearnComponentTimeout = 3, - ReadyXferTimeout = 4, - DownloadTimeout = 5, - VerifyTimeout = 6, - ApplyTimeout = 7, - VendorDefined, -} - -impl TryFrom for GetStatusReasonCode { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(GetStatusReasonCode::Initialization), - 1 => Ok(GetStatusReasonCode::ActivateFw), - 2 => Ok(GetStatusReasonCode::CancelUpdate), - 3 => Ok(GetStatusReasonCode::LearnComponentTimeout), - 4 => Ok(GetStatusReasonCode::ReadyXferTimeout), - 5 => Ok(GetStatusReasonCode::DownloadTimeout), - 6 => Ok(GetStatusReasonCode::VerifyTimeout), - 7 => Ok(GetStatusReasonCode::ApplyTimeout), - 200..=255 => Ok(GetStatusReasonCode::VendorDefined), - _ => Err(PldmError::InvalidGetStatusReasonCode), - } - } -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct GetStatusRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, -} - -impl GetStatusRequest { - pub fn new(instance_id: InstanceId, msg_type: PldmMsgType) -> GetStatusRequest { - GetStatusRequest { - hdr: PldmMsgHeader::new( - instance_id, - msg_type, - PldmSupportedType::FwUpdate, - FwUpdateCmd::GetStatus as u8, - ), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -#[repr(u8)] -pub enum UpdateOptionResp { - NoForceUpdate = 0, - ForceUpdate = 1, -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct GetStatusResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, - pub current_state: u8, - pub previous_state: u8, - pub aux_state: u8, - pub aux_state_status: u8, - pub progress_percent: u8, - pub reason_code: u8, - pub update_option_flags_enabled: u32, // Assuming bitfield32_t is a 32-bit integer -} - -#[allow(clippy::too_many_arguments)] -impl GetStatusResponse { - pub fn new( - instance_id: InstanceId, - completion_code: u8, - current_state: FirmwareDeviceState, - previous_state: FirmwareDeviceState, - aux_state: AuxState, - aux_state_status: u8, - progress_percent: ProgressPercent, - reason_code: GetStatusReasonCode, - update_option: UpdateOptionResp, - ) -> GetStatusResponse { - GetStatusResponse { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::FwUpdate, - FwUpdateCmd::GetStatus as u8, - ), - completion_code, - current_state: current_state as u8, - previous_state: previous_state as u8, - aux_state: aux_state as u8, - aux_state_status, - progress_percent: progress_percent.value(), - reason_code: reason_code as u8, - update_option_flags_enabled: { - let mut flags = UpdateOptionFlags(0); - flags.set_request_force_update(update_option == UpdateOptionResp::ForceUpdate); - flags.0 - }, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::codec::PldmCodec; - - #[test] - fn test_get_status_request() { - let instance_id = 1; - let msg_type = PldmMsgType::Request; - let request = GetStatusRequest::new(instance_id, msg_type); - let mut buffer = [0u8; 16]; - let encoded_size = request.encode(&mut buffer).unwrap(); - assert_eq!(encoded_size, core::mem::size_of::()); - - let decoded_request = GetStatusRequest::decode(&buffer).unwrap(); - assert_eq!(request, decoded_request); - } - - #[test] - fn test_get_status_response() { - let response = GetStatusResponse::new( - 1, - 0, - FirmwareDeviceState::Idle, - FirmwareDeviceState::Idle, - AuxState::IdleLearnComponentsReadXfer, - AuxStateStatus::AuxStateInProgressOrSuccess as u8, - ProgressPercent::new(50).unwrap(), - GetStatusReasonCode::Initialization, - UpdateOptionResp::NoForceUpdate, - ); - - let mut buffer = [0u8; 32]; - let encoded_size = response.encode(&mut buffer).unwrap(); - assert_eq!(encoded_size, core::mem::size_of::()); - - let decoded_response = GetStatusResponse::decode(&buffer).unwrap(); - assert_eq!(response, decoded_response); - } -} diff --git a/src/message/firmware_update/mod.rs b/src/message/firmware_update/mod.rs deleted file mode 100644 index 859a174..0000000 --- a/src/message/firmware_update/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed under the Apache-2.0 license - -pub mod activate_fw; -pub mod apply_complete; -pub mod get_fw_params; -pub mod get_status; -pub mod pass_component; -pub mod query_devid; -pub mod request_cancel; -pub mod request_fw_data; -pub mod request_update; -pub mod transfer_complete; -pub mod update_component; -pub mod verify_complete; diff --git a/src/message/firmware_update/pass_component.rs b/src/message/firmware_update/pass_component.rs deleted file mode 100644 index a8d2b7c..0000000 --- a/src/message/firmware_update/pass_component.rs +++ /dev/null @@ -1,189 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::codec::{PldmCodec, PldmCodecError}; -use crate::protocol::base::{ - InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, TransferRespFlag, - PLDM_MSG_HEADER_LEN, -}; -use crate::protocol::firmware_update::{ - ComponentClassification, ComponentResponse, ComponentResponseCode, FwUpdateCmd, - PldmFirmwareString, PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN, -}; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -#[derive(Debug, Copy, Clone, PartialEq)] -#[repr(C)] -pub struct PassComponentTableRequest { - pub fixed: PassComponentTableRequestFixed, - pub comp_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], -} - -#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct PassComponentTableRequestFixed { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub transfer_flag: u8, - pub comp_classification: u16, - pub comp_identifier: u16, - pub comp_classification_index: u8, - pub comp_comparison_stamp: u32, - pub comp_ver_str_type: u8, - pub comp_ver_str_len: u8, -} - -#[allow(clippy::too_many_arguments)] -impl PassComponentTableRequest { - pub fn new( - instance_id: InstanceId, - msg_type: PldmMsgType, - transfer_flag: TransferRespFlag, - comp_classification: ComponentClassification, - comp_identifier: u16, - comp_classification_index: u8, - comp_comparison_stamp: u32, - comp_version_string: &PldmFirmwareString, - ) -> PassComponentTableRequest { - PassComponentTableRequest { - fixed: PassComponentTableRequestFixed { - hdr: PldmMsgHeader::new( - instance_id, - msg_type, - PldmSupportedType::FwUpdate, - FwUpdateCmd::PassComponentTable as u8, - ), - transfer_flag: transfer_flag as u8, - comp_classification: comp_classification as u16, - comp_identifier, - comp_classification_index, - comp_comparison_stamp, - comp_ver_str_type: comp_version_string.str_type, - comp_ver_str_len: comp_version_string.str_len, - }, - comp_ver_str: { - let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - let len = comp_version_string.str_data.len(); - arr[..len].copy_from_slice(&comp_version_string.str_data); - arr - }, - } - } - - pub fn codec_size_in_bytes(&self) -> usize { - let mut bytes = core::mem::size_of::(); - bytes += self.fixed.comp_ver_str_len as usize; - bytes - } -} - -impl PldmCodec for PassComponentTableRequest { - fn encode(&self, buffer: &mut [u8]) -> Result { - if buffer.len() < self.codec_size_in_bytes() { - return Err(PldmCodecError::BufferTooShort); - } - - let mut offset = 0; - self.fixed - .write_to( - &mut buffer - [offset..offset + core::mem::size_of::()], - ) - .unwrap(); - offset += core::mem::size_of::(); - - let str_len = self.fixed.comp_ver_str_len as usize; - buffer[offset..offset + str_len].copy_from_slice(&self.comp_ver_str[..str_len]); - Ok(offset + str_len) - } - - fn decode(buffer: &[u8]) -> Result { - let mut offset = 0; - let fixed = PassComponentTableRequestFixed::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += core::mem::size_of::(); - - let str_len = fixed.comp_ver_str_len as usize; - let mut comp_ver_str = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - comp_ver_str[..str_len].copy_from_slice( - &buffer - .get(offset..offset + str_len) - .ok_or(PldmCodecError::BufferTooShort)?[..str_len], - ); - - Ok(PassComponentTableRequest { - fixed, - comp_ver_str, - }) - } -} -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq, Default)] -#[repr(C, packed)] -pub struct PassComponentTableResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, - pub comp_resp: u8, - pub comp_resp_code: u8, -} - -impl PassComponentTableResponse { - pub fn new( - instance_id: InstanceId, - completion_code: u8, - comp_resp: ComponentResponse, - comp_resp_code: ComponentResponseCode, - ) -> PassComponentTableResponse { - PassComponentTableResponse { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::FwUpdate, - FwUpdateCmd::PassComponentTable as u8, - ), - completion_code, - comp_resp: comp_resp as u8, - comp_resp_code: comp_resp_code as u8, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_pass_component_table_request() { - let request = PassComponentTableRequest::new( - 1, - PldmMsgType::Request, - TransferRespFlag::StartAndEnd, - ComponentClassification::Firmware, - 2, - 3, - 4, - &PldmFirmwareString::new("UTF-8", "bmc-fw-1.2.0").unwrap(), - ); - - let mut buffer = [0u8; 1024]; - let encoded_size = request.encode(&mut buffer).unwrap(); - let decoded_request = PassComponentTableRequest::decode(&buffer[..encoded_size]).unwrap(); - assert_eq!(request, decoded_request); - } - - #[test] - fn test_pass_component_table_response() { - let response = PassComponentTableResponse::new( - 0, - 0, - ComponentResponse::CompCanBeUpdated, - ComponentResponseCode::CompCanBeUpdated, - ); - - let mut buffer = [0u8; 1024]; - let encoded_size = response.encode(&mut buffer).unwrap(); - let decoded_response = PassComponentTableResponse::decode(&buffer[..encoded_size]).unwrap(); - assert_eq!(response, decoded_response); - } -} diff --git a/src/message/firmware_update/query_devid.rs b/src/message/firmware_update/query_devid.rs deleted file mode 100644 index d17a980..0000000 --- a/src/message/firmware_update/query_devid.rs +++ /dev/null @@ -1,250 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::codec::{PldmCodec, PldmCodecError}; -use crate::error::PldmError; -use crate::protocol::base::{ - InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, -}; -use crate::protocol::firmware_update::{Descriptor, FwUpdateCmd}; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -pub const ADDITIONAL_DESCRIPTORS_MAX_COUNT: usize = 4; // Arbitrary limit for static storage - -#[derive(Debug, Clone, FromBytes, IntoBytes, PartialEq, Immutable)] -#[repr(C, packed)] -pub struct QueryDeviceIdentifiersRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, -} - -impl QueryDeviceIdentifiersRequest { - pub fn new(instance_id: InstanceId, message_type: PldmMsgType) -> Self { - QueryDeviceIdentifiersRequest { - hdr: PldmMsgHeader::new( - instance_id, - message_type, - PldmSupportedType::FwUpdate, - FwUpdateCmd::QueryDeviceIdentifiers as u8, - ), - } - } -} - -#[derive(Debug, Clone, PartialEq, Default)] -#[repr(C)] -pub struct QueryDeviceIdentifiersResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, - pub device_identifiers_len: u32, - pub descriptor_count: u8, - pub initial_descriptor: Descriptor, - pub additional_descriptors: Option<[Descriptor; ADDITIONAL_DESCRIPTORS_MAX_COUNT]>, -} - -impl QueryDeviceIdentifiersResponse { - pub fn new( - instance_id: InstanceId, - completion_code: u8, - initial_descriptor: &Descriptor, - additional_descriptors: Option<&[Descriptor]>, - ) -> Result { - let descriptor_count = - 1 + additional_descriptors.map_or(0, |descriptors| descriptors.len()); - if descriptor_count > ADDITIONAL_DESCRIPTORS_MAX_COUNT + 1 { - return Err(PldmError::InvalidDescriptorCount); - } - - Ok(QueryDeviceIdentifiersResponse { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::FwUpdate, - FwUpdateCmd::QueryDeviceIdentifiers as u8, - ), - completion_code, - device_identifiers_len: { - let mut len = initial_descriptor.codec_size_in_bytes(); - if let Some(additional) = additional_descriptors { - for descriptor in additional.iter() { - len += descriptor.codec_size_in_bytes(); - } - } - len as u32 - }, - descriptor_count: descriptor_count as u8, - initial_descriptor: *initial_descriptor, - additional_descriptors: if descriptor_count > 1 { - if let Some(additional) = additional_descriptors { - let mut descriptors = - [Descriptor::new_empty(); ADDITIONAL_DESCRIPTORS_MAX_COUNT]; - descriptors[..additional.len()].copy_from_slice(additional); - Some(descriptors) - } else { - None - } - } else { - None - }, - }) - } - - pub fn codec_size_in_bytes(&self) -> usize { - let mut size = PLDM_MSG_HEADER_LEN - + core::mem::size_of::() - + core::mem::size_of::() - + core::mem::size_of::(); - size += self.initial_descriptor.codec_size_in_bytes(); - - if let Some(additional_descriptors) = &self.additional_descriptors { - for descriptor in additional_descriptors - .iter() - .take(self.descriptor_count as usize - 1) - { - size += descriptor.codec_size_in_bytes(); - } - } - size - } -} - -impl PldmCodec for QueryDeviceIdentifiersResponse { - fn encode(&self, buffer: &mut [u8]) -> Result { - if buffer.len() < self.codec_size_in_bytes() { - return Err(PldmCodecError::BufferTooShort); - } - let mut offset = 0; - self.hdr - .write_to(&mut buffer[offset..offset + PLDM_MSG_HEADER_LEN]) - .unwrap(); - offset += PLDM_MSG_HEADER_LEN; - - self.completion_code - .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) - .unwrap(); - offset += core::mem::size_of::(); - - self.device_identifiers_len - .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) - .unwrap(); - offset += core::mem::size_of::(); - - self.descriptor_count - .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) - .unwrap(); - offset += core::mem::size_of::(); - - let bytes = self.initial_descriptor.encode(&mut buffer[offset..])?; - offset += bytes; - - if let Some(additional_descriptors) = &self.additional_descriptors { - for descriptor in additional_descriptors - .iter() - .take(self.descriptor_count as usize - 1) - { - let bytes = descriptor.encode(&mut buffer[offset..])?; - offset += bytes; - } - } - Ok(offset) - } - - fn decode(buffer: &[u8]) -> Result { - let mut offset = 0; - - let hdr = PldmMsgHeader::<[u8; PLDM_MSG_HEADER_LEN]>::read_from_bytes( - buffer - .get(offset..offset + PLDM_MSG_HEADER_LEN) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += PLDM_MSG_HEADER_LEN; - - let completion_code = u8::read_from_bytes( - buffer - .get(offset..offset + 1) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += 1; - - let device_identifiers_len = u32::read_from_bytes( - buffer - .get(offset..offset + 4) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += 4; - - let descriptor_count = u8::read_from_bytes( - buffer - .get(offset..offset + 1) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += 1; - - let initial_descriptor = Descriptor::decode(&buffer[offset..])?; - offset += Descriptor::codec_size_in_bytes(&initial_descriptor); - - let additional_descriptors = if descriptor_count > 1 { - let mut descriptors = [Descriptor::new_empty(); ADDITIONAL_DESCRIPTORS_MAX_COUNT]; - let count = descriptor_count as usize - 1; - for descriptor in descriptors.iter_mut().take(count) { - *descriptor = Descriptor::decode(&buffer[offset..])?; - offset += Descriptor::codec_size_in_bytes(descriptor); - } - Some(descriptors) - } else { - None - }; - - Ok(QueryDeviceIdentifiersResponse { - hdr, - completion_code, - device_identifiers_len, - descriptor_count, - initial_descriptor, - additional_descriptors, - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::protocol::firmware_update::{Descriptor, DescriptorType}; - - #[test] - fn test_query_device_identifiers_resp() { - let instance_id = 0; - let completion_code = 0; - - let test_uid = [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, - 0x0F, 0x10, - ]; - let initial_descriptor = Descriptor::new(DescriptorType::Uuid, &test_uid).unwrap(); - let additional_descriptor = Descriptor::new(DescriptorType::Uuid, &test_uid).unwrap(); - - let resp = QueryDeviceIdentifiersResponse::new( - instance_id, - completion_code, - &initial_descriptor, - Some(&[additional_descriptor]), - ) - .unwrap(); - - assert_eq!(resp.descriptor_count, 2); - assert_eq!( - resp.device_identifiers_len, - (initial_descriptor.codec_size_in_bytes() + additional_descriptor.codec_size_in_bytes()) - as u32 - ); - - let mut buffer: [u8; 256] = [0; 256]; - let resp_len = resp.encode(&mut buffer).unwrap(); - assert_eq!(resp_len, resp.codec_size_in_bytes()); - - let resp_decoded = QueryDeviceIdentifiersResponse::decode(&buffer).unwrap(); - assert_eq!(resp, resp_decoded); - } -} diff --git a/src/message/firmware_update/request_cancel.rs b/src/message/firmware_update/request_cancel.rs deleted file mode 100644 index 905698f..0000000 --- a/src/message/firmware_update/request_cancel.rs +++ /dev/null @@ -1,157 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::error::PldmError; -use crate::protocol::base::{ - InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, -}; -use crate::protocol::firmware_update::FwUpdateCmd; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -#[repr(u8)] -pub enum NonFunctioningComponentIndication { - ComponentsFunctioning = 0, - ComponentsNotFunctioning = 1, -} - -impl TryFrom for NonFunctioningComponentIndication { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(NonFunctioningComponentIndication::ComponentsFunctioning), - 1 => Ok(NonFunctioningComponentIndication::ComponentsNotFunctioning), - _ => Err(PldmError::InvalidData), - } - } -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable)] -#[repr(C, packed)] -pub struct CancelUpdateComponentRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, -} - -impl CancelUpdateComponentRequest { - pub fn new(instance_id: InstanceId, msg_type: PldmMsgType) -> Self { - CancelUpdateComponentRequest { - hdr: PldmMsgHeader::new( - instance_id, - msg_type, - PldmSupportedType::FwUpdate, - FwUpdateCmd::CancelUpdateComponent as u8, - ), - } - } -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable)] -#[repr(C, packed)] -pub struct CancelUpdateComponentResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, -} - -impl CancelUpdateComponentResponse { - pub fn new(instance_id: InstanceId, completion_code: u8) -> CancelUpdateComponentResponse { - CancelUpdateComponentResponse { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::FwUpdate, - FwUpdateCmd::CancelUpdateComponent as u8, - ), - completion_code, - } - } -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct CancelUpdateRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, -} - -impl CancelUpdateRequest { - pub fn new(instance_id: InstanceId, msg_type: PldmMsgType) -> Self { - CancelUpdateRequest { - hdr: PldmMsgHeader::new( - instance_id, - msg_type, - PldmSupportedType::FwUpdate, - FwUpdateCmd::CancelUpdate as u8, - ), - } - } -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable)] -pub struct NonFunctioningComponentBitmap(u64); - -impl NonFunctioningComponentBitmap { - pub fn new(value: u64) -> Self { - NonFunctioningComponentBitmap(value) - } - - pub fn value(&self) -> u64 { - self.0 - } -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct CancelUpdateResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, - pub non_functioning_component_indication: u8, - pub non_functioning_component_bitmap: u64, -} - -impl CancelUpdateResponse { - pub fn new( - instance_id: InstanceId, - completion_code: u8, - non_functioning_component_indication: NonFunctioningComponentIndication, - non_functioning_component_bitmap: NonFunctioningComponentBitmap, - ) -> CancelUpdateResponse { - CancelUpdateResponse { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::FwUpdate, - FwUpdateCmd::CancelUpdate as u8, - ), - completion_code, - non_functioning_component_indication: non_functioning_component_indication as u8, - non_functioning_component_bitmap: non_functioning_component_bitmap.value(), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::codec::PldmCodec; - - #[test] - fn test_cancel_update_request() { - let cancel_update_request = CancelUpdateRequest::new(0x01, PldmMsgType::Request); - let mut buffer = [0u8; core::mem::size_of::()]; - cancel_update_request.encode(&mut buffer).unwrap(); - let decoded_request = CancelUpdateRequest::decode(&buffer).unwrap(); - assert_eq!(cancel_update_request, decoded_request); - } - - #[test] - fn test_cancel_update_response() { - let response = CancelUpdateResponse::new( - 0x01, - 0x00, - NonFunctioningComponentIndication::ComponentsFunctioning, - NonFunctioningComponentBitmap::new(0x00), - ); - let mut buffer = [0u8; core::mem::size_of::()]; - response.encode(&mut buffer).unwrap(); - let decoded_response = CancelUpdateResponse::decode(&buffer).unwrap(); - assert_eq!(response, decoded_response); - } -} diff --git a/src/message/firmware_update/request_fw_data.rs b/src/message/firmware_update/request_fw_data.rs deleted file mode 100644 index 432b124..0000000 --- a/src/message/firmware_update/request_fw_data.rs +++ /dev/null @@ -1,129 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::codec::{PldmCodec, PldmCodecError}; -use crate::protocol::base::{ - InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, -}; -use crate::protocol::firmware_update::FwUpdateCmd; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -pub const MAX_TRANSFER_SIZE: usize = 512; // Define an appropriate size - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct RequestFirmwareDataRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub offset: u32, - pub length: u32, -} - -impl RequestFirmwareDataRequest { - pub fn new( - instance_id: InstanceId, - msg_type: PldmMsgType, - offset: u32, - length: u32, - ) -> RequestFirmwareDataRequest { - let hdr = PldmMsgHeader::new( - instance_id, - msg_type, - PldmSupportedType::FwUpdate, - FwUpdateCmd::RequestFirmwareData as u8, - ); - RequestFirmwareDataRequest { - hdr, - offset, - length, - } - } -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct RequestFirmwareDataResponseFixed { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, -} - -#[derive(Debug, Clone, PartialEq)] -#[repr(C)] -pub struct RequestFirmwareDataResponse<'a> { - pub fixed: RequestFirmwareDataResponseFixed, - pub data: &'a [u8], -} - -impl RequestFirmwareDataResponse<'_> { - pub fn new( - instance_id: InstanceId, - completion_code: u8, - data: &[u8], - ) -> RequestFirmwareDataResponse { - let fixed = RequestFirmwareDataResponseFixed { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::FwUpdate, - FwUpdateCmd::RequestFirmwareData as u8, - ), - completion_code, - }; - RequestFirmwareDataResponse { fixed, data } - } - - pub fn codec_size_in_bytes(&self) -> usize { - let mut bytes = core::mem::size_of::(); - bytes += self.data.len(); - bytes - } -} - -impl PldmCodec for RequestFirmwareDataResponse<'_> { - fn encode(&self, buffer: &mut [u8]) -> Result { - if buffer.len() < self.codec_size_in_bytes() { - return Err(PldmCodecError::BufferTooShort); - } - - let mut offset = 0; - let bytes = core::mem::size_of::(); - self.fixed - .write_to(&mut buffer[offset..offset + bytes]) - .unwrap(); - offset += bytes; - - let data_len = self.data.len(); - if data_len > MAX_TRANSFER_SIZE { - return Err(PldmCodecError::BufferTooShort); - } - buffer[offset..offset + data_len].copy_from_slice(self.data); - Ok(bytes + data_len) - } - - // 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) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_request_firmware_data_request() { - 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() { - 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()); - } -} diff --git a/src/message/firmware_update/request_update.rs b/src/message/firmware_update/request_update.rs deleted file mode 100644 index 9c8b154..0000000 --- a/src/message/firmware_update/request_update.rs +++ /dev/null @@ -1,268 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::codec::{PldmCodec, PldmCodecError}; -use crate::protocol::base::{ - InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, -}; -use crate::protocol::firmware_update::{ - FwUpdateCmd, PldmFirmwareString, PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN, -}; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -#[derive(Debug, Copy, Clone, PartialEq)] -#[repr(C)] -pub struct RequestUpdateRequest { - pub fixed: RequestUpdateRequestFixed, - pub comp_image_set_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], -} - -#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct RequestUpdateRequestFixed { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub max_transfer_size: u32, - pub num_of_comp: u16, - pub max_outstanding_transfer_req: u8, - pub pkg_data_len: u16, - pub comp_image_set_ver_str_type: u8, - pub comp_image_set_ver_str_len: u8, -} - -impl RequestUpdateRequest { - pub fn new( - instance_id: InstanceId, - msg_type: PldmMsgType, - max_transfer_size: u32, - num_of_comp: u16, - max_outstanding_transfer_req: u8, - pkg_data_len: u16, - comp_image_set_version_string: &PldmFirmwareString, - ) -> Self { - RequestUpdateRequest { - fixed: RequestUpdateRequestFixed { - hdr: PldmMsgHeader::new( - instance_id, - msg_type, - PldmSupportedType::FwUpdate, - FwUpdateCmd::RequestUpdate as u8, - ), - max_transfer_size, - num_of_comp, - max_outstanding_transfer_req, - pkg_data_len, - comp_image_set_ver_str_type: comp_image_set_version_string.str_type, - comp_image_set_ver_str_len: comp_image_set_version_string.str_len, - }, - comp_image_set_ver_str: { - let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - let len = comp_image_set_version_string.str_data.len(); - arr[..len].copy_from_slice(&comp_image_set_version_string.str_data); - arr - }, - } - } - - pub fn get_comp_image_set_ver_str(&self) -> PldmFirmwareString { - PldmFirmwareString { - str_type: self.fixed.comp_image_set_ver_str_type, - str_len: self.fixed.comp_image_set_ver_str_len, - str_data: { - let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - arr.copy_from_slice( - &self.comp_image_set_ver_str[..self.fixed.comp_image_set_ver_str_len as usize], - ); - arr - }, - } - } - - pub fn codec_size_in_bytes(&self) -> usize { - let mut bytes = core::mem::size_of::(); - bytes += self.fixed.comp_image_set_ver_str_len as usize; - bytes - } -} - -impl PldmCodec for RequestUpdateRequest { - fn encode(&self, buffer: &mut [u8]) -> Result { - if buffer.len() < self.codec_size_in_bytes() { - return Err(PldmCodecError::BufferTooShort); - } - - let mut offset = 0; - self.fixed - .write_to( - &mut buffer[offset..offset + core::mem::size_of::()], - ) - .unwrap(); - offset += core::mem::size_of::(); - - let str_len = self.fixed.comp_image_set_ver_str_len as usize; - buffer[offset..offset + str_len].copy_from_slice(&self.comp_image_set_ver_str[..str_len]); - Ok(offset + str_len) - } - - fn decode(buffer: &[u8]) -> Result { - let mut offset = 0; - let fixed = RequestUpdateRequestFixed::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += core::mem::size_of::(); - - let str_len = fixed.comp_image_set_ver_str_len as usize; - let mut comp_image_set_ver_str = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - comp_image_set_ver_str[..str_len].copy_from_slice( - &buffer - .get(offset..offset + str_len) - .ok_or(PldmCodecError::BufferTooShort)?[..str_len], - ); - - Ok(RequestUpdateRequest { - fixed, - comp_image_set_ver_str, - }) - } -} - -#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, PartialEq, Default)] -#[repr(C, packed)] -pub struct RequestUpdateResponseFixed { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, - pub fd_meta_data_len: u16, - pub fd_will_send_pkg_data_cmd: u8, -} - -#[derive(Debug, Clone, PartialEq, Default)] -pub struct RequestUpdateResponse { - pub fixed: RequestUpdateResponseFixed, - // This field is only present if FDWillSendGetPackageDataCommand is set to 0x02. - pub get_pkg_data_max_transfer_size: Option, -} - -impl RequestUpdateResponse { - pub fn new( - instance_id: InstanceId, - completion_code: u8, - fd_meta_data_len: u16, - fd_will_send_pkg_data_cmd: u8, - get_pkg_data_max_transfer_size: Option, - ) -> RequestUpdateResponse { - RequestUpdateResponse { - fixed: RequestUpdateResponseFixed { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::FwUpdate, - FwUpdateCmd::RequestUpdate as u8, - ), - completion_code, - fd_meta_data_len, - fd_will_send_pkg_data_cmd, - }, - get_pkg_data_max_transfer_size, - } - } - - pub fn codec_size_in_bytes(&self) -> usize { - let mut bytes = core::mem::size_of::(); - if self.fixed.fd_will_send_pkg_data_cmd == 0x02 { - bytes += core::mem::size_of::(); - } - bytes - } -} - -impl PldmCodec for RequestUpdateResponse { - fn encode(&self, buffer: &mut [u8]) -> Result { - if buffer.len() < self.codec_size_in_bytes() { - return Err(PldmCodecError::BufferTooShort); - } - - let mut offset = 0; - self.fixed - .write_to( - &mut buffer[offset..offset + core::mem::size_of::()], - ) - .unwrap(); - offset += core::mem::size_of::(); - - if let Some(size) = self.get_pkg_data_max_transfer_size { - size.write_to(&mut buffer[offset..offset + core::mem::size_of::()]) - .unwrap(); - offset += core::mem::size_of::(); - } - - Ok(offset) - } - - fn decode(buffer: &[u8]) -> Result { - let mut offset = 0; - let fixed = RequestUpdateResponseFixed::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += core::mem::size_of::(); - - let get_pkg_data_max_transfer_size = if fixed.fd_will_send_pkg_data_cmd == 0x02 { - Some( - u32::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(), - ) - } else { - None - }; - - Ok(RequestUpdateResponse { - fixed, - get_pkg_data_max_transfer_size, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::protocol::firmware_update::PldmFirmwareString; - - #[test] - fn test_request_update_request() { - let request = RequestUpdateRequest::new( - 0, - PldmMsgType::Request, - 512, - 2, - 1, - 256, - &PldmFirmwareString::new("ASCII", "mcu-1.0.0").unwrap(), - ); - - let mut buffer = [0u8; 512]; - let encoded_size = request.encode(&mut buffer).unwrap(); - assert_eq!(encoded_size, request.codec_size_in_bytes()); - - let decoded_request = RequestUpdateRequest::decode(&buffer[..encoded_size]).unwrap(); - assert_eq!(request, decoded_request); - } - - #[test] - fn test_request_update_response() { - let response = RequestUpdateResponse::new(1, 0, 128, 0x02, Some(2048)); - - let mut buffer = [0u8; 512]; - let encoded_size = response.encode(&mut buffer).unwrap(); - assert_eq!(encoded_size, response.codec_size_in_bytes()); - - let decoded_response = RequestUpdateResponse::decode(&buffer[..encoded_size]).unwrap(); - assert_eq!(response, decoded_response); - } -} diff --git a/src/message/firmware_update/transfer_complete.rs b/src/message/firmware_update/transfer_complete.rs deleted file mode 100644 index fae32c8..0000000 --- a/src/message/firmware_update/transfer_complete.rs +++ /dev/null @@ -1,124 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::error::PldmError; -use crate::protocol::base::{ - InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, -}; -use crate::protocol::firmware_update::FwUpdateCmd; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum TransferResult { - TransferSuccess = 0x00, - TransferErrorImageCorrupt = 0x01, - TransferErrorVersionMismatch = 0x02, - FdAbortedTransfer = 0x03, - TransferTimeOut = 0x09, - #[default] - TransferGenericError = 0x0a, - FdAbortedTransferLowPowerState = 0x0b, - FdAbortedTransferResetNeeded = 0x0c, - FdAbortedTransferStorageIssue = 0x0d, - FdAbortedTransferInvalidComponentOpaqueData = 0x0e, - FdAbortedTransferDownstreamDeviceIssue = 0x0f, - FdAbortedTransferSecurityRevisionError = 0x10, - VendorDefined, // 0x70..=0x8f -} - -impl TryFrom for TransferResult { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0x00 => Ok(TransferResult::TransferSuccess), - 0x01 => Ok(TransferResult::TransferErrorImageCorrupt), - 0x02 => Ok(TransferResult::TransferErrorVersionMismatch), - 0x03 => Ok(TransferResult::FdAbortedTransfer), - 0x09 => Ok(TransferResult::TransferTimeOut), - 0x0a => Ok(TransferResult::TransferGenericError), - 0x0b => Ok(TransferResult::FdAbortedTransferLowPowerState), - 0x0c => Ok(TransferResult::FdAbortedTransferResetNeeded), - 0x0d => Ok(TransferResult::FdAbortedTransferStorageIssue), - 0x0e => Ok(TransferResult::FdAbortedTransferInvalidComponentOpaqueData), - 0x0f => Ok(TransferResult::FdAbortedTransferDownstreamDeviceIssue), - 0x10 => Ok(TransferResult::FdAbortedTransferSecurityRevisionError), - 0x70..=0x8f => Ok(TransferResult::VendorDefined), - _ => Err(PldmError::InvalidTransferResult), - } - } -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct TransferCompleteRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub tranfer_result: u8, -} - -impl TransferCompleteRequest { - pub fn new( - instance_id: InstanceId, - msg_type: PldmMsgType, - tranfer_result: TransferResult, - ) -> Self { - TransferCompleteRequest { - hdr: PldmMsgHeader::new( - instance_id, - msg_type, - PldmSupportedType::FwUpdate, - FwUpdateCmd::TransferComplete as u8, - ), - tranfer_result: tranfer_result as u8, - } - } -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct TransferCompleteResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, -} - -impl TransferCompleteResponse { - pub fn new(instance_id: InstanceId, completion_code: u8) -> TransferCompleteResponse { - TransferCompleteResponse { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::FwUpdate, - FwUpdateCmd::TransferComplete as u8, - ), - completion_code, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::codec::PldmCodec; - - #[test] - fn test_transfer_complete_request() { - let request = TransferCompleteRequest::new( - 0x01, - PldmMsgType::Request, - TransferResult::TransferSuccess, - ); - let mut buffer = [0u8; core::mem::size_of::()]; - request.encode(&mut buffer).unwrap(); - let decoded_request = TransferCompleteRequest::decode(&buffer).unwrap(); - assert_eq!(decoded_request, request); - } - - #[test] - fn test_transfer_complete_response() { - let response = TransferCompleteResponse::new(0x01, 0x00); - let mut buffer = [0u8; core::mem::size_of::()]; - response.encode(&mut buffer).unwrap(); - let decoded_response = TransferCompleteResponse::decode(&buffer).unwrap(); - assert_eq!(decoded_response, response); - } -} diff --git a/src/message/firmware_update/update_component.rs b/src/message/firmware_update/update_component.rs deleted file mode 100644 index 30f5384..0000000 --- a/src/message/firmware_update/update_component.rs +++ /dev/null @@ -1,338 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::codec::{PldmCodec, PldmCodecError}; -use crate::protocol::base::{ - InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, -}; -use crate::protocol::firmware_update::{ - ComponentClassification, ComponentCompatibilityResponse, ComponentCompatibilityResponseCode, - FwUpdateCmd, PldmFirmwareString, UpdateOptionFlags, PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN, -}; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct UpdateComponentRequestFixed { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub comp_classification: u16, - pub comp_identifier: u16, - pub comp_classification_index: u8, - pub comp_comparison_stamp: u32, - pub comp_image_size: u32, - pub update_option_flags: u32, - pub comp_ver_str_type: u8, - pub comp_ver_str_len: u8, -} - -#[derive(Debug, Clone, PartialEq)] -#[repr(C)] -pub struct UpdateComponentRequest { - pub fixed: UpdateComponentRequestFixed, - pub comp_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], -} - -#[allow(clippy::too_many_arguments)] -impl UpdateComponentRequest { - pub fn new( - instance_id: InstanceId, - msg_type: PldmMsgType, - comp_classification: ComponentClassification, - comp_identifier: u16, - comp_classification_index: u8, - comp_comparison_stamp: u32, - comp_image_size: u32, - update_option_flags: UpdateOptionFlags, - comp_version_string: &PldmFirmwareString, - ) -> UpdateComponentRequest { - UpdateComponentRequest { - fixed: UpdateComponentRequestFixed { - hdr: PldmMsgHeader::new( - instance_id, - msg_type, - PldmSupportedType::FwUpdate, - FwUpdateCmd::UpdateComponent as u8, - ), - comp_classification: comp_classification as u16, - comp_identifier, - comp_classification_index, - comp_comparison_stamp, - comp_image_size, - update_option_flags: update_option_flags.0, - comp_ver_str_type: comp_version_string.str_type, - comp_ver_str_len: comp_version_string.str_len, - }, - comp_ver_str: { - let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - let len = comp_version_string.str_data.len(); - arr[..len].copy_from_slice(&comp_version_string.str_data); - arr - }, - } - } - - fn codec_size_in_bytes(&self) -> usize { - let mut bytes = 0; - bytes += core::mem::size_of::(); - bytes += self.fixed.comp_ver_str_len as usize; - bytes - } -} - -impl PldmCodec for UpdateComponentRequest { - fn encode(&self, buffer: &mut [u8]) -> Result { - if buffer.len() < self.codec_size_in_bytes() { - return Err(PldmCodecError::BufferTooShort); - } - - let mut offset = 0; - let bytes = core::mem::size_of::(); - self.fixed - .write_to(&mut buffer[offset..offset + bytes]) - .unwrap(); - offset += bytes; - - let str_len = self.fixed.comp_ver_str_len as usize; - buffer[offset..offset + str_len].copy_from_slice(&self.comp_ver_str[..str_len]); - Ok(offset + str_len) - } - - fn decode(buffer: &[u8]) -> Result { - let mut offset = 0; - let bytes = core::mem::size_of::(); - let fixed = UpdateComponentRequestFixed::read_from_bytes( - buffer - .get(offset..offset + bytes) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += bytes; - - let comp_ver_str = { - let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - let str_len = fixed.comp_ver_str_len as usize; - arr[..str_len].copy_from_slice(&buffer[offset..offset + str_len]); - arr - }; - Ok(UpdateComponentRequest { - fixed, - comp_ver_str, - }) - } -} - -#[derive(Debug, Clone, PartialEq)] -#[repr(C)] -pub struct UpdateComponentResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, - pub comp_compatibility_resp: u8, - pub comp_compatibility_resp_code: u8, - pub update_option_flags_enabled: u32, - pub time_before_req_fw_data: u16, - pub get_comp_opaque_data_max_transfer_size: Option, -} - -impl UpdateComponentResponse { - pub fn new( - instance_id: InstanceId, - completion_code: u8, - comp_compatibility_resp: ComponentCompatibilityResponse, - comp_compatibility_resp_code: ComponentCompatibilityResponseCode, - update_option_flags_enabled: UpdateOptionFlags, - time_before_req_fw_data: u16, - get_comp_opaque_data_max_transfer_size: Option, - ) -> UpdateComponentResponse { - UpdateComponentResponse { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::FwUpdate, - FwUpdateCmd::UpdateComponent as u8, - ), - completion_code, - comp_compatibility_resp: comp_compatibility_resp as u8, - comp_compatibility_resp_code: comp_compatibility_resp_code as u8, - update_option_flags_enabled: update_option_flags_enabled.0, - time_before_req_fw_data, - get_comp_opaque_data_max_transfer_size, - } - } - - fn codec_size_in_bytes(&self) -> usize { - let mut bytes = PLDM_MSG_HEADER_LEN - + core::mem::size_of::() * 3 - + core::mem::size_of::() - + core::mem::size_of::(); - if self.get_comp_opaque_data_max_transfer_size.is_some() { - bytes += core::mem::size_of::(); - } - bytes - } -} - -impl PldmCodec for UpdateComponentResponse { - fn encode(&self, buffer: &mut [u8]) -> Result { - if buffer.len() < self.codec_size_in_bytes() { - return Err(PldmCodecError::BufferTooShort); - } - - let mut offset = 0; - self.hdr - .write_to(&mut buffer[offset..offset + PLDM_MSG_HEADER_LEN]) - .unwrap(); - offset += PLDM_MSG_HEADER_LEN; - - self.completion_code - .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) - .unwrap(); - offset += core::mem::size_of::(); - - self.comp_compatibility_resp - .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) - .unwrap(); - offset += core::mem::size_of::(); - - self.comp_compatibility_resp_code - .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) - .unwrap(); - offset += core::mem::size_of::(); - - self.update_option_flags_enabled - .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) - .unwrap(); - offset += core::mem::size_of::(); - - self.time_before_req_fw_data - .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) - .unwrap(); - offset += core::mem::size_of::(); - - if let Some(size) = self.get_comp_opaque_data_max_transfer_size { - size.write_to(&mut buffer[offset..offset + core::mem::size_of::()]) - .unwrap(); - offset += core::mem::size_of::(); - } - - Ok(offset) - } - - fn decode(buffer: &[u8]) -> Result { - let mut offset = 0; - let hdr = PldmMsgHeader::read_from_bytes( - buffer - .get(offset..offset + PLDM_MSG_HEADER_LEN) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += PLDM_MSG_HEADER_LEN; - - let completion_code = u8::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += core::mem::size_of::(); - - let comp_compatibility_resp = u8::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += core::mem::size_of::(); - - let comp_compatibility_resp_code = u8::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += core::mem::size_of::(); - - let update_option_flags_enabled = u32::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += core::mem::size_of::(); - - let time_before_req_fw_data = u16::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += core::mem::size_of::(); - - let update_option_flags = UpdateOptionFlags(update_option_flags_enabled); - - let get_comp_opaque_data_max_transfer_size = if update_option_flags.component_opaque_data() - { - Some( - u32::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(), - ) - } else { - None - }; - - Ok(UpdateComponentResponse { - hdr, - completion_code, - comp_compatibility_resp, - comp_compatibility_resp_code, - update_option_flags_enabled, - time_before_req_fw_data, - get_comp_opaque_data_max_transfer_size, - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_update_component_request() { - let request = UpdateComponentRequest::new( - 0, - PldmMsgType::Request, - ComponentClassification::Firmware, - 0x0001, - 0x01, - 0x00000001, - 0x00000001, - UpdateOptionFlags(0x00000002), - &PldmFirmwareString::new("UTF-8", "mcu-fw-1.2.0").unwrap(), - ); - let mut buffer = [0u8; 512]; - let bytes = request.encode(&mut buffer).unwrap(); - assert_eq!(bytes, request.codec_size_in_bytes()); - let decoded_request = UpdateComponentRequest::decode(&buffer[..bytes]).unwrap(); - assert_eq!(request, decoded_request); - } - - #[test] - fn test_update_component_response() { - let response = UpdateComponentResponse::new( - 0, - 0x00, - ComponentCompatibilityResponse::CompCanBeUpdated, - ComponentCompatibilityResponseCode::NoResponseCode, - UpdateOptionFlags(0x00000002), - 0x0001, - Some(0x00000100), - ); - let mut buffer = [0u8; 512]; - let bytes = response.encode(&mut buffer).unwrap(); - assert_eq!(bytes, response.codec_size_in_bytes()); - let decoded_response = UpdateComponentResponse::decode(&buffer[..bytes]).unwrap(); - assert_eq!(response, decoded_response); - } -} diff --git a/src/message/firmware_update/verify_complete.rs b/src/message/firmware_update/verify_complete.rs deleted file mode 100644 index 1e7b0cb..0000000 --- a/src/message/firmware_update/verify_complete.rs +++ /dev/null @@ -1,111 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::error::PldmError; -use crate::protocol::base::{ - InstanceId, PldmMsgHeader, PldmMsgType, PldmSupportedType, PLDM_MSG_HEADER_LEN, -}; -use crate::protocol::firmware_update::FwUpdateCmd; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum VerifyResult { - VerifySuccess = 0x00, - VerifyErrorVerificationFailure = 0x01, - VerifyErrorVersionMismatch = 0x02, - VerifyFailedFdSecurityChecks = 0x03, - VerifyErrorImageIncomplete = 0x04, - VerifyTimeOut = 0x09, - #[default] - VerifyGenericError = 0x0a, - VendorDefined, -} - -impl TryFrom for VerifyResult { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0x00 => Ok(VerifyResult::VerifySuccess), - 0x01 => Ok(VerifyResult::VerifyErrorVerificationFailure), - 0x02 => Ok(VerifyResult::VerifyErrorVersionMismatch), - 0x03 => Ok(VerifyResult::VerifyFailedFdSecurityChecks), - 0x04 => Ok(VerifyResult::VerifyErrorImageIncomplete), - 0x09 => Ok(VerifyResult::VerifyTimeOut), - 0x0a => Ok(VerifyResult::VerifyGenericError), - 0x90..=0xaf => Ok(VerifyResult::VendorDefined), - _ => Err(PldmError::InvalidVerifyResult), - } - } -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct VerifyCompleteRequest { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub verify_result: u8, -} - -impl VerifyCompleteRequest { - pub fn new( - instance_id: InstanceId, - msg_type: PldmMsgType, - verify_result: VerifyResult, - ) -> Self { - VerifyCompleteRequest { - hdr: PldmMsgHeader::new( - instance_id, - msg_type, - PldmSupportedType::FwUpdate, - FwUpdateCmd::VerifyComplete as u8, - ), - verify_result: verify_result as u8, - } - } -} - -#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct VerifyCompleteResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, -} - -impl VerifyCompleteResponse { - pub fn new(instance_id: InstanceId, completion_code: u8) -> VerifyCompleteResponse { - VerifyCompleteResponse { - hdr: PldmMsgHeader::new( - instance_id, - PldmMsgType::Response, - PldmSupportedType::FwUpdate, - FwUpdateCmd::VerifyComplete as u8, - ), - completion_code, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::codec::PldmCodec; - - #[test] - fn test_verify_complete_request() { - let request = - VerifyCompleteRequest::new(0x01, PldmMsgType::Request, VerifyResult::VerifySuccess); - let mut buffer = [0u8; core::mem::size_of::()]; - request.encode(&mut buffer).unwrap(); - let decoded_request = VerifyCompleteRequest::decode(&buffer).unwrap(); - assert_eq!(request, decoded_request); - } - - #[test] - fn test_verify_complete_response() { - let response = VerifyCompleteResponse::new(0x01, 0x00); - let mut buffer = [0u8; core::mem::size_of::()]; - response.encode(&mut buffer).unwrap(); - let decoded_response = VerifyCompleteResponse::decode(&buffer).unwrap(); - assert_eq!(response, decoded_response); - } -} diff --git a/src/message/mod.rs b/src/message/mod.rs deleted file mode 100644 index 0fbfacb..0000000 --- a/src/message/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Licensed under the Apache-2.0 license - -pub mod control; -pub mod firmware_update; diff --git a/src/protocol/base.rs b/src/protocol/base.rs deleted file mode 100644 index ccb757a..0000000 --- a/src/protocol/base.rs +++ /dev/null @@ -1,302 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::error::PldmError; -use bitfield::bitfield; -use core::convert::TryFrom; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -pub const PLDM_MSG_HEADER_LEN: usize = 3; -pub const PLDM_FAILURE_RESP_LEN: usize = 4; -pub type InstanceId = u8; - -#[derive(Debug, Clone, Copy, PartialEq)] -#[repr(u8)] -pub enum PldmSupportedType { - Base = 0x00, - Platform = 0x02, - Bios = 0x03, - Fru = 0x04, - FwUpdate = 0x05, - Oem = 0x3F, -} - -impl TryFrom for PldmSupportedType { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0x00 => Ok(PldmSupportedType::Base), - 0x02 => Ok(PldmSupportedType::Platform), - 0x03 => Ok(PldmSupportedType::Bios), - 0x04 => Ok(PldmSupportedType::Fru), - 0x05 => Ok(PldmSupportedType::FwUpdate), - 0x3F => Ok(PldmSupportedType::Oem), - _ => Err(PldmError::UnsupportedPldmType), - } - } -} - -#[derive(Debug, Clone, Copy)] -#[repr(u8)] -pub enum PldmControlCmd { - SetTid = 0x1, - GetTid = 0x2, - GetPldmVersion = 0x3, - GetPldmTypes = 0x4, - GetPldmCommands = 0x5, -} - -impl TryFrom for PldmControlCmd { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0x1 => Ok(PldmControlCmd::SetTid), - 0x2 => Ok(PldmControlCmd::GetTid), - 0x3 => Ok(PldmControlCmd::GetPldmVersion), - 0x4 => Ok(PldmControlCmd::GetPldmTypes), - 0x5 => Ok(PldmControlCmd::GetPldmCommands), - _ => Err(PldmError::UnsupportedCmd), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -#[repr(u8)] -pub enum PldmMsgType { - Response = 0x00, - Reserved = 0x01, - Request = 0x02, - AsyncRequestNotify = 0x03, -} - -impl TryFrom for PldmMsgType { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0x00 => Ok(PldmMsgType::Response), - 0x01 => Ok(PldmMsgType::Reserved), - 0x02 => Ok(PldmMsgType::Request), - 0x03 => Ok(PldmMsgType::AsyncRequestNotify), - _ => Err(PldmError::InvalidMsgType), - } - } -} - -#[derive(Debug, Clone, Copy)] -#[repr(u8)] -pub enum PldmHeaderVersion { - Version0 = 0x00, -} - -#[derive(Debug, Clone, Copy)] -#[repr(u8)] -pub enum PldmBaseCompletionCode { - Success = 0x00, - Error = 0x01, - InvalidData = 0x02, - InvalidLength = 0x03, - NotReady = 0x04, - UnsupportedPldmCmd = 0x05, - InvalidPldmType = 0x20, -} - -impl TryFrom for PldmBaseCompletionCode { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0x00 => Ok(PldmBaseCompletionCode::Success), - 0x01 => Ok(PldmBaseCompletionCode::Error), - 0x02 => Ok(PldmBaseCompletionCode::InvalidData), - 0x03 => Ok(PldmBaseCompletionCode::InvalidLength), - 0x04 => Ok(PldmBaseCompletionCode::NotReady), - 0x05 => Ok(PldmBaseCompletionCode::UnsupportedPldmCmd), - 0x20 => Ok(PldmBaseCompletionCode::InvalidPldmType), - _ => Err(PldmError::InvalidCompletionCode), - } - } -} - -#[derive(Debug, Clone, Copy)] -#[repr(u8)] -pub enum PldmControlCompletionCode { - InvalidDataTransferHandle = 0x80, - InvalidTransferOperationFlag = 0x81, - InvalidPldmTypeInRequestData = 0x83, - InvalidPldmVersionInRequestData = 0x84, -} - -impl TryFrom for PldmControlCompletionCode { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0x80 => Ok(PldmControlCompletionCode::InvalidDataTransferHandle), - 0x81 => Ok(PldmControlCompletionCode::InvalidTransferOperationFlag), - 0x83 => Ok(PldmControlCompletionCode::InvalidPldmTypeInRequestData), - 0x84 => Ok(PldmControlCompletionCode::InvalidPldmVersionInRequestData), - _ => Err(PldmError::InvalidCompletionCode), - } - } -} - -#[derive(Debug, Clone, Copy)] -#[repr(u8)] -pub enum TransferOperationFlag { - GetNextPart = 0, - GetFirstPart = 1, -} - -impl TryFrom for TransferOperationFlag { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(TransferOperationFlag::GetNextPart), - 1 => Ok(TransferOperationFlag::GetFirstPart), - _ => Err(PldmError::InvalidTransferOpFlag), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -#[repr(u8)] -pub enum TransferRespFlag { - Start = 0x01, - Middle = 0x02, - End = 0x04, - StartAndEnd = 0x05, -} - -impl TryFrom for TransferRespFlag { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0x01 => Ok(TransferRespFlag::Start), - 0x02 => Ok(TransferRespFlag::Middle), - 0x04 => Ok(TransferRespFlag::End), - 0x05 => Ok(TransferRespFlag::StartAndEnd), - _ => Err(PldmError::InvalidTransferRespFlag), - } - } -} - -bitfield! { - #[repr(C)] - #[derive(Copy, Clone, FromBytes, IntoBytes, Immutable, PartialEq, Default)] - pub struct PldmMsgHeader([u8]); - impl Debug; - pub u8, instance_id, set_instance_id: 4, 0; - pub u8, reserved, _: 5, 5; - pub u8, datagram, set_datagram: 6, 6; - pub u8, rq, set_rq: 7, 7; - pub u8, pldm_type, set_pldm_type: 13, 8; - pub u8, hdr_ver, set_hdr_ver: 15, 14; - pub u8, cmd_code, set_command_code: 23, 16; -} - -impl PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]> { - const DATAGRAM_MASK: u8 = 0x01; - const REQUEST_MASK: u8 = 0x01 << 1; - - pub fn new( - instance_id: InstanceId, - message_type: PldmMsgType, - pldm_type: PldmSupportedType, - cmd_code: u8, - ) -> Self { - let mut header = PldmMsgHeader([0; PLDM_MSG_HEADER_LEN]); - header.set_instance_id(instance_id); - header.set_datagram(message_type as u8 & Self::DATAGRAM_MASK); - header.set_rq((message_type as u8 & Self::REQUEST_MASK) >> 1); - header.set_pldm_type(pldm_type as u8); - header.set_hdr_ver(PldmHeaderVersion::Version0 as u8); - header.set_command_code(cmd_code); - header - } - - pub fn is_request(&self) -> bool { - self.rq() == (PldmMsgType::Request as u8 >> 0x01) - } - - pub fn is_hdr_ver_valid(&self) -> bool { - self.hdr_ver() == PldmHeaderVersion::Version0 as u8 - } - - // switch the message type to response - pub fn into_response(&self) -> Self { - let mut header = *self; - header.set_rq(PldmMsgType::Response as u8); - header - } -} - -#[derive(Debug, FromBytes, IntoBytes, Immutable, PartialEq)] -#[repr(C, packed)] -pub struct PldmFailureResponse { - pub hdr: PldmMsgHeader<[u8; PLDM_MSG_HEADER_LEN]>, - pub completion_code: u8, -} - -impl PldmFailureResponse { - pub fn new( - instance_id: InstanceId, - pldm_type: PldmSupportedType, - cmd_code: u8, - completion_code: u8, - ) -> Self { - let hdr = PldmMsgHeader::new(instance_id, PldmMsgType::Response, pldm_type, cmd_code); - PldmFailureResponse { - hdr, - completion_code, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::codec::PldmCodec; - - #[test] - fn test_pldm_msg_header() { - let header = PldmMsgHeader::new( - 0x01, - PldmMsgType::Request, - PldmSupportedType::Base, - PldmControlCmd::GetTid as u8, - ); - assert_eq!(header.0, [0x81, 0x00, 0x02]); - assert!(header.is_request()); - let response = header.into_response(); - assert_eq!(response.0, [0x01, 0x00, 0x02]); - assert_eq!(response.rq(), PldmMsgType::Response as u8); - - let mut buffer = [0; PLDM_MSG_HEADER_LEN]; - let size = header.encode(&mut buffer).unwrap(); - assert_eq!(size, PLDM_MSG_HEADER_LEN); - - let decoded_header = PldmMsgHeader::decode(&buffer).unwrap(); - assert_eq!(header, decoded_header); - } - - #[test] - fn test_pldm_failure_resp() { - let resp = PldmFailureResponse::new( - 0x01, - PldmSupportedType::Base, - PldmControlCmd::GetTid as u8, - PldmBaseCompletionCode::Success as u8, - ); - - let mut buffer = [0; PLDM_FAILURE_RESP_LEN]; - let size = resp.encode(&mut buffer).unwrap(); - assert_eq!(size, PLDM_FAILURE_RESP_LEN); - - let decoded_resp = PldmFailureResponse::decode(&buffer).unwrap(); - assert_eq!(resp, decoded_resp); - } -} diff --git a/src/protocol/firmware_update.rs b/src/protocol/firmware_update.rs deleted file mode 100644 index f959f50..0000000 --- a/src/protocol/firmware_update.rs +++ /dev/null @@ -1,884 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::codec::{PldmCodec, PldmCodecError}; -use crate::error::PldmError; -use bitfield::bitfield; -use core::convert::TryFrom; -use core::fmt; -use zerocopy::{FromBytes, Immutable, IntoBytes}; - -pub const PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN: usize = 8; -pub const PLDM_FWUP_BASELINE_TRANSFER_SIZE: usize = 32; -pub const PLDM_FWUP_MAX_PADDING_SIZE: usize = PLDM_FWUP_BASELINE_TRANSFER_SIZE; -pub const PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN: usize = 32; -pub const DESCRIPTOR_DATA_MAX_LEN: usize = 64; // Arbitrary limit for static storage -pub const MAX_COMPONENT_COUNT: usize = 8; // Arbitrary limit, change as needed -pub const MAX_DESCRIPTORS_COUNT: usize = 4; // Arbitrary limit, change as needed -pub type PldmFdTime = u64; // Monotonic timestamp in milliseconds - -#[repr(u8)] -pub enum FwUpdateCmd { - QueryDeviceIdentifiers = 0x01, - GetFirmwareParameters = 0x02, - RequestUpdate = 0x10, - PassComponentTable = 0x13, - UpdateComponent = 0x14, - RequestFirmwareData = 0x15, - TransferComplete = 0x16, - VerifyComplete = 0x17, - ApplyComplete = 0x18, - ActivateFirmware = 0x1A, - GetStatus = 0x1B, - CancelUpdateComponent = 0x1C, - CancelUpdate = 0x1D, -} - -impl TryFrom for FwUpdateCmd { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0x01 => Ok(FwUpdateCmd::QueryDeviceIdentifiers), - 0x02 => Ok(FwUpdateCmd::GetFirmwareParameters), - 0x10 => Ok(FwUpdateCmd::RequestUpdate), - 0x13 => Ok(FwUpdateCmd::PassComponentTable), - 0x14 => Ok(FwUpdateCmd::UpdateComponent), - 0x15 => Ok(FwUpdateCmd::RequestFirmwareData), - 0x16 => Ok(FwUpdateCmd::TransferComplete), - 0x17 => Ok(FwUpdateCmd::VerifyComplete), - 0x18 => Ok(FwUpdateCmd::ApplyComplete), - 0x1A => Ok(FwUpdateCmd::ActivateFirmware), - 0x1B => Ok(FwUpdateCmd::GetStatus), - 0x1C => Ok(FwUpdateCmd::CancelUpdateComponent), - 0x1D => Ok(FwUpdateCmd::CancelUpdate), - _ => Err(PldmError::UnsupportedCmd), - } - } -} - -#[repr(u8)] -pub enum FwUpdateCompletionCode { - NotInUpdateMode = 0x80, - AlreadyInUpdateMode = 0x81, - DataOutOfRange = 0x82, - InvalidTransferLength = 0x83, - InvalidStateForCommand = 0x84, - IncompleteUpdate = 0x85, - BusyInBackground = 0x86, - CancelPending = 0x87, - CommandNotExpected = 0x88, - RetryRequestFwData = 0x89, - UnableToInitiateUpdate = 0x8A, - ActivationNotRequired = 0x8B, - SelfContainedActivationNotPermitted = 0x8C, - NoDeviceMetadata = 0x8D, - RetryRequestUpdate = 0x8E, - NoPackageData = 0x8F, - InvalidTransferHandle = 0x90, - InvalidTransferOperationFlag = 0x91, - ActivatePendingImageNotPermitted = 0x92, - PackageDataError = 0x93, -} - -impl TryFrom for FwUpdateCompletionCode { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0x80 => Ok(FwUpdateCompletionCode::NotInUpdateMode), - 0x81 => Ok(FwUpdateCompletionCode::AlreadyInUpdateMode), - 0x82 => Ok(FwUpdateCompletionCode::DataOutOfRange), - 0x83 => Ok(FwUpdateCompletionCode::InvalidTransferLength), - 0x84 => Ok(FwUpdateCompletionCode::InvalidStateForCommand), - 0x85 => Ok(FwUpdateCompletionCode::IncompleteUpdate), - 0x86 => Ok(FwUpdateCompletionCode::BusyInBackground), - 0x87 => Ok(FwUpdateCompletionCode::CancelPending), - 0x88 => Ok(FwUpdateCompletionCode::CommandNotExpected), - 0x89 => Ok(FwUpdateCompletionCode::RetryRequestFwData), - 0x8A => Ok(FwUpdateCompletionCode::UnableToInitiateUpdate), - 0x8B => Ok(FwUpdateCompletionCode::ActivationNotRequired), - 0x8C => Ok(FwUpdateCompletionCode::SelfContainedActivationNotPermitted), - 0x8D => Ok(FwUpdateCompletionCode::NoDeviceMetadata), - 0x8E => Ok(FwUpdateCompletionCode::RetryRequestUpdate), - 0x8F => Ok(FwUpdateCompletionCode::NoPackageData), - 0x90 => Ok(FwUpdateCompletionCode::InvalidTransferHandle), - 0x91 => Ok(FwUpdateCompletionCode::InvalidTransferOperationFlag), - 0x92 => Ok(FwUpdateCompletionCode::ActivatePendingImageNotPermitted), - 0x93 => Ok(FwUpdateCompletionCode::PackageDataError), - _ => Err(PldmError::InvalidCompletionCode), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -#[repr(u8)] -pub enum FirmwareDeviceState { - Idle = 0, - LearnComponents = 1, - ReadyXfer = 2, - Download = 3, - Verify = 4, - Apply = 5, - Activate = 6, -} - -impl TryFrom for FirmwareDeviceState { - type Error = PldmError; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(FirmwareDeviceState::Idle), - 1 => Ok(FirmwareDeviceState::LearnComponents), - 2 => Ok(FirmwareDeviceState::ReadyXfer), - 3 => Ok(FirmwareDeviceState::Download), - 4 => Ok(FirmwareDeviceState::Verify), - 5 => Ok(FirmwareDeviceState::Apply), - 6 => Ok(FirmwareDeviceState::Activate), - _ => Err(PldmError::InvalidFdState), - } - } -} - -bitfield! { - #[derive(Clone, Copy, PartialEq, FromBytes, IntoBytes)] - pub struct UpdateOptionFlags(u32); - impl Debug; - pub u32, reserved, _: 31, 3; - pub u32, svn_delayed_update, set_svn_delayed_update: 2; - pub u32, component_opaque_data, set_component_opaque_data: 1; - pub u32, request_force_update, set_request_force_update: 0; -} - -#[repr(u8)] -pub enum VersionStringType { - Unspecified = 0, - Ascii = 1, - Utf8 = 2, - Utf16 = 3, - Utf16Le = 4, - Utf16Be = 5, -} - -impl VersionStringType { - fn as_string(&self) -> &str { - match *self { - VersionStringType::Ascii => "ASCII", - VersionStringType::Utf8 => "UTF-8", - VersionStringType::Utf16 => "UTF-16", - VersionStringType::Utf16Le => "UTF-16LE", - VersionStringType::Utf16Be => "UTF-16BE", - VersionStringType::Unspecified => "UNSPECIFIED", - } - } -} - -impl fmt::Display for VersionStringType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.as_string()) - } -} -impl TryFrom<&str> for VersionStringType { - type Error = PldmError; - - fn try_from(input: &str) -> Result { - match input { - "ASCII" | "ascii" => Ok(VersionStringType::Ascii), - "UTF-8" | "utf-8" => Ok(VersionStringType::Utf8), - "UTF-16" | "utf-16" => Ok(VersionStringType::Utf16), - "UTF-16LE" | "utf-16le" => Ok(VersionStringType::Utf16Le), - "UTF-16BE" | "utf-16be" => Ok(VersionStringType::Utf16Be), - "UNSPECIFIED" | "unspecified" => Ok(VersionStringType::Unspecified), - _ => Err(PldmError::InvalidVersionStringType), - } - } -} - -#[derive(Clone, Copy, PartialEq)] -#[repr(u16)] -pub enum DescriptorType { - PciVendorId = 0x0000, - IanaEnterpriseId = 0x0001, - Uuid = 0x0002, - PnpVendorId = 0x0003, - AcpiVendorId = 0x0004, - IeeeAssignedCompanyId = 0x0005, - ScsiVendorId = 0x0006, - PciDeviceId = 0x0100, - PciSubsystemVendorId = 0x0101, - PciSubsystemId = 0x0102, - PciRevisionId = 0x0103, - PnpProductIdentifier = 0x0104, - AcpiProductIdentifier = 0x0105, - AsciiModelNumberLongString = 0x0106, - AsciiModelNumberShortString = 0x0107, - ScsiProductId = 0x0108, - UbmControllerDeviceCode = 0x0109, - VendorDefined = 0xFFFF, -} - -impl TryFrom for DescriptorType { - type Error = PldmError; - - fn try_from(value: u16) -> Result { - match value { - 0x0000 => Ok(DescriptorType::PciVendorId), - 0x0001 => Ok(DescriptorType::IanaEnterpriseId), - 0x0002 => Ok(DescriptorType::Uuid), - 0x0003 => Ok(DescriptorType::PnpVendorId), - 0x0004 => Ok(DescriptorType::AcpiVendorId), - 0x0005 => Ok(DescriptorType::IeeeAssignedCompanyId), - 0x0006 => Ok(DescriptorType::ScsiVendorId), - 0x0100 => Ok(DescriptorType::PciDeviceId), - 0x0101 => Ok(DescriptorType::PciSubsystemVendorId), - 0x0102 => Ok(DescriptorType::PciSubsystemId), - 0x0103 => Ok(DescriptorType::PciRevisionId), - 0x0104 => Ok(DescriptorType::PnpProductIdentifier), - 0x0105 => Ok(DescriptorType::AcpiProductIdentifier), - 0x0106 => Ok(DescriptorType::AsciiModelNumberLongString), - 0x0107 => Ok(DescriptorType::AsciiModelNumberShortString), - 0x0108 => Ok(DescriptorType::ScsiProductId), - 0x0109 => Ok(DescriptorType::UbmControllerDeviceCode), - 0xFFFF => Ok(DescriptorType::VendorDefined), - _ => Err(PldmError::InvalidDescriptorType), - } - } -} - -pub fn get_descriptor_length(descriptor_type: DescriptorType) -> usize { - match &descriptor_type { - DescriptorType::PciVendorId => 2, - DescriptorType::IanaEnterpriseId => 4, - DescriptorType::Uuid => 16, - DescriptorType::PnpVendorId => 3, - DescriptorType::AcpiVendorId => 5, - DescriptorType::IeeeAssignedCompanyId => 3, - DescriptorType::ScsiVendorId => 8, - DescriptorType::PciDeviceId => 2, - DescriptorType::PciSubsystemVendorId => 2, - DescriptorType::PciSubsystemId => 2, - DescriptorType::PciRevisionId => 1, - DescriptorType::PnpProductIdentifier => 4, - DescriptorType::AcpiProductIdentifier => 4, - DescriptorType::AsciiModelNumberLongString => 40, - DescriptorType::AsciiModelNumberShortString => 10, - DescriptorType::ScsiProductId => 16, - DescriptorType::UbmControllerDeviceCode => 4, - DescriptorType::VendorDefined => DESCRIPTOR_DATA_MAX_LEN, - } -} - -#[derive(Debug, Copy, Clone, PartialEq)] -#[repr(C)] -pub struct Descriptor { - pub descriptor_type: u16, - pub descriptor_length: u16, - pub descriptor_data: [u8; DESCRIPTOR_DATA_MAX_LEN], -} - -impl Default for Descriptor { - fn default() -> Self { - Descriptor { - descriptor_type: 0, - descriptor_length: 0, - descriptor_data: [0; DESCRIPTOR_DATA_MAX_LEN], - } - } -} - -impl Descriptor { - pub fn new_empty() -> Self { - Descriptor { - descriptor_type: 0, - descriptor_length: 0, - descriptor_data: [0; DESCRIPTOR_DATA_MAX_LEN], - } - } - - pub fn new(descriptor_type: DescriptorType, descriptor_data: &[u8]) -> Result { - let descriptor_length = get_descriptor_length(descriptor_type); - if descriptor_data.len() != descriptor_length { - return Err(PldmError::InvalidDescriptorLength); - } - - let mut descriptor_data_array = [0u8; DESCRIPTOR_DATA_MAX_LEN]; - descriptor_data_array[..descriptor_length].copy_from_slice(descriptor_data); - - Ok(Descriptor { - descriptor_type: descriptor_type as u16, - descriptor_length: descriptor_length as u16, - descriptor_data: descriptor_data_array, - }) - } - - pub fn codec_size_in_bytes(&self) -> usize { - core::mem::size_of::() * 2 + self.descriptor_length as usize - } -} - -impl PldmCodec for Descriptor { - fn encode(&self, buffer: &mut [u8]) -> Result { - if buffer.len() < self.codec_size_in_bytes() { - return Err(PldmCodecError::BufferTooShort); - } - let mut offset = 0; - - self.descriptor_type - .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) - .unwrap(); - offset += core::mem::size_of::(); - - self.descriptor_length - .write_to(&mut buffer[offset..offset + core::mem::size_of::()]) - .unwrap(); - offset += core::mem::size_of::(); - - self.descriptor_data[..self.descriptor_length as usize] - .write_to(&mut buffer[offset..offset + self.descriptor_length as usize]) - .unwrap(); - offset += self.descriptor_length as usize; - - Ok(offset) - } - - fn decode(buffer: &[u8]) -> Result { - let mut offset = 0; - - let descriptor_type = u16::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += core::mem::size_of::(); - - let descriptor_length = u16::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - offset += core::mem::size_of::(); - - let mut descriptor_data = [0u8; DESCRIPTOR_DATA_MAX_LEN]; - descriptor_data[..descriptor_length as usize].copy_from_slice( - buffer - .get(offset..offset + descriptor_length as usize) - .ok_or(PldmCodecError::BufferTooShort)?, - ); - - Ok(Descriptor { - descriptor_type, - descriptor_length, - descriptor_data, - }) - } -} - -bitfield! { - #[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, PartialEq, Eq, Default)] - pub struct FirmwareDeviceCapability(u32); - impl Debug; - pub u32, reserved, _: 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, 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; -} - -bitfield! { - #[derive(Clone, Copy, FromBytes, IntoBytes, Immutable, PartialEq, Eq)] - pub struct ComponentActivationMethods(u16); - impl Debug; - pub u16, reserved, _: 15, 8; - pub u16, activate_pending_comp_image_set, set_activate_pending_comp_image_set: 7; - pub u16, activate_pending_image, set_activate_pending_image: 6; - pub u16, ac_power_cycle, set_ac_power_cycle: 5; - pub u16, dc_power_cycle, set_dc_power_cycle: 4; - pub u16, system_reboot, set_system_reboot: 3; - pub u16, medium_specific_reset, set_medium_specific_reset: 2; - pub u16, self_contained, set_self_contained: 1; - pub u16, automatic, set_automatic: 0; -} - -#[repr(u16)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ComponentClassification { - Unspecified = 0x0000, - Other = 0x0001, - Driver = 0x0002, - ConfigurationSoftware = 0x0003, - ApplicationSoftware = 0x0004, - Instrumentation = 0x0005, - FirmwareOrBios = 0x0006, - DiagnosticSoftware = 0x0007, - OperatingSystem = 0x0008, - Middleware = 0x0009, - Firmware = 0x000A, - BiosOrFcode = 0x000B, - SupportOrServicePack = 0x000C, - SoftwareBundle = 0x000D, - DownstreamDevice = 0xFFFF, -} - -impl TryFrom for ComponentClassification { - type Error = PldmError; - - fn try_from(value: u16) -> Result { - match value { - 0x0000 => Ok(ComponentClassification::Unspecified), - 0x0001 => Ok(ComponentClassification::Other), - 0x0002 => Ok(ComponentClassification::Driver), - 0x0003 => Ok(ComponentClassification::ConfigurationSoftware), - 0x0004 => Ok(ComponentClassification::ApplicationSoftware), - 0x0005 => Ok(ComponentClassification::Instrumentation), - 0x0006 => Ok(ComponentClassification::FirmwareOrBios), - 0x0007 => Ok(ComponentClassification::DiagnosticSoftware), - 0x0008 => Ok(ComponentClassification::OperatingSystem), - 0x0009 => Ok(ComponentClassification::Middleware), - 0x000A => Ok(ComponentClassification::Firmware), - 0x000B => Ok(ComponentClassification::BiosOrFcode), - 0x000C => Ok(ComponentClassification::SupportOrServicePack), - 0x000D => Ok(ComponentClassification::SoftwareBundle), - 0xFFFF => Ok(ComponentClassification::DownstreamDevice), - _ => Err(PldmError::InvalidComponentClassification), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct PldmFirmwareString { - pub str_type: u8, - pub str_len: u8, - pub str_data: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], -} - -impl Default for PldmFirmwareString { - fn default() -> Self { - PldmFirmwareString { - str_type: 0, - str_len: 0, - str_data: [0; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], - } - } -} - -impl PartialOrd for PldmFirmwareString { - fn partial_cmp(&self, other: &Self) -> Option { - if self.str_type != other.str_type { - return None; - } - let self_str = &self.str_data[..self.str_len as usize]; - let other_str = &other.str_data[..other.str_len as usize]; - Some(self_str.cmp(other_str)) - } -} - -impl PldmFirmwareString { - pub fn new(str_type: &str, fw_str: &str) -> Result { - let str_type = VersionStringType::try_from(str_type)?; - - if fw_str.len() > PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN { - return Err(PldmError::InvalidVersionStringLength); - } - - let mut str_data = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - str_data[..fw_str.len()].copy_from_slice(fw_str.as_bytes()); - - Ok(PldmFirmwareString { - str_type: str_type as u8, - str_len: fw_str.len() as u8, - str_data, - }) - } -} - -#[derive(Clone)] -pub struct PldmFirmwareVersion<'a> { - pub comparison_stamp: u32, - pub str: &'a PldmFirmwareString, - pub date: [u8; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], -} - -impl Default for PldmFirmwareVersion<'_> { - fn default() -> Self { - static DEFAULT_FW_STRING: PldmFirmwareString = PldmFirmwareString { - str_type: 0, - str_len: 0, - str_data: [0; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], - }; - PldmFirmwareVersion { - comparison_stamp: 0, - str: &DEFAULT_FW_STRING, - date: [0; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], - } - } -} - -impl<'a> PldmFirmwareVersion<'a> { - pub fn new(comparison_stamp: u32, str: &'a PldmFirmwareString, date_str: Option<&str>) -> Self { - let mut date_array = [0u8; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN]; - if let Some(date_str) = date_str { - let date_bytes = date_str.as_bytes(); - let len = date_bytes.len().min(PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN); - date_array[..len].copy_from_slice(&date_bytes[..len]); - } - PldmFirmwareVersion { - comparison_stamp, - str, - date: date_array, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, FromBytes, IntoBytes, Immutable, Copy)] -#[repr(C, packed)] -pub struct ComponentParameterEntryFixed { - pub comp_classification: u16, - pub comp_identifier: u16, - pub comp_classification_index: u8, - pub active_comp_comparison_stamp: u32, - pub active_comp_ver_str_type: u8, - pub active_comp_ver_str_len: u8, - pub active_comp_release_date: [u8; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], - pub pending_comp_comparison_stamp: u32, - pub pending_comp_ver_str_type: u8, - pub pending_comp_ver_str_len: u8, - pub pending_comp_release_date: [u8; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], - pub comp_activation_methods: ComponentActivationMethods, - pub capabilities_during_update: FirmwareDeviceCapability, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -#[repr(C)] -pub struct ComponentParameterEntry { - pub comp_param_entry_fixed: ComponentParameterEntryFixed, - pub active_comp_ver_str: [u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], - pub pending_comp_ver_str: Option<[u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]>, -} - -impl Default for ComponentParameterEntry { - fn default() -> Self { - ComponentParameterEntry { - comp_param_entry_fixed: ComponentParameterEntryFixed { - comp_classification: 0, - comp_identifier: 0, - comp_classification_index: 0, - active_comp_comparison_stamp: 0, - active_comp_ver_str_type: 0, - active_comp_ver_str_len: 0, - active_comp_release_date: [0; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], - pending_comp_comparison_stamp: 0, - pending_comp_ver_str_type: 0, - pending_comp_ver_str_len: 0, - pending_comp_release_date: [0; PLDM_FWUP_COMPONENT_RELEASE_DATA_LEN], - comp_activation_methods: ComponentActivationMethods(0), - capabilities_during_update: FirmwareDeviceCapability(0), - }, - active_comp_ver_str: [0; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN], - pending_comp_ver_str: None, - } - } -} - -impl ComponentParameterEntry { - pub fn new( - comp_classification: ComponentClassification, - comp_identifier: u16, - comp_classification_index: u8, - active_firmware_version: &PldmFirmwareVersion, - pending_firmware_version: &PldmFirmwareVersion, - comp_activation_methods: ComponentActivationMethods, - capabilities_during_update: FirmwareDeviceCapability, - ) -> Self { - ComponentParameterEntry { - comp_param_entry_fixed: ComponentParameterEntryFixed { - comp_classification: comp_classification as u16, - comp_identifier, - comp_classification_index, - active_comp_comparison_stamp: active_firmware_version.comparison_stamp, - active_comp_ver_str_type: active_firmware_version.str.str_type, - active_comp_ver_str_len: active_firmware_version.str.str_len, - active_comp_release_date: active_firmware_version.date, - pending_comp_comparison_stamp: pending_firmware_version.comparison_stamp, - pending_comp_ver_str_type: pending_firmware_version.str.str_type, - pending_comp_ver_str_len: pending_firmware_version.str.str_len, - pending_comp_release_date: pending_firmware_version.date, - comp_activation_methods, - capabilities_during_update, - }, - active_comp_ver_str: { - let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - let len = active_firmware_version.str.str_len as usize; - arr[..len].copy_from_slice(&active_firmware_version.str.str_data[..len]); - arr - }, - pending_comp_ver_str: { - if pending_firmware_version.str.str_len > 0 { - let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - let len = pending_firmware_version.str.str_len as usize; - arr[..len].copy_from_slice(&pending_firmware_version.str.str_data[..len]); - Some(arr) - } else { - None - } - }, - } - } - - pub fn codec_size_in_bytes(&self) -> usize { - let mut bytes = 0; - bytes += core::mem::size_of::(); - bytes += self.comp_param_entry_fixed.active_comp_ver_str_len as usize; - if self.pending_comp_ver_str.is_some() { - bytes += self.comp_param_entry_fixed.pending_comp_ver_str_len as usize; - } - bytes - } - - pub fn get_active_fw_ver(&self) -> PldmFirmwareString { - PldmFirmwareString { - str_type: self.comp_param_entry_fixed.active_comp_ver_str_type, - str_len: self.comp_param_entry_fixed.active_comp_ver_str_len, - str_data: self.active_comp_ver_str, - } - } -} - -impl PldmCodec for ComponentParameterEntry { - fn encode(&self, buffer: &mut [u8]) -> Result { - if buffer.len() < self.codec_size_in_bytes() { - return Err(PldmCodecError::BufferTooShort); - } - let mut offset = 0; - - self.comp_param_entry_fixed - .write_to( - &mut buffer[offset..offset + core::mem::size_of::()], - ) - .unwrap(); - - offset += core::mem::size_of::(); - - let len = self.comp_param_entry_fixed.active_comp_ver_str_len as usize; - self.active_comp_ver_str[..len] - .write_to(&mut buffer[offset..offset + len]) - .unwrap(); - offset += len; - - if let Some(pending_comp_ver_str) = &self.pending_comp_ver_str { - let len = self.comp_param_entry_fixed.pending_comp_ver_str_len as usize; - pending_comp_ver_str[..len] - .write_to(&mut buffer[offset..offset + len]) - .unwrap(); - offset += len; - } - - Ok(offset) - } - - fn decode(buffer: &[u8]) -> Result { - let mut offset = 0; - - let comp_param_entry_fixed = ComponentParameterEntryFixed::read_from_bytes( - buffer - .get(offset..offset + core::mem::size_of::()) - .ok_or(PldmCodecError::BufferTooShort)?, - ) - .unwrap(); - - offset += core::mem::size_of::(); - - let active_comp_ver_str_len = comp_param_entry_fixed.active_comp_ver_str_len as usize; - let mut active_comp_ver_str = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - active_comp_ver_str[..active_comp_ver_str_len].copy_from_slice( - buffer - .get(offset..offset + active_comp_ver_str_len) - .ok_or(PldmCodecError::BufferTooShort)?, - ); - offset += active_comp_ver_str_len; - - let pending_comp_ver_str = if comp_param_entry_fixed.pending_comp_ver_str_len > 0 { - let len = comp_param_entry_fixed.pending_comp_ver_str_len as usize; - let mut arr = [0u8; PLDM_FWUP_IMAGE_SET_VER_STR_MAX_LEN]; - arr[..len].copy_from_slice( - buffer - .get(offset..offset + len) - .ok_or(PldmCodecError::BufferTooShort)?, - ); - Some(arr) - } else { - None - }; - - Ok(ComponentParameterEntry { - comp_param_entry_fixed, - active_comp_ver_str, - pending_comp_ver_str, - }) - } -} - -#[repr(u8)] -#[derive(Debug, Clone, Copy)] -pub enum ComponentResponse { - CompCanBeUpdated, - CompCannotBeUpdated, -} - -#[repr(u8)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ComponentResponseCode { - CompCanBeUpdated = 0x00, - CompComparisonStampIdentical = 0x01, - CompComparisonStampLower = 0x02, - InvalidCompComparisonStamp = 0x03, - CompConflict = 0x04, - CompPrerequisitesNotMet = 0x05, - CompNotSupported = 0x06, - CompSecurityRestrictions = 0x07, - IncompleteCompImageSet = 0x08, - ActiveImageNotUpdateableSubsequently = 0x09, - CompVerStrIdentical = 0x0a, - CompVerStrLower = 0x0b, - VendorDefined, // 0xd0..=0xef -} - -impl TryFrom for ComponentResponseCode { - type Error = PldmError; - - fn try_from(val: u8) -> Result { - match val { - 0x00 => Ok(ComponentResponseCode::CompCanBeUpdated), - 0x01 => Ok(ComponentResponseCode::CompComparisonStampIdentical), - 0x02 => Ok(ComponentResponseCode::CompComparisonStampLower), - 0x03 => Ok(ComponentResponseCode::InvalidCompComparisonStamp), - 0x04 => Ok(ComponentResponseCode::CompConflict), - 0x05 => Ok(ComponentResponseCode::CompPrerequisitesNotMet), - 0x06 => Ok(ComponentResponseCode::CompNotSupported), - 0x07 => Ok(ComponentResponseCode::CompSecurityRestrictions), - 0x08 => Ok(ComponentResponseCode::IncompleteCompImageSet), - 0x09 => Ok(ComponentResponseCode::ActiveImageNotUpdateableSubsequently), - 0x0a => Ok(ComponentResponseCode::CompVerStrIdentical), - 0x0b => Ok(ComponentResponseCode::CompVerStrLower), - 0xd0..=0xef => Ok(ComponentResponseCode::VendorDefined), - _ => Err(PldmError::InvalidComponentResponseCode), - } - } -} - -#[repr(u8)] -#[derive(Debug, Clone, Copy)] -pub enum ComponentCompatibilityResponse { - CompCanBeUpdated = 0, - CompCannotBeUpdated = 1, -} - -impl TryFrom for ComponentCompatibilityResponse { - type Error = PldmError; - - fn try_from(val: u8) -> Result { - match val { - 0 => Ok(ComponentCompatibilityResponse::CompCanBeUpdated), - 1 => Ok(ComponentCompatibilityResponse::CompCannotBeUpdated), - _ => Err(PldmError::InvalidComponentCompatibilityResponse), - } - } -} - -#[repr(u8)] -pub enum ComponentCompatibilityResponseCode { - NoResponseCode = 0x00, - CompComparisonStampIdentical = 0x01, - CompComparisonStampLower = 0x02, - InvalidCompComparisonStamp = 0x03, - CompConflict = 0x04, - CompPrerequisitesNotMet = 0x05, - CompNotSupported = 0x06, - CompSecurityRestrictions = 0x07, - IncompleteCompImageSet = 0x08, - CompInfoNoMatch = 0x09, - CompVerStrIdentical = 0x0a, - CompVerStrLower = 0x0b, - VendorDefined, -} - -impl TryFrom for ComponentCompatibilityResponseCode { - type Error = PldmError; - - fn try_from(val: u8) -> Result { - match val { - 0x00 => Ok(ComponentCompatibilityResponseCode::NoResponseCode), - 0x01 => Ok(ComponentCompatibilityResponseCode::CompComparisonStampIdentical), - 0x02 => Ok(ComponentCompatibilityResponseCode::CompComparisonStampLower), - 0x03 => Ok(ComponentCompatibilityResponseCode::InvalidCompComparisonStamp), - 0x04 => Ok(ComponentCompatibilityResponseCode::CompConflict), - 0x05 => Ok(ComponentCompatibilityResponseCode::CompPrerequisitesNotMet), - 0x06 => Ok(ComponentCompatibilityResponseCode::CompNotSupported), - 0x07 => Ok(ComponentCompatibilityResponseCode::CompSecurityRestrictions), - 0x08 => Ok(ComponentCompatibilityResponseCode::IncompleteCompImageSet), - 0x09 => Ok(ComponentCompatibilityResponseCode::CompInfoNoMatch), - 0x0a => Ok(ComponentCompatibilityResponseCode::CompVerStrIdentical), - 0x0b => Ok(ComponentCompatibilityResponseCode::CompVerStrLower), - 0xd0..=0xef => Ok(ComponentCompatibilityResponseCode::VendorDefined), - _ => Err(PldmError::InvalidComponentCompatibilityResponseCode), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_descriptor_encode_decode() { - let test_uid = [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, - 0x0F, 0x10, - ]; - let descriptor = Descriptor::new(DescriptorType::Uuid, &test_uid).unwrap(); - assert_eq!( - descriptor.descriptor_length, - get_descriptor_length(DescriptorType::Uuid) as u16 - ); - let mut buffer = [0u8; 512]; - descriptor.encode(&mut buffer).unwrap(); - let decoded_descriptor = Descriptor::decode(&buffer).unwrap(); - assert_eq!(descriptor, decoded_descriptor); - } - - #[test] - fn test_component_parameter_entry() { - let active_firmware_string = PldmFirmwareString::new("UTF-8", "mcu-runtime-1.0").unwrap(); - let active_firmware_version = - PldmFirmwareVersion::new(0x12345678, &active_firmware_string, Some("20250210")); - - let pending_firmware_string = PldmFirmwareString::new("UTF-8", "mcu-runtime-1.5").unwrap(); - let pending_firmware_version = - PldmFirmwareVersion::new(0x87654321, &pending_firmware_string, Some("20250213")); - - let comp_activation_methods = ComponentActivationMethods(0x0001); - let capabilities_during_update = FirmwareDeviceCapability(0x0010); - - let component_parameter_entry = ComponentParameterEntry::new( - ComponentClassification::Firmware, - 0x0001, - 0x01, - &active_firmware_version, - &pending_firmware_version, - comp_activation_methods, - capabilities_during_update, - ); - - let mut buffer = [0u8; 512]; - component_parameter_entry.encode(&mut buffer).unwrap(); - let decoded_component_parameter_entry = ComponentParameterEntry::decode(&buffer).unwrap(); - assert_eq!(component_parameter_entry, decoded_component_parameter_entry); - - let active_fw_ver = component_parameter_entry.get_active_fw_ver(); - assert_eq!(active_firmware_string, active_fw_ver); - assert!(active_firmware_string < pending_firmware_string); - } -} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs deleted file mode 100644 index 1622365..0000000 --- a/src/protocol/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -// Licensed under the Apache-2.0 license - -pub mod base; -pub mod firmware_update; -pub mod version; diff --git a/src/protocol/version.rs b/src/protocol/version.rs deleted file mode 100644 index bdbe090..0000000 --- a/src/protocol/version.rs +++ /dev/null @@ -1,250 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::error::PldmError; -use core::convert::TryFrom; - -pub type Ver32 = u32; -pub type VersionCheckSum = u32; -pub type ProtocolVersionStr = &'static str; - -// The PLDM base protocol version 1.1.0 -pub const PLDM_BASE_PROTOCOL_VERSION: ProtocolVersionStr = "1.1.0"; - -// PLDM firmware update protocol 1.3.0 -pub const PLDM_FW_UPDATE_PROTOCOL_VERSION: ProtocolVersionStr = "1.3.0"; - -/// PLDM version structure. Ver32 encoding -/// -/// The "major," "minor," and "update" bytes are BCD-encoded, and each byte holds two BCD -/// digits. The "alpha" byte holds an optional alphanumeric character extension that is encoded using the -/// ISO/IEC 8859-1 Character Set. The value 0xF in the most-significant nibble of a BCD-encoded value indicates that the most -/// significant nibble should be ignored and the overall field treated as a single-digit value. Software -/// or utilities that display the number should display only a single digit and should not put in a -/// leading "0" when displaying the number. -/// -/// A value of 0xFF in the "update" field indicates that the entire field is not present. 0xFF is not -/// allowed as a value for the "major" or "minor" fields. Software or utilities that display the version -/// number should not display any characters for this field. -/// -/// For example: -/// - Version 3.7.10a → 0xF3F71061 -/// -/// - Version 3.1 → 0xF3F1FF00 -/// - Version 1.0a → 0xF1F0FF61 -#[derive(Debug, Clone, PartialEq, Eq)] -#[repr(C)] -pub struct PldmVersion { - pub alpha: u8, - pub update: u8, - pub minor: u8, - pub major: u8, -} - -impl TryFrom for PldmVersion { - type Error = PldmError; - - fn try_from(version: ProtocolVersionStr) -> Result { - let mut parts = version.split('.'); - let major_str = parts.next().ok_or(PldmError::InvalidProtocolVersion)?; - let minor_str = parts.next().ok_or(PldmError::InvalidProtocolVersion)?; - let update_str = parts.next().unwrap_or("FF"); - - if parts.next().is_some() { - return Err(PldmError::InvalidProtocolVersion); - } - - let (alpha, minor_str) = if let Some(last_char) = minor_str.chars().last() { - if last_char.is_alphabetic() { - (last_char as u8, &minor_str[..minor_str.len() - 1]) - } else { - (0, minor_str) - } - } else { - (0, minor_str) - }; - - let (alpha, update_str) = if update_str != "FF" { - if let Some(last_char) = update_str.chars().last() { - if last_char.is_alphabetic() { - (last_char as u8, &update_str[..update_str.len() - 1]) - } else { - (alpha, update_str) - } - } else { - (alpha, update_str) - } - } else { - (alpha, update_str) - }; - - let major = major_str - .parse::() - .map_err(|_| PldmError::InvalidProtocolVersion)?; - let minor = minor_str - .parse::() - .map_err(|_| PldmError::InvalidProtocolVersion)?; - let update = if update_str == "FF" { - 0xFF - } else { - update_str - .parse::() - .map_err(|_| PldmError::InvalidProtocolVersion)? - }; - - Ok(PldmVersion { - alpha, - update, - minor, - major, - }) - } -} - -impl PldmVersion { - pub fn new(alpha: u8, update: u8, minor: u8, major: u8) -> Self { - PldmVersion { - alpha, - update, - minor, - major, - } - } - - pub fn bcd_encode_to_ver32(&self) -> Ver32 { - let major_bcd = if self.major < 10 { - 0xF0 | self.major - } else { - ((self.major / 10) << 4) | (self.major % 10) - }; - let minor_bcd = if self.minor < 10 { - 0xF0 | self.minor - } else { - ((self.minor / 10) << 4) | (self.minor % 10) - }; - let update_bcd = if self.update == 0xFF { - 0xFF - } else if self.update < 10 { - 0xF0 | self.update - } else { - ((self.update / 10) << 4) | (self.update % 10) - }; - let alpha_bcd = self.alpha; // Alpha is directly used as it's already in the correct format or 0x00 if not present - - (major_bcd as u32) << 24 - | (minor_bcd as u32) << 16 - | (update_bcd as u32) << 8 - | alpha_bcd as u32 - } - - pub fn bcd_decode_from_ver32(encoded_ver32: Ver32) -> Self { - let major_bcd = ((encoded_ver32 >> 24) & 0xFF) as u8; - let minor_bcd = ((encoded_ver32 >> 16) & 0xFF) as u8; - let update_bcd = ((encoded_ver32 >> 8) & 0xFF) as u8; - let alpha = (encoded_ver32 & 0xFF) as u8; - - let major = if major_bcd >> 4 == 0xF { - major_bcd & 0x0F - } else { - ((major_bcd >> 4) * 10) + (major_bcd & 0x0F) - }; - let minor = if minor_bcd >> 4 == 0xF { - minor_bcd & 0x0F - } else { - ((minor_bcd >> 4) * 10) + (minor_bcd & 0x0F) - }; - let update = if update_bcd == 0xFF { - update_bcd - } else if update_bcd >> 4 == 0xF { - update_bcd & 0x0F - } else { - ((update_bcd >> 4) * 10) + (update_bcd & 0x0F) - }; - - PldmVersion { - alpha, - update, - minor, - major, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_pldm_version_try_from_str() { - let test_version = PldmVersion::try_from("3.7.10").unwrap(); - assert_eq!(test_version, PldmVersion::new(0, 10, 7, 3)); - - let test_version = PldmVersion::try_from("3.7").unwrap(); - assert_eq!(test_version, PldmVersion::new(0, 0xFF, 7, 3)); - - let test_version = PldmVersion::try_from("1.1.0").unwrap(); - assert_eq!(test_version, PldmVersion::new(0, 0, 1, 1)); - - let test_version = PldmVersion::try_from("1.3.0").unwrap(); - assert_eq!(test_version, PldmVersion::new(0, 0, 3, 1)); - - let test_version = PldmVersion::try_from("1.5.18a").unwrap(); - assert_eq!(test_version, PldmVersion::new(0x61, 18, 5, 1)); - - let test_version = PldmVersion::try_from("1.5a").unwrap(); - assert_eq!(test_version, PldmVersion::new(0x61, 0xFF, 5, 1)); - } - - #[test] - fn test_pldm_version_try_from_str_error() { - let test_version = PldmVersion::try_from("3.FF.10"); - assert!(test_version.is_err()); - - let test_version = PldmVersion::try_from("3.7.10a.1"); - assert!(test_version.is_err()); - - let test_version = PldmVersion::try_from("3.7.10a.1.1"); - assert!(test_version.is_err()); - - let test_version = PldmVersion::try_from("3a.7.10"); - assert!(test_version.is_err()); - } - - #[test] - fn test_pldm_version_bcd_encode() { - let test_version1 = PldmVersion::new(0x61, 0x10, 0x7, 0x3); - assert_eq!(test_version1.bcd_encode_to_ver32(), 0xF3F71661); - - let test_version2 = PldmVersion::new(0x61, 0xFF, 0x1, 0x3); - assert_eq!(test_version2.bcd_encode_to_ver32(), 0xF3F1FF61); - - let test_version3 = PldmVersion::new(0x61, 0xFF, 0xa, 0x1); - assert_eq!(test_version3.bcd_encode_to_ver32(), 0xF110FF61); - } - - #[test] - fn test_pldm_version_bcd_decode_from_ver32() { - let test_version1 = PldmVersion::bcd_decode_from_ver32(0xF3F71661); - assert_eq!(test_version1, PldmVersion::new(0x61, 0x10, 0x7, 0x3)); - - let test_version2 = PldmVersion::bcd_decode_from_ver32(0xF3F1FF61); - assert_eq!(test_version2, PldmVersion::new(0x61, 0xFF, 0x1, 0x3)); - - let test_version3 = PldmVersion::bcd_decode_from_ver32(0xF1F0FF62); - assert_eq!(test_version3, PldmVersion::new(0x62, 0xFF, 0x0, 0x1)); - } - - #[test] - fn test_pldm_version_str_to_ver32() { - let test_version1 = "1.3.0"; - let test_version1_ver32 = PldmVersion::try_from(test_version1) - .unwrap() - .bcd_encode_to_ver32(); - assert_eq!(test_version1_ver32, 0xF1F3F000); - - let test_version2 = "1.1.0"; - let test_version2_ver32 = PldmVersion::try_from(test_version2) - .unwrap() - .bcd_encode_to_ver32(); - assert_eq!(test_version2_ver32, 0xF1F1F000); - } -} diff --git a/pldm-service/src/timer.rs b/src/timer.rs similarity index 100% rename from pldm-service/src/timer.rs rename to src/timer.rs diff --git a/pldm-service/src/transport.rs b/src/transport.rs similarity index 100% rename from pldm-service/src/transport.rs rename to src/transport.rs diff --git a/src/util/fw_component.rs b/src/util/fw_component.rs deleted file mode 100644 index 17ff3ff..0000000 --- a/src/util/fw_component.rs +++ /dev/null @@ -1,200 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::message::firmware_update::get_fw_params::FirmwareParameters; -use crate::protocol::firmware_update::{ - ComponentResponseCode, PldmFirmwareString, UpdateOptionFlags, -}; - -// An entry for Pass Component Table or Update Component -#[derive(Clone, Default, PartialEq)] -pub struct FirmwareComponent { - pub comp_classification: u16, - pub comp_identifier: u16, - pub comp_classification_index: u8, - pub comp_comparison_stamp: u32, - pub comp_version: PldmFirmwareString, - pub comp_image_size: Option, - pub update_option_flags: Option, -} - -impl FirmwareComponent { - pub fn new( - comp_classification: u16, - comp_identifier: u16, - comp_classification_index: u8, - comp_comparison_stamp: u32, - comp_version: PldmFirmwareString, - comp_image_size: Option, - update_option_flags: Option, - ) -> Self { - Self { - comp_classification, - comp_identifier, - comp_classification_index, - comp_comparison_stamp, - comp_version, - comp_image_size, - update_option_flags, - } - } - - // Determines if the component is eligible for an update based on the firmware parameters and returns the appropriate ComponentResponseCode - // defined in the PLDM firmware update specification. - pub fn evaluate_update_eligibility( - &self, - fw_params: &FirmwareParameters, - ) -> ComponentResponseCode { - if let Some(entry) = fw_params.comp_param_table.iter().find(|entry| { - entry.comp_param_entry_fixed.comp_classification == self.comp_classification - && entry.comp_param_entry_fixed.comp_identifier == self.comp_identifier - && entry.comp_param_entry_fixed.comp_classification_index - == self.comp_classification_index - }) { - if self.comp_comparison_stamp - == entry.comp_param_entry_fixed.active_comp_comparison_stamp - { - ComponentResponseCode::CompComparisonStampIdentical - } else if self.comp_comparison_stamp - < entry.comp_param_entry_fixed.active_comp_comparison_stamp - { - ComponentResponseCode::CompComparisonStampLower - } else if self.comp_version == entry.get_active_fw_ver() { - ComponentResponseCode::CompVerStrIdentical - } else if self.comp_version < entry.get_active_fw_ver() { - ComponentResponseCode::CompVerStrLower - } else { - ComponentResponseCode::CompCanBeUpdated - } - } else { - ComponentResponseCode::CompNotSupported - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::protocol::firmware_update::{ - ComponentActivationMethods, ComponentClassification, ComponentParameterEntry, - FirmwareDeviceCapability, PldmFirmwareVersion, - }; - - fn construct_firmware_params() -> FirmwareParameters { - let active_firmware_string = PldmFirmwareString::new("UTF-8", "mcu-runtime-1.0").unwrap(); - let active_firmware_version = - PldmFirmwareVersion::new(0x12345678, &active_firmware_string, Some("20250210")); - let pending_firmware_string = PldmFirmwareString::new("UTF-8", "mcu-runtime-1.5").unwrap(); - let pending_firmware_version = - PldmFirmwareVersion::new(0x87654321, &pending_firmware_string, Some("20250213")); - let comp_activation_methods = ComponentActivationMethods(0x0001); - let capabilities_during_update = FirmwareDeviceCapability(0x0010); - let component_parameter_entry = ComponentParameterEntry::new( - ComponentClassification::Firmware, - 0x0001, - 0x01, - &active_firmware_version, - &pending_firmware_version, - comp_activation_methods, - capabilities_during_update, - ); - - const COMP_COUNT: usize = 8; - let comp_param_table: [ComponentParameterEntry; COMP_COUNT] = - core::array::from_fn(|_| component_parameter_entry.clone()); - FirmwareParameters::new( - capabilities_during_update, - COMP_COUNT as u16, - &active_firmware_string, - &pending_firmware_string, - &comp_param_table, - ) - } - - #[test] - fn test_check_update_component() { - let fw_params = construct_firmware_params(); - let comp = FirmwareComponent::new( - ComponentClassification::Firmware as u16, - 0x0001, - 0x01, - 0x12345680, - PldmFirmwareString::new("UTF-8", "mcu-runtime-1.2").unwrap(), - None, - None, - ); - assert_eq!( - comp.evaluate_update_eligibility(&fw_params), - ComponentResponseCode::CompCanBeUpdated - ); - - let comp = FirmwareComponent::new( - ComponentClassification::Firmware as u16, - 0x0001, - 0x01, - 0x12345670, - PldmFirmwareString::new("UTF-8", "mcu-runtime-1.2").unwrap(), - None, - None, - ); - assert_eq!( - comp.evaluate_update_eligibility(&fw_params), - ComponentResponseCode::CompComparisonStampLower - ); - - let comp = FirmwareComponent::new( - ComponentClassification::Firmware as u16, - 0x0001, - 0x01, - 0x12345678, - PldmFirmwareString::new("UTF-8", "mcu-runtime-1.5").unwrap(), - None, - None, - ); - assert_eq!( - comp.evaluate_update_eligibility(&fw_params), - ComponentResponseCode::CompComparisonStampIdentical - ); - - let comp = FirmwareComponent::new( - ComponentClassification::Firmware as u16, - 0x0001, - 0x01, - 0x12345680, - PldmFirmwareString::new("UTF-8", "mcu-runtime-0.5").unwrap(), - None, - None, - ); - assert_eq!( - comp.evaluate_update_eligibility(&fw_params), - ComponentResponseCode::CompVerStrLower - ); - - let comp = FirmwareComponent::new( - ComponentClassification::Firmware as u16, - 0x0001, - 0x01, - 0x12345680, - PldmFirmwareString::new("UTF-8", "mcu-runtime-1.0").unwrap(), - None, - None, - ); - assert_eq!( - comp.evaluate_update_eligibility(&fw_params), - ComponentResponseCode::CompVerStrIdentical - ); - - let comp = FirmwareComponent::new( - ComponentClassification::Firmware as u16, - 0x0001, - 0x05, - 0x12345680, - PldmFirmwareString::new("UTF-8", "mcu-runtime-1.0").unwrap(), - None, - None, - ); - assert_eq!( - comp.evaluate_update_eligibility(&fw_params), - ComponentResponseCode::CompNotSupported - ); - } -} diff --git a/src/util/mctp_transport.rs b/src/util/mctp_transport.rs deleted file mode 100644 index 166163a..0000000 --- a/src/util/mctp_transport.rs +++ /dev/null @@ -1,110 +0,0 @@ -// Licensed under the Apache-2.0 license - -use crate::error::UtilError; -use crate::protocol::base::PLDM_MSG_HEADER_LEN; -use bitfield::bitfield; - -pub const MCTP_PLDM_MSG_TYPE: u8 = 0x01; -pub const MCTP_COMMON_HEADER_OFFSET: usize = 0; -pub const PLDM_MSG_OFFSET: usize = 1; - -bitfield! { - #[derive(Copy, Clone, PartialEq)] - pub struct MctpCommonHeader(u8); - impl Debug; - pub u8, ic, set_ic: 7, 7; - pub u8, msg_type, set_msg_type: 6, 0; -} - -// -/// Extracts the PLDM message from the given MCTP payload. -/// -/// # Arguments -/// -/// * `mctp_payload` - A mutable reference to the MCTP payload. -/// -/// # Returns -/// -/// * `Result<&mut [u8], UtilError>` - A result containing a mutable reference to the PLDM message slice -/// if successful, or a `UtilError` if the payload length is invalid or the message type is incorrect. -pub fn extract_pldm_msg(mctp_payload: &mut [u8]) -> Result<&mut [u8], UtilError> { - // Check if the payload length is sufficient to contain the MCTP common header and PLDM message header. - if mctp_payload.len() < 1 + PLDM_MSG_HEADER_LEN { - return Err(UtilError::InvalidMctpPayloadLength); - } - - // Extract the MCTP common header from the payload. - let mctp_common_header = MctpCommonHeader(mctp_payload[MCTP_COMMON_HEADER_OFFSET]); - - // Validate the integrity check (IC) and message type fields. - if mctp_common_header.ic() != 0 || mctp_common_header.msg_type() != MCTP_PLDM_MSG_TYPE { - return Err(UtilError::InvalidMctpMsgType); - } - - // Return a mutable reference to the PLDM message slice. - Ok(&mut mctp_payload[PLDM_MSG_OFFSET..]) -} - -/// Constructs an MCTP payload with a PLDM message. -/// -/// # Arguments -/// -/// * `mctp_payload` - A mutable reference to the MCTP payload. -/// -/// # Returns -/// -/// * `Result<&mut [u8], UtilError>` - A result containing a mutable reference to the PLDM message slice -/// if successful, or a `UtilError` if the payload length is invalid. -pub fn construct_mctp_pldm_msg(mctp_payload: &mut [u8]) -> Result<&mut [u8], UtilError> { - // Check if the payload length is sufficient to contain the MCTP common header and PLDM message header. - if mctp_payload.len() < 1 + PLDM_MSG_HEADER_LEN { - return Err(UtilError::InvalidMctpPayloadLength); - } - - // Initialize the MCTP common header. - let mut mctp_common_header = MctpCommonHeader(0); - mctp_common_header.set_ic(0); - mctp_common_header.set_msg_type(MCTP_PLDM_MSG_TYPE); - - // Set the MCTP common header in the payload. - mctp_payload[MCTP_COMMON_HEADER_OFFSET] = mctp_common_header.0; - - // Return a mutable reference to the PLDM message slice. - Ok(&mut mctp_payload[PLDM_MSG_OFFSET..]) -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_retrieve_pldm_msg() { - let mut mctp_payload = [0u8; 8]; - assert_eq!( - extract_pldm_msg(&mut mctp_payload), - Err(UtilError::InvalidMctpMsgType) - ); - mctp_payload[0] = 0x01; - assert_eq!(extract_pldm_msg(&mut mctp_payload).unwrap(), &mut [0u8; 7]); - let mut mctp_payload = [0u8; 3]; - assert_eq!( - extract_pldm_msg(&mut mctp_payload), - Err(UtilError::InvalidMctpPayloadLength) - ); - } - - #[test] - fn test_construct_mctp_pldm_msg() { - let mut mctp_payload = [0u8; 10]; - assert_eq!( - construct_mctp_pldm_msg(&mut mctp_payload).unwrap(), - &mut [0u8; 9] - ); - assert_eq!(mctp_payload[0], 0x01); - let mut mctp_payload = [0u8; 3]; - assert_eq!( - construct_mctp_pldm_msg(&mut mctp_payload), - Err(UtilError::InvalidMctpPayloadLength) - ); - } -} diff --git a/src/util/mod.rs b/src/util/mod.rs deleted file mode 100644 index e435fb3..0000000 --- a/src/util/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Licensed under the Apache-2.0 license - -pub mod fw_component; -pub mod mctp_transport; From d4d7a1cf14485e986c2cff535977fb3d60d75326 Mon Sep 17 00:00:00 2001 From: CourtneyDrant Date: Fri, 10 Oct 2025 15:31:59 -0700 Subject: [PATCH 4/5] Moved to no_std. Removed dynamic allocation. --- pldm-common/src/lib.rs | 8 ++++ src/cmd_interface.rs | 9 ++--- src/config.rs | 70 ++++++++++++++++------------------- src/control_context.rs | 1 - src/error.rs | 1 - src/firmware_device/fd_ops.rs | 2 - src/lib.rs | 8 ++++ src/transport.rs | 31 ++++++++++++---- 8 files changed, 76 insertions(+), 54 deletions(-) diff --git a/pldm-common/src/lib.rs b/pldm-common/src/lib.rs index 1cbd5bb..c57ad8f 100644 --- a/pldm-common/src/lib.rs +++ b/pldm-common/src/lib.rs @@ -1,3 +1,11 @@ +#![no_std] + +// Re-export core types for no_std compatibility +pub use core::{ + option::Option::{self, Some, None}, + result::Result::{self, Ok, Err}, +}; + // Licensed under the Apache-2.0 license diff --git a/src/cmd_interface.rs b/src/cmd_interface.rs index 02ed640..9fb27a0 100644 --- a/src/cmd_interface.rs +++ b/src/cmd_interface.rs @@ -1,5 +1,4 @@ // Licensed under the Apache-2.0 license - use crate::control_context::{ControlContext, CtrlCmdResponder, ProtocolCapability}; use crate::error::MsgHandlerError; use crate::firmware_device::fd_context::FirmwareDeviceContext; @@ -29,18 +28,18 @@ pub(crate) fn generate_failure_response( resp.encode(payload).map_err(MsgHandlerError::Codec) } -pub struct CmdInterface<'a> { +pub struct CmdInterface<'a, MctpTransport> { ctrl_ctx: ControlContext<'a>, fd_ctx: FirmwareDeviceContext, - transport: &'a mut dyn MctpTransport, + transport: &'a mut MctpTransport, busy: AtomicBool, } -impl<'a> CmdInterface<'a> { +impl<'a> CmdInterface<'a, MctpTransport> { pub fn new( protocol_capabilities: &'a [ProtocolCapability], fd_ctx: FirmwareDeviceContext, - transport: &'a mut dyn MctpTransport, + transport: &'a mut MctpTransport, ) -> Self { let ctrl_ctx = ControlContext::new(protocol_capabilities); Self { diff --git a/src/config.rs b/src/config.rs index f338071..fd0089a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,5 @@ // Licensed under the Apache-2.0 license - use crate::control_context::ProtocolCapability; -use std::sync::LazyLock; use pldm_common::protocol::base::{PldmControlCmd, PldmSupportedType}; use pldm_common::protocol::firmware_update::{FwUpdateCmd, PldmFdTime}; @@ -12,39 +10,35 @@ pub const DEFAULT_FD_T2_RETRY_TIME: PldmFdTime = 5000; // FD_T2 retry request fo pub const INSTANCE_ID_COUNT: u8 = 32; pub const UA_EID: u8 = 8; // Update Agent Endpoint ID for testing. -pub static PLDM_PROTOCOL_CAPABILITIES: LazyLock< - [ProtocolCapability<'static>; PLDM_PROTOCOL_CAP_COUNT], -> = LazyLock::new(|| { - [ - ProtocolCapability { - pldm_type: PldmSupportedType::Base, - protocol_version: 0xF1F1F000, //"1.1.0" - supported_commands: &[ - PldmControlCmd::SetTid as u8, - PldmControlCmd::GetTid as u8, - PldmControlCmd::GetPldmCommands as u8, - PldmControlCmd::GetPldmVersion as u8, - PldmControlCmd::GetPldmTypes as u8, - ], - }, - ProtocolCapability { - pldm_type: PldmSupportedType::FwUpdate, - protocol_version: 0xF1F3F000, // "1.3.0" - supported_commands: &[ - FwUpdateCmd::QueryDeviceIdentifiers as u8, - FwUpdateCmd::GetFirmwareParameters as u8, - FwUpdateCmd::RequestUpdate as u8, - FwUpdateCmd::PassComponentTable as u8, - FwUpdateCmd::UpdateComponent as u8, - FwUpdateCmd::RequestFirmwareData as u8, - FwUpdateCmd::TransferComplete as u8, - FwUpdateCmd::VerifyComplete as u8, - FwUpdateCmd::ApplyComplete as u8, - FwUpdateCmd::ActivateFirmware as u8, - FwUpdateCmd::GetStatus as u8, - FwUpdateCmd::CancelUpdateComponent as u8, - FwUpdateCmd::CancelUpdate as u8, - ], - }, - ] -}); +pub static PLDM_PROTOCOL_CAPABILITIES: [ProtocolCapability<'static>; PLDM_PROTOCOL_CAP_COUNT] = [ + ProtocolCapability { + pldm_type: PldmSupportedType::Base, + protocol_version: 0xF1F1F000, //"1.1.0" + supported_commands: &[ + PldmControlCmd::SetTid as u8, + PldmControlCmd::GetTid as u8, + PldmControlCmd::GetPldmCommands as u8, + PldmControlCmd::GetPldmVersion as u8, + PldmControlCmd::GetPldmTypes as u8, + ], + }, + ProtocolCapability { + pldm_type: PldmSupportedType::FwUpdate, + protocol_version: 0xF1F3F000, // "1.3.0" + supported_commands: &[ + FwUpdateCmd::QueryDeviceIdentifiers as u8, + FwUpdateCmd::GetFirmwareParameters as u8, + FwUpdateCmd::RequestUpdate as u8, + FwUpdateCmd::PassComponentTable as u8, + FwUpdateCmd::UpdateComponent as u8, + FwUpdateCmd::RequestFirmwareData as u8, + FwUpdateCmd::TransferComplete as u8, + FwUpdateCmd::VerifyComplete as u8, + FwUpdateCmd::ApplyComplete as u8, + FwUpdateCmd::ActivateFirmware as u8, + FwUpdateCmd::GetStatus as u8, + FwUpdateCmd::CancelUpdateComponent as u8, + FwUpdateCmd::CancelUpdate as u8, + ], + }, +]; \ No newline at end of file diff --git a/src/control_context.rs b/src/control_context.rs index ae381c5..814d379 100644 --- a/src/control_context.rs +++ b/src/control_context.rs @@ -1,5 +1,4 @@ // Licensed under the Apache-2.0 license - use crate::cmd_interface::generate_failure_response; use crate::error::MsgHandlerError; use core::sync::atomic::{AtomicUsize, Ordering}; diff --git a/src/error.rs b/src/error.rs index 8f42a18..f58428a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,4 @@ // Licensed under the Apache-2.0 license - use crate::firmware_device::fd_ops::FdOpsError; use crate::transport::TransportError; use pldm_common::codec::PldmCodecError; diff --git a/src/firmware_device/fd_ops.rs b/src/firmware_device/fd_ops.rs index 2a64cc9..f9f746d 100644 --- a/src/firmware_device/fd_ops.rs +++ b/src/firmware_device/fd_ops.rs @@ -1,7 +1,5 @@ // Licensed under the Apache-2.0 license -extern crate alloc; -use alloc::boxed::Box; use pldm_common::message::firmware_update::apply_complete::ApplyResult; use pldm_common::message::firmware_update::get_status::ProgressPercent; use pldm_common::message::firmware_update::request_cancel::{ diff --git a/src/lib.rs b/src/lib.rs index 9793a31..1ff2dc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,12 @@ // Licensed under the Apache-2.0 license +#![no_std] + +// Re-export core types for no_std compatibility +pub use core::{ + option::Option::{self, Some, None}, + result::Result::{self, Ok, Err}, +}; + pub mod cmd_interface; pub mod config; diff --git a/src/transport.rs b/src/transport.rs index 736fe19..1fdb4b0 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -18,14 +18,31 @@ pub enum TransportError { NoRequestInFlight, } -pub trait MctpTransport { - //fn new(drv_num: u32) -> Self; +pub struct MctpTransport; - fn send_request(&mut self, dest_eid: u8, req: &[u8]) -> Result<(), TransportError>; +impl MctpTransport { + pub fn new() -> Self { + MctpTransport + } - fn receive_response(&mut self, rsp: &mut [u8]) -> Result<(), TransportError>; + pub fn send_request(&mut self, _dest_eid: u8, _req: &[u8]) -> Result<(), TransportError> { + // TODO: Implement actual MCTP transport + Ok(()) + } - fn receive_request(&mut self, req: &mut [u8]) -> Result<(), TransportError>; + pub fn receive_response(&mut self, _rsp: &mut [u8]) -> Result<(), TransportError> { + // TODO: Implement actual MCTP transport + Ok(()) + } - fn send_response(&mut self, resp: &[u8]) -> Result<(), TransportError>; -} + pub fn receive_request(&mut self, _req: &mut [u8]) -> Result<(), TransportError> { + // TODO: Implement actual MCTP transport + Ok(()) + } + + pub fn send_response(&mut self, _resp: &[u8]) -> Result<(), TransportError> { + // TODO: Implement actual MCTP transport + Ok(()) + } + +} \ No newline at end of file From 2cf630a199e0db1430fcffa651af37708a5bd93b Mon Sep 17 00:00:00 2001 From: CourtneyDrant Date: Mon, 20 Oct 2025 13:41:56 -0700 Subject: [PATCH 5/5] Adjusted interface to support ipc between responder and pldm-lib. --- pldm-common/src/protocol/firmware_update.rs | 3 +- src/cmd_interface.rs | 14 +- src/firmware_device/fd_context.rs | 42 +-- src/firmware_device/fd_internal.rs | 272 ++++++++++++++++---- 4 files changed, 247 insertions(+), 84 deletions(-) diff --git a/pldm-common/src/protocol/firmware_update.rs b/pldm-common/src/protocol/firmware_update.rs index f959f50..4033e96 100644 --- a/pldm-common/src/protocol/firmware_update.rs +++ b/pldm-common/src/protocol/firmware_update.rs @@ -268,7 +268,8 @@ pub fn get_descriptor_length(descriptor_type: DescriptorType) -> usize { } #[derive(Debug, Copy, Clone, PartialEq)] -#[repr(C)] +#[derive(FromBytes, IntoBytes)] +//#[repr(C)] pub struct Descriptor { pub descriptor_type: u16, pub descriptor_length: u16, diff --git a/src/cmd_interface.rs b/src/cmd_interface.rs index 9fb27a0..3a0b94f 100644 --- a/src/cmd_interface.rs +++ b/src/cmd_interface.rs @@ -38,10 +38,10 @@ pub struct CmdInterface<'a, MctpTransport> { impl<'a> CmdInterface<'a, MctpTransport> { pub fn new( protocol_capabilities: &'a [ProtocolCapability], - fd_ctx: FirmwareDeviceContext, transport: &'a mut MctpTransport, ) -> Self { let ctrl_ctx = ControlContext::new(protocol_capabilities); + let fd_ctx = FirmwareDeviceContext::new(); Self { ctrl_ctx, fd_ctx, @@ -103,15 +103,7 @@ impl<'a> CmdInterface<'a, MctpTransport> { Ok(()) } - pub fn should_start_initiator_mode(&self) -> bool { - self.fd_ctx.should_start_initiator_mode() - } - - pub fn should_stop_initiator_mode(&self) -> bool { - self.fd_ctx.should_stop_initiator_mode() - } - - fn process_request(&self, msg_buf: &mut [u8]) -> Result { + fn process_request(&mut self, msg_buf: &mut [u8]) -> Result { // Check if the handler is busy processing a request if self.busy.load(Ordering::SeqCst) { return Err(MsgHandlerError::NotReady); @@ -167,7 +159,7 @@ impl<'a> CmdInterface<'a, MctpTransport> { } fn process_fw_update_cmd( - &self, + &mut self, cmd_opcode: u8, payload: &mut [u8], ) -> Result { diff --git a/src/firmware_device/fd_context.rs b/src/firmware_device/fd_context.rs index b84aedd..82eec88 100644 --- a/src/firmware_device/fd_context.rs +++ b/src/firmware_device/fd_context.rs @@ -67,7 +67,7 @@ impl FirmwareDeviceContext { pub fn new() -> Self { Self { ops: FdOps::new(), - internal: FdInternal::new() + internal: FdInternal::new(0,0,0) } } @@ -102,7 +102,7 @@ impl FirmwareDeviceContext { } pub fn get_firmware_parameters_rsp( - &self, + &mut self, payload: &mut [u8], ) -> Result { // Decode the request message @@ -128,7 +128,7 @@ impl FirmwareDeviceContext { } } - pub fn request_update_rsp(&self, payload: &mut [u8]) -> Result { + pub fn request_update_rsp(&mut self, payload: &mut [u8]) -> Result { // Check if FD is in idle state. Otherwise returns 'ALREADY_IN_UPDATE_MODE' completion code if self.internal.is_update_mode() { return generate_failure_response( @@ -181,7 +181,7 @@ impl FirmwareDeviceContext { } } - pub fn pass_component_rsp(&self, payload: &mut [u8]) -> Result { + pub fn pass_component_rsp(&mut self, payload: &mut [u8]) -> Result { // Check if FD is in 'LearnComponents' state. Otherwise returns 'INVALID_STATE' completion code if self.internal.get_fd_state() != FirmwareDeviceState::LearnComponents { return generate_failure_response( @@ -263,7 +263,7 @@ impl FirmwareDeviceContext { } } - pub fn update_component_rsp(&self, payload: &mut [u8]) -> Result { + pub fn update_component_rsp(&mut self, payload: &mut [u8]) -> Result { // Check if FD is in 'ReadyTransfer' state. Otherwise returns 'INVALID_STATE' completion code if self.internal.get_fd_state() != FirmwareDeviceState::ReadyXfer { return generate_failure_response( @@ -351,7 +351,7 @@ impl FirmwareDeviceContext { } pub fn activate_firmware_rsp( - &self, + &mut self, payload: &mut [u8], ) -> Result { // Check if FD is in 'ReadyTransfer' state. Otherwise returns 'INVALID_STATE' completion code @@ -406,7 +406,7 @@ impl FirmwareDeviceContext { } pub fn cancel_update_component_rsp( - &self, + &mut self, payload: &mut [u8], ) -> Result { // If FD is not in update mode, return 'NOT_IN_UPDATE_MODE' completion code @@ -464,7 +464,7 @@ impl FirmwareDeviceContext { } } - pub fn cancel_update_rsp(&self, payload: &mut [u8]) -> Result { + pub fn cancel_update_rsp(&mut self, payload: &mut [u8]) -> Result { // If FD is not in update mode, return 'NOT_IN_UPDATE_MODE' completion code if !self.internal.is_update_mode() { return generate_failure_response( @@ -529,7 +529,7 @@ impl FirmwareDeviceContext { } } - pub fn get_status_rsp(&self, payload: &mut [u8]) -> Result { + pub fn get_status_rsp(&mut self, payload: &mut [u8]) -> Result { let req = GetStatusRequest::decode(payload).map_err(MsgHandlerError::Codec)?; let cur_state = self.internal.get_fd_state(); @@ -624,16 +624,16 @@ impl FirmwareDeviceContext { } } - pub fn set_fd_t1_ts(&self) { + pub fn set_fd_t1_ts(&mut self) { self.internal .set_fd_t1_update_ts(self.ops.now()); } - pub fn should_start_initiator_mode(&self) -> bool { + pub fn should_start_initiator_mode(&mut self) -> bool { self.internal.get_fd_state() == FirmwareDeviceState::Download } - pub fn should_stop_initiator_mode(&self) -> bool { + pub fn should_stop_initiator_mode(&mut self) -> bool { !matches!( self.internal.get_fd_state(), FirmwareDeviceState::Download @@ -642,7 +642,7 @@ impl FirmwareDeviceContext { ) } - pub fn fd_progress(&self, payload: &mut [u8]) -> Result { + pub fn fd_progress(&mut self, payload: &mut [u8]) -> Result { let fd_state = self.internal.get_fd_state(); let result = match fd_state { @@ -670,7 +670,7 @@ impl FirmwareDeviceContext { Ok(result) } - pub fn handle_response(&self, payload: &mut [u8]) -> Result<(), MsgHandlerError> { + pub fn handle_response(&mut self, payload: &mut [u8]) -> Result<(), MsgHandlerError> { let rsp_header = PldmMsgHeader::<[u8; 3]>::decode(payload).map_err(MsgHandlerError::Codec)?; let (cmd_code, instance_id) = (rsp_header.cmd_code(), rsp_header.instance_id()); @@ -695,7 +695,7 @@ impl FirmwareDeviceContext { } } - fn process_request_fw_data_rsp(&self, payload: &mut [u8]) -> Result<(), MsgHandlerError> { + fn process_request_fw_data_rsp(&mut self, payload: &mut [u8]) -> Result<(), MsgHandlerError> { let fd_state = self.internal.get_fd_state(); if fd_state != FirmwareDeviceState::Download { return Err(MsgHandlerError::FdInitiatorModeError); @@ -766,7 +766,7 @@ impl FirmwareDeviceContext { } fn process_transfer_complete_rsp( - &self, + &mut self, _payload: &mut [u8], ) -> Result<(), MsgHandlerError> { let fd_state = self.internal.get_fd_state(); @@ -798,7 +798,7 @@ impl FirmwareDeviceContext { } fn process_verify_complete_rsp( - &self, + &mut self, _payload: &mut [u8], ) -> Result<(), MsgHandlerError> { let fd_state = self.internal.get_fd_state(); @@ -828,7 +828,7 @@ impl FirmwareDeviceContext { Ok(()) } - fn process_apply_complete_rsp(&self, _payload: &mut [u8]) -> Result<(), MsgHandlerError> { + fn process_apply_complete_rsp(&mut self, _payload: &mut [u8]) -> Result<(), MsgHandlerError> { let fd_state = self.internal.get_fd_state(); if fd_state != FirmwareDeviceState::Apply { return Err(MsgHandlerError::FdInitiatorModeError); @@ -854,7 +854,7 @@ impl FirmwareDeviceContext { Ok(()) } - fn fd_progress_download(&self, payload: &mut [u8]) -> Result { + fn fd_progress_download(&mut self, payload: &mut [u8]) -> Result { if !self.should_send_fd_request() { return Err(MsgHandlerError::FdInitiatorModeError); } @@ -929,7 +929,7 @@ impl FirmwareDeviceContext { } } - fn pldm_fd_progress_verify(&self, _payload: &mut [u8]) -> Result { + fn pldm_fd_progress_verify(&mut self, _payload: &mut [u8]) -> Result { if !self.should_send_fd_request() { return Err(MsgHandlerError::FdInitiatorModeError); } @@ -974,7 +974,7 @@ impl FirmwareDeviceContext { Ok(msg_len) } - fn pldm_fd_progress_apply(&self, _payload: &mut [u8]) -> Result { + fn pldm_fd_progress_apply(&mut self, _payload: &mut [u8]) -> Result { if !self.should_send_fd_request() { return Err(MsgHandlerError::FdInitiatorModeError); } diff --git a/src/firmware_device/fd_internal.rs b/src/firmware_device/fd_internal.rs index d4d1325..bf60f28 100644 --- a/src/firmware_device/fd_internal.rs +++ b/src/firmware_device/fd_internal.rs @@ -8,114 +8,283 @@ use pldm_common::protocol::firmware_update::{ use pldm_common::util::fw_component::FirmwareComponent; pub struct FdInternal { - req: FdReq + // Current state of the firmware device. + state: FirmwareDeviceState, + + // Previous state of the firmware device. + prev_state: FirmwareDeviceState, + + // Reason for the last transition to the idle state. + // Only valid when `state == FirmwareDeviceState::Idle`. + reason: Option, + + // Details of the component currently being updated. + // Set by `UpdateComponent`, available during download/verify/apply. + update_comp: FirmwareComponent, + + // Flags indicating update options. + update_flags: UpdateOptionFlags, + + // Maximum transfer size allowed by the UA or platform implementation. + max_xfer_size: u32, + + // Request details used for download/verify/apply operations. + req: FdReq, + + // Mode-specific data for the requester. + initiator_mode_state: InitiatorModeState, + + // Address of the Update Agent (UA). + _ua_address: Option, + + // Timestamp for FD T1 timeout in milliseconds. + fd_t1_update_ts: PldmFdTime, + + fd_t1_timeout: PldmFdTime, + fd_t2_retry_time: PldmFdTime, +} + +impl Default for FdInternal { + fn default() -> Self { + Self::new( + crate::config::FD_MAX_XFER_SIZE as u32, + crate::config::DEFAULT_FD_T1_TIMEOUT, + crate::config::DEFAULT_FD_T2_RETRY_TIME, + ) + } } impl FdInternal { - pub fn new() -> Self { + pub fn new(max_xfer_size: u32, fd_t1_timeout: u64, fd_t2_retry_time: u64) -> Self { Self { - req: FdReq::default() + state: FirmwareDeviceState::Idle, + prev_state: FirmwareDeviceState::Idle, + reason: None, + update_comp: FirmwareComponent::default(), + update_flags: UpdateOptionFlags(0), + max_xfer_size, + req: FdReq::new(), + initiator_mode_state: InitiatorModeState::Download(DownloadState::default()), + _ua_address: None, + fd_t1_update_ts: 0, + fd_t1_timeout, + fd_t2_retry_time, } } - // Request details used for download/verify/apply operations. - pub fn is_update_mode(&self) -> bool { - false + + pub fn is_update_mode(&self) -> bool { + self.state != FirmwareDeviceState::Idle } - pub fn set_fd_state(&self, state: FirmwareDeviceState) { } + pub fn set_fd_state(&mut self, state: FirmwareDeviceState) { + if self.state != state { + self.prev_state = self.state.clone(); + self.state = state; + } + } - pub fn set_fd_idle(&self, reason_code: GetStatusReasonCode) { } + pub fn set_fd_idle(&mut self, reason_code: GetStatusReasonCode) { + if self.state != FirmwareDeviceState::Idle { + self.prev_state = self.state.clone(); + self.state = FirmwareDeviceState::Idle; + self.reason = Some(reason_code); + } + } - pub fn fd_idle_timeout(&self) { } + pub fn fd_idle_timeout(&mut self) { + let state = self.get_fd_state(); + let reason = match state { + FirmwareDeviceState::Idle => return, + FirmwareDeviceState::LearnComponents => GetStatusReasonCode::LearnComponentTimeout, + FirmwareDeviceState::ReadyXfer => GetStatusReasonCode::ReadyXferTimeout, + FirmwareDeviceState::Download => GetStatusReasonCode::DownloadTimeout, + FirmwareDeviceState::Verify => GetStatusReasonCode::VerifyTimeout, + FirmwareDeviceState::Apply => GetStatusReasonCode::ApplyTimeout, + FirmwareDeviceState::Activate => GetStatusReasonCode::ActivateFw, + }; + + self.set_fd_idle(reason); + } pub fn get_fd_reason(&self) -> Option { - Some(GetStatusReasonCode::CancelUpdate) - } + self.reason + } pub fn get_fd_state(&self) -> FirmwareDeviceState { - FirmwareDeviceState::Idle - } + self.state.clone() + } - pub fn get_fd_prev_state(&self) -> FirmwareDeviceState { - FirmwareDeviceState::Idle + pub fn get_fd_prev_state(&self) -> FirmwareDeviceState { + self.prev_state.clone() } - pub fn set_xfer_size(&self, transfer_size: usize) { } + pub fn set_xfer_size(&mut self, transfer_size: usize) { + self.max_xfer_size = transfer_size as u32; + } - pub fn get_xfer_size(&self) -> usize { 0 } + pub fn get_xfer_size(&self) -> usize { + self.max_xfer_size as usize + } - pub fn set_component(&self, comp: &FirmwareComponent) { } + pub fn set_component(&mut self, comp: &FirmwareComponent) { + self.update_comp = comp.clone(); + } - pub fn get_component(&self) -> FirmwareComponent { FirmwareComponent::default() } + pub fn get_component(&self) -> FirmwareComponent { + self.update_comp.clone() + } - pub fn set_update_flags(&self, flags: UpdateOptionFlags) { } + pub fn set_update_flags(&mut self, flags: UpdateOptionFlags) { + self.update_flags = flags; + } - pub fn get_update_flags(&self) -> UpdateOptionFlags { UpdateOptionFlags((0)) } + pub fn get_update_flags(&self) -> UpdateOptionFlags { + self.update_flags + } pub fn set_fd_req( - &self, + &mut self, req_state: FdReqState, complete: bool, result: Option, instance_id: Option, command: Option, sent_time: Option, - ) { } - + ) { + self.req = FdReq { + state: req_state, + complete, + result, + instance_id, + command, + sent_time, + }; + } - pub fn alloc_next_instance_id(&self) -> Option { Some(0xff) } + pub fn alloc_next_instance_id(&mut self) -> Option { + self.req.instance_id = Some( + self + .req + .instance_id + .map_or(1, |id| (id + 1) % crate::config::INSTANCE_ID_COUNT), + ); + self.req.instance_id + } - pub fn get_fd_req(&self) -> FdReq { self.req.clone() } + pub fn get_fd_req(&self) -> FdReq { + self.req.clone() + } - pub fn get_fd_req_state(&self) -> FdReqState { FdReqState::Failed } + pub fn get_fd_req_state(&self) -> FdReqState { + self.req.state.clone() + } - pub fn set_fd_req_state(&self, state: FdReqState) { } + pub fn set_fd_req_state(&mut self, state: FdReqState) { + self.req.state = state; + } - pub fn get_fd_sent_time(&self) -> Option { Some(0xbaddbadd) } + pub fn get_fd_sent_time(&self) -> Option { + self.req.sent_time + } - pub fn is_fd_req_complete(&self) -> bool { false } + pub fn is_fd_req_complete(&self) -> bool { + self.req.complete + } - pub fn get_fd_req_result(&self) -> Option { Some(0xff) } + pub fn get_fd_req_result(&self) -> Option { + self.req.result + } pub fn get_fd_download_chunk( &self, requested_offset: u32, requested_length: u32, - ) -> Option<(u32, u32)> { - Some((0xFFFFFFFF, 0xFFFFFFFF)) + ) -> Option<(u32, u32)> { + if self.state != FirmwareDeviceState::Download { + return None; + } + + let comp_image_size = self.update_comp.comp_image_size.unwrap_or(0); + if requested_offset > comp_image_size + || requested_offset + requested_length + > comp_image_size + PLDM_FWUP_MAX_PADDING_SIZE as u32 + { + return None; + } + let chunk_size = requested_length.min(self.max_xfer_size); + Some((requested_offset, chunk_size)) } pub fn get_fd_download_state(&self) -> Option<(u32, u32)> { - Some((0xFFFFFFFF, 0xFFFFFFFF)) + if let InitiatorModeState::Download(download) = &self.initiator_mode_state { + Some((download.offset, download.length)) + } else { + None + } } - pub fn set_fd_download_state(&self, offset: u32, length: u32) { } + pub fn set_fd_download_state(&mut self, offset: u32, length: u32) { + if let InitiatorModeState::Download(download) = &mut self.initiator_mode_state { + download.offset = offset; + download.length = length; + } + } - pub fn set_initiator_mode(&self, mode: InitiatorModeState) { } + pub fn set_initiator_mode(&mut self, mode: InitiatorModeState) { + self.initiator_mode_state = mode; + } - pub fn set_fd_verify_progress(&self, progress: u8) { } + pub fn set_fd_verify_progress(&mut self, progress: u8) { + if let InitiatorModeState::Verify(verify) = &mut self.initiator_mode_state { + verify.progress_percent = progress; + } + } - pub fn set_fd_apply_progress(&self, progress: u8) { } + pub fn set_fd_apply_progress(&mut self, progress: u8) { + if let InitiatorModeState::Apply(apply) = &mut self.initiator_mode_state { + apply.progress_percent = progress; + } + } - pub fn get_fd_verify_progress(&self) -> Option { - Some(0xff) + pub fn get_fd_verify_progress(&mut self) -> Option { + if let InitiatorModeState::Verify(verify) = &mut self.initiator_mode_state { + Some(verify.progress_percent) + } else { + None + } } pub fn get_fd_apply_progress(&self) -> Option { - Some(0xff) + if let InitiatorModeState::Apply(apply) = &self.initiator_mode_state { + Some(apply.progress_percent) + } else { + None + } } - pub fn set_fd_t1_update_ts(&self, timestamp: PldmFdTime) { } + pub fn set_fd_t1_update_ts(&mut self, timestamp: PldmFdTime) { + self.fd_t1_update_ts = timestamp; + } - pub fn get_fd_t1_update_ts(&self) -> PldmFdTime { 0xbaddbadd } + pub fn get_fd_t1_update_ts(&self) -> PldmFdTime { + self.fd_t1_update_ts + } - pub fn set_fd_t1_timeout(&self, timeout: PldmFdTime) { } + pub fn set_fd_t1_timeout(&mut self, timeout: PldmFdTime) { + self.fd_t1_timeout = timeout; + } - pub fn get_fd_t1_timeout(&self) -> PldmFdTime { 0xbaddbadd } + pub fn get_fd_t1_timeout(&self) -> PldmFdTime { + self.fd_t1_timeout + } - pub fn set_fd_t2_retry_time(&self, retry_time: PldmFdTime) { } + pub fn set_fd_t2_retry_time(&mut self, retry_time: PldmFdTime) { + self.fd_t2_retry_time = retry_time; + } - pub fn get_fd_t2_retry_time(&self) -> PldmFdTime { 0xbaddbadd } + pub fn get_fd_t2_retry_time(&self) -> PldmFdTime { + self.fd_t2_retry_time + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -139,6 +308,7 @@ pub struct FdReq { // This is relevant for TransferComplete, VerifyComplete, and ApplyComplete requests. pub complete: bool, + // The result of the request, only valid when `complete` is set. pub result: Option, // The instance ID of the request, only valid in the `SENT` state. @@ -179,16 +349,16 @@ pub enum InitiatorModeState { #[derive(Debug, Default)] pub struct DownloadState { - offset: u32, - length: u32, + pub offset: u32, + pub length: u32, } #[derive(Debug, Default)] pub struct VerifyState { - progress_percent: u8, + pub progress_percent: u8, } #[derive(Debug, Default)] pub struct ApplyState { - progress_percent: u8, + pub progress_percent: u8, }