fw update: pldm package parser
The code is taken from 'pldm' repo, but there is an ongoing effort to
make it part of libpldm [1].
The intent of this patch is to provide a few high-level functions to
parse a PLDM fw update package.
If/when the package parser is available in libpldm, this code can be
dropped.
References:
- [1] https://gerrit.openbmc.org/c/openbmc/libpldm/+/77095
Tested: next patch in series
Change-Id: I8212d88702e59d9d78965baf4e8c2243b8035b42
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/common/pldm/package_parser.cpp b/common/pldm/package_parser.cpp
new file mode 100644
index 0000000..6c04df3
--- /dev/null
+++ b/common/pldm/package_parser.cpp
@@ -0,0 +1,351 @@
+#include "package_parser.hpp"
+
+#include <libpldm/firmware_update.h>
+#include <libpldm/utils.h>
+
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <memory>
+
+// NOLINTBEGIN
+
+using namespace std;
+
+PHOSPHOR_LOG2_USING;
+
+namespace pldm
+{
+
+namespace utils
+{
+
+std::string toString(const struct variable_field& var)
+{
+ if (var.ptr == nullptr || !var.length)
+ {
+ return "";
+ }
+
+ std::string str(reinterpret_cast<const char*>(var.ptr), var.length);
+ std::replace_if(
+ str.begin(), str.end(), [](const char& c) { return !isprint(c); }, ' ');
+ return str;
+}
+
+} // namespace utils
+
+namespace fw_update
+{
+
+using InternalFailure =
+ sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+
+size_t PackageParser::parseFDIdentificationArea(
+ DeviceIDRecordCount deviceIdRecCount, const std::vector<uint8_t>& pkgHdr,
+ size_t offset)
+{
+ size_t pkgHdrRemainingSize = pkgHdr.size() - offset;
+
+ while (deviceIdRecCount-- && (pkgHdrRemainingSize > 0))
+ {
+ pldm_firmware_device_id_record deviceIdRecHeader{};
+ variable_field applicableComponents{};
+ variable_field compImageSetVersionStr{};
+ variable_field recordDescriptors{};
+ variable_field fwDevicePkgData{};
+
+ auto rc = decode_firmware_device_id_record(
+ pkgHdr.data() + offset, pkgHdrRemainingSize,
+ componentBitmapBitLength, &deviceIdRecHeader, &applicableComponents,
+ &compImageSetVersionStr, &recordDescriptors, &fwDevicePkgData);
+ if (rc)
+ {
+ error(
+ "Failed to decode firmware device ID record, response code '{RC}'",
+ "RC", rc);
+ throw InternalFailure();
+ }
+
+ Descriptors descriptors{};
+ while (deviceIdRecHeader.descriptor_count-- &&
+ (recordDescriptors.length > 0))
+ {
+ uint16_t descriptorType = 0;
+ variable_field descriptorData{};
+
+ rc = decode_descriptor_type_length_value(
+ recordDescriptors.ptr, recordDescriptors.length,
+ &descriptorType, &descriptorData);
+ if (rc)
+ {
+ error(
+ "Failed to decode descriptor type value of type '{TYPE}' and length '{LENGTH}', response code '{RC}'",
+ "TYPE", descriptorType, "LENGTH", recordDescriptors.length,
+ "RC", rc);
+ throw InternalFailure();
+ }
+
+ if (descriptorType != PLDM_FWUP_VENDOR_DEFINED)
+ {
+ descriptors.emplace(
+ descriptorType,
+ DescriptorData{descriptorData.ptr,
+ descriptorData.ptr + descriptorData.length});
+ }
+ else
+ {
+ uint8_t descTitleStrType = 0;
+ variable_field descTitleStr{};
+ variable_field vendorDefinedDescData{};
+
+ rc = decode_vendor_defined_descriptor_value(
+ descriptorData.ptr, descriptorData.length,
+ &descTitleStrType, &descTitleStr, &vendorDefinedDescData);
+ if (rc)
+ {
+ error(
+ "Failed to decode vendor-defined descriptor value of type '{TYPE}' and length '{LENGTH}', response code '{RC}'",
+ "TYPE", descriptorType, "LENGTH",
+ recordDescriptors.length, "RC", rc);
+ throw InternalFailure();
+ }
+
+ descriptors.emplace(
+ descriptorType,
+ std::make_tuple(utils::toString(descTitleStr),
+ VendorDefinedDescriptorData{
+ vendorDefinedDescData.ptr,
+ vendorDefinedDescData.ptr +
+ vendorDefinedDescData.length}));
+ }
+
+ auto nextDescriptorOffset =
+ sizeof(pldm_descriptor_tlv().descriptor_type) +
+ sizeof(pldm_descriptor_tlv().descriptor_length) +
+ descriptorData.length;
+ recordDescriptors.ptr += nextDescriptorOffset;
+ recordDescriptors.length -= nextDescriptorOffset;
+ }
+
+ DeviceUpdateOptionFlags deviceUpdateOptionFlags =
+ deviceIdRecHeader.device_update_option_flags.value;
+
+ ApplicableComponents componentsList;
+
+ for (size_t varBitfieldIdx = 0;
+ varBitfieldIdx < applicableComponents.length; varBitfieldIdx++)
+ {
+ std::bitset<8> entry{*(applicableComponents.ptr + varBitfieldIdx)};
+ for (size_t idx = 0; idx < entry.size(); idx++)
+ {
+ if (entry[idx])
+ {
+ componentsList.emplace_back(
+ idx + (varBitfieldIdx * entry.size()));
+ }
+ }
+ }
+
+ fwDeviceIDRecords.emplace_back(std::make_tuple(
+ deviceUpdateOptionFlags, componentsList,
+ utils::toString(compImageSetVersionStr), std::move(descriptors),
+ FirmwareDevicePackageData{
+ fwDevicePkgData.ptr,
+ fwDevicePkgData.ptr + fwDevicePkgData.length}));
+ offset += deviceIdRecHeader.record_length;
+ pkgHdrRemainingSize -= deviceIdRecHeader.record_length;
+ }
+
+ return offset;
+}
+
+size_t PackageParser::parseCompImageInfoArea(ComponentImageCount compImageCount,
+ const std::vector<uint8_t>& pkgHdr,
+ size_t offset)
+{
+ size_t pkgHdrRemainingSize = pkgHdr.size() - offset;
+
+ while (compImageCount-- && (pkgHdrRemainingSize > 0))
+ {
+ pldm_component_image_information compImageInfo{};
+ variable_field compVersion{};
+
+ auto rc = decode_pldm_comp_image_info(
+ pkgHdr.data() + offset, pkgHdrRemainingSize, &compImageInfo,
+ &compVersion);
+ if (rc)
+ {
+ error(
+ "Failed to decode component image information, response code '{RC}'",
+ "RC", rc);
+ throw InternalFailure();
+ }
+
+ CompClassification compClassification =
+ compImageInfo.comp_classification;
+ CompIdentifier compIdentifier = compImageInfo.comp_identifier;
+ CompComparisonStamp compComparisonTime =
+ compImageInfo.comp_comparison_stamp;
+ CompOptions compOptions = compImageInfo.comp_options.value;
+ ReqCompActivationMethod reqCompActivationMethod =
+ compImageInfo.requested_comp_activation_method.value;
+ CompLocationOffset compLocationOffset =
+ compImageInfo.comp_location_offset;
+ CompSize compSize = compImageInfo.comp_size;
+
+ componentImageInfos.emplace_back(std::make_tuple(
+ compClassification, compIdentifier, compComparisonTime, compOptions,
+ reqCompActivationMethod, compLocationOffset, compSize,
+ utils::toString(compVersion)));
+ offset += sizeof(pldm_component_image_information) +
+ compImageInfo.comp_version_string_length;
+ pkgHdrRemainingSize -= sizeof(pldm_component_image_information) +
+ compImageInfo.comp_version_string_length;
+ }
+
+ return offset;
+}
+
+void PackageParser::validatePkgTotalSize(uintmax_t pkgSize)
+{
+ uintmax_t calcPkgSize = pkgHeaderSize;
+ for (const auto& componentImageInfo : componentImageInfos)
+ {
+ CompLocationOffset compLocOffset = std::get<static_cast<size_t>(
+ ComponentImageInfoPos::CompLocationOffsetPos)>(componentImageInfo);
+ CompSize compSize =
+ std::get<static_cast<size_t>(ComponentImageInfoPos::CompSizePos)>(
+ componentImageInfo);
+
+ if (compLocOffset != calcPkgSize)
+ {
+ auto cmpVersion = std::get<static_cast<size_t>(
+ ComponentImageInfoPos::CompVersionPos)>(componentImageInfo);
+ error(
+ "Failed to validate the component location offset '{OFFSET}' for version '{COMPONENT_VERSION}' and package size '{SIZE}'",
+ "OFFSET", compLocOffset, "COMPONENT_VERSION", cmpVersion,
+ "SIZE", calcPkgSize);
+ throw InternalFailure();
+ }
+
+ calcPkgSize += compSize;
+ }
+
+ if (calcPkgSize != pkgSize)
+ {
+ error(
+ "Failed to match package size '{PKG_SIZE}' to calculated package size '{CALCULATED_PACKAGE_SIZE}'.",
+ "PKG_SIZE", pkgSize, "CALCULATED_PACKAGE_SIZE", calcPkgSize);
+ throw InternalFailure();
+ }
+}
+
+void PackageParserV1::parse(const std::vector<uint8_t>& pkgHdr,
+ uintmax_t pkgSize)
+{
+ if (pkgHeaderSize != pkgHdr.size())
+ {
+ error("Invalid package header size '{PKG_HDR_SIZE}' ", "PKG_HDR_SIZE",
+ pkgHeaderSize);
+ throw InternalFailure();
+ }
+
+ size_t offset = sizeof(pldm_package_header_information) + pkgVersion.size();
+ if (offset + sizeof(DeviceIDRecordCount) >= pkgHeaderSize)
+ {
+ error("Failed to parse package header of size '{PKG_HDR_SIZE}'",
+ "PKG_HDR_SIZE", pkgHeaderSize);
+ throw InternalFailure();
+ }
+
+ auto deviceIdRecCount = static_cast<DeviceIDRecordCount>(pkgHdr[offset]);
+ offset += sizeof(DeviceIDRecordCount);
+
+ offset = parseFDIdentificationArea(deviceIdRecCount, pkgHdr, offset);
+ if (deviceIdRecCount != fwDeviceIDRecords.size())
+ {
+ error("Failed to find DeviceIDRecordCount {DREC_CNT} entries",
+ "DREC_CNT", deviceIdRecCount);
+ throw InternalFailure();
+ }
+ if (offset + sizeof(ComponentImageCount) >= pkgHeaderSize)
+ {
+ error("Failed to parsing package header of size '{PKG_HDR_SIZE}'",
+ "PKG_HDR_SIZE", pkgHeaderSize);
+ throw InternalFailure();
+ }
+
+ auto compImageCount = static_cast<ComponentImageCount>(
+ le16toh(pkgHdr[offset] | (pkgHdr[offset + 1] << 8)));
+ offset += sizeof(ComponentImageCount);
+
+ offset = parseCompImageInfoArea(compImageCount, pkgHdr, offset);
+ if (compImageCount != componentImageInfos.size())
+ {
+ error("Failed to find ComponentImageCount '{COMP_IMG_CNT}' entries",
+ "COMP_IMG_CNT", compImageCount);
+ throw InternalFailure();
+ }
+
+ if (offset + sizeof(PackageHeaderChecksum) != pkgHeaderSize)
+ {
+ error("Failed to parse package header of size '{PKG_HDR_SIZE}'",
+ "PKG_HDR_SIZE", pkgHeaderSize);
+ throw InternalFailure();
+ }
+
+ auto calcChecksum = crc32(pkgHdr.data(), offset);
+ auto checksum = static_cast<PackageHeaderChecksum>(
+ le32toh(pkgHdr[offset] | (pkgHdr[offset + 1] << 8) |
+ (pkgHdr[offset + 2] << 16) | (pkgHdr[offset + 3] << 24)));
+ if (calcChecksum != checksum)
+ {
+ error(
+ "Failed to parse package header for calculated checksum '{CALCULATED_CHECKSUM}' and header checksum '{PACKAGE_HEADER_CHECKSUM}'",
+ "CALCULATED_CHECKSUM", calcChecksum, "PACKAGE_HEADER_CHECKSUM",
+ checksum);
+ throw InternalFailure();
+ }
+
+ validatePkgTotalSize(pkgSize);
+}
+
+std::unique_ptr<PackageParser> parsePackageHeader(std::vector<uint8_t>& pkgData)
+{
+ constexpr std::array<uint8_t, PLDM_FWUP_UUID_LENGTH> hdrIdentifierv1{
+ 0xF0, 0x18, 0x87, 0x8C, 0xCB, 0x7D, 0x49, 0x43,
+ 0x98, 0x00, 0xA0, 0x2F, 0x05, 0x9A, 0xCA, 0x02};
+ constexpr uint8_t pkgHdrVersion1 = 0x01;
+
+ pldm_package_header_information pkgHeader{};
+ variable_field pkgVersion{};
+ auto rc = decode_pldm_package_header_info(pkgData.data(), pkgData.size(),
+ &pkgHeader, &pkgVersion);
+ if (rc)
+ {
+ error(
+ "Failed to decode PLDM package header information, response code '{RC}'",
+ "RC", rc);
+ return nullptr;
+ }
+
+ if (std::equal(pkgHeader.uuid, pkgHeader.uuid + PLDM_FWUP_UUID_LENGTH,
+ hdrIdentifierv1.begin(), hdrIdentifierv1.end()) &&
+ (pkgHeader.package_header_format_version == pkgHdrVersion1))
+ {
+ PackageHeaderSize pkgHdrSize = pkgHeader.package_header_size;
+ ComponentBitmapBitLength componentBitmapBitLength =
+ pkgHeader.component_bitmap_bit_length;
+ return std::make_unique<PackageParserV1>(
+ pkgHdrSize, utils::toString(pkgVersion), componentBitmapBitLength);
+ }
+
+ return nullptr;
+}
+
+} // namespace fw_update
+
+} // namespace pldm
+
+// NOLINTEND