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/meson.build b/common/meson.build
new file mode 100644
index 0000000..766fbe0
--- /dev/null
+++ b/common/meson.build
@@ -0,0 +1,2 @@
+
+subdir('pldm')
diff --git a/common/pldm/meson.build b/common/pldm/meson.build
new file mode 100644
index 0000000..973dad2
--- /dev/null
+++ b/common/pldm/meson.build
@@ -0,0 +1,13 @@
+
+libpldmutil = static_library('pldmpackageutil',
+  'package_parser.cpp',
+  'pldm_package_util.cpp',
+  include_directories: ['.'],
+  dependencies: [
+    pdi_dep,
+    phosphor_logging_dep,
+    sdbusplus_dep,
+    libpldm_dep,
+  ],
+  install: false,
+)
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
diff --git a/common/pldm/package_parser.hpp b/common/pldm/package_parser.hpp
new file mode 100644
index 0000000..67f9b95
--- /dev/null
+++ b/common/pldm/package_parser.hpp
@@ -0,0 +1,192 @@
+#pragma once
+
+#include "types.hpp"
+
+#include <libpldm/firmware_update.h>
+
+#include <array>
+#include <cstdint>
+#include <memory>
+#include <tuple>
+#include <vector>
+
+// NOLINTBEGIN
+
+namespace pldm
+{
+
+namespace fw_update
+{
+
+/** @class PackageParser
+ *
+ *  PackageParser is the abstract base class for parsing the PLDM firmware
+ *  update package. The PLDM firmware update contains two major sections; the
+ *  firmware package header, and the firmware package payload. Each package
+ *  header version will have a concrete implementation of the PackageParser.
+ *  The concrete implementation understands the format of the package header and
+ *  will implement the parse API.
+ */
+class PackageParser
+{
+  public:
+    PackageParser() = delete;
+    PackageParser(const PackageParser&) = delete;
+    PackageParser(PackageParser&&) = default;
+    PackageParser& operator=(const PackageParser&) = delete;
+    PackageParser& operator=(PackageParser&&) = delete;
+    virtual ~PackageParser() = default;
+
+    /** @brief Constructor
+     *
+     *  @param[in] pkgHeaderSize - Size of package header section
+     *  @param[in] pkgVersion - Package version
+     *  @param[in] componentBitmapBitLength - The number of bits used to
+     *                                        represent the bitmap in the
+     *                                        ApplicableComponents field for a
+     *                                        matching device.
+     */
+    explicit PackageParser(PackageHeaderSize pkgHeaderSize,
+                           const PackageVersion& pkgVersion,
+                           ComponentBitmapBitLength componentBitmapBitLength) :
+        pkgHeaderSize(pkgHeaderSize), pkgVersion(pkgVersion),
+        componentBitmapBitLength(componentBitmapBitLength)
+    {}
+
+    /** @brief Parse the firmware update package header
+     *
+     *  @param[in] pkgHdr - Package header
+     *  @param[in] pkgSize - Size of the firmware update package
+     *
+     *  @note Throws exception is parsing fails
+     */
+    virtual void parse(const std::vector<uint8_t>& pkgHdr,
+                       uintmax_t pkgSize) = 0;
+
+    /** @brief Get firmware device ID records from the package
+     *
+     *  @return if parsing the package is successful, return firmware device ID
+     *          records
+     */
+    const FirmwareDeviceIDRecords& getFwDeviceIDRecords() const
+    {
+        return fwDeviceIDRecords;
+    }
+
+    /** @brief Get component image information from the package
+     *
+     *  @return if parsing the package is successful, return component image
+     *          information
+     */
+    const ComponentImageInfos& getComponentImageInfos() const
+    {
+        return componentImageInfos;
+    }
+
+    /** @brief Device identifiers of the managed FDs */
+    const PackageHeaderSize pkgHeaderSize;
+
+    /** @brief Package version string */
+    const PackageVersion pkgVersion;
+
+  protected:
+    /** @brief Parse the firmware device identification area
+     *
+     *  @param[in] deviceIdRecCount - count of firmware device ID records
+     *  @param[in] pkgHdr - firmware package header
+     *  @param[in] offset - offset in package header which is the start of the
+     *                      firmware device identification area
+     *
+     *  @return On success return the offset which is the end of the firmware
+     *          device identification area, on error throw exception.
+     */
+    size_t parseFDIdentificationArea(DeviceIDRecordCount deviceIdRecCount,
+                                     const std::vector<uint8_t>& pkgHdr,
+                                     size_t offset);
+
+    /** @brief Parse the component image information area
+     *
+     *  @param[in] compImageCount - component image count
+     *  @param[in] pkgHdr - firmware package header
+     *  @param[in] offset - offset in package header which is the start of the
+     *                      component image information area
+     *
+     *  @return On success return the offset which is the end of the component
+     *          image information area, on error throw exception.
+     */
+    size_t parseCompImageInfoArea(ComponentImageCount compImageCount,
+                                  const std::vector<uint8_t>& pkgHdr,
+                                  size_t offset);
+
+    /** @brief Validate the total size of the package
+     *
+     *  Verify the total size of the package is the sum of package header and
+     *  the size of each component.
+     *
+     *  @param[in] pkgSize - firmware update package size
+     *
+     *  @note Throws exception if validation fails
+     */
+    void validatePkgTotalSize(uintmax_t pkgSize);
+
+    /** @brief Firmware Device ID Records in the package */
+    FirmwareDeviceIDRecords fwDeviceIDRecords;
+
+    /** @brief Component Image Information in the package */
+    ComponentImageInfos componentImageInfos;
+
+    /** @brief The number of bits that will be used to represent the bitmap in
+     *         the ApplicableComponents field for matching device. The value
+     *         shall be a multiple of 8 and be large enough to contain a bit
+     *         for each component in the package.
+     */
+    const ComponentBitmapBitLength componentBitmapBitLength;
+};
+
+/** @class PackageParserV1
+ *
+ *  This class implements the package parser for the header format version 0x01
+ */
+class PackageParserV1 final : public PackageParser
+{
+  public:
+    PackageParserV1() = delete;
+    PackageParserV1(const PackageParserV1&) = delete;
+    PackageParserV1(PackageParserV1&&) = default;
+    PackageParserV1& operator=(const PackageParserV1&) = delete;
+    PackageParserV1& operator=(PackageParserV1&&) = delete;
+    ~PackageParserV1() = default;
+
+    /** @brief Constructor
+     *
+     *  @param[in] pkgHeaderSize - Size of package header section
+     *  @param[in] pkgVersion - Package version
+     *  @param[in] componentBitmapBitLength - The number of bits used to
+     *                                        represent the bitmap in the
+     *                                        ApplicableComponents field for a
+     *                                        matching device.
+     */
+    explicit PackageParserV1(
+        PackageHeaderSize pkgHeaderSize, const PackageVersion& pkgVersion,
+        ComponentBitmapBitLength componentBitmapBitLength) :
+        PackageParser(pkgHeaderSize, pkgVersion, componentBitmapBitLength)
+    {}
+
+    virtual void parse(const std::vector<uint8_t>& pkgHdr, uintmax_t pkgSize);
+};
+
+/** @brief Parse the package header information
+ *
+ *  @param[in] pkgHdrInfo - package header information section in the package
+ *
+ *  @return On success return the PackageParser for the header format version
+ *          on failure return nullptr
+ */
+std::unique_ptr<PackageParser>
+    parsePackageHeader(std::vector<uint8_t>& pkgHdrInfo);
+
+} // namespace fw_update
+
+} // namespace pldm
+
+// NOLINTEND
diff --git a/common/pldm/pldm_package_util.cpp b/common/pldm/pldm_package_util.cpp
new file mode 100644
index 0000000..c3e92e5
--- /dev/null
+++ b/common/pldm/pldm_package_util.cpp
@@ -0,0 +1,259 @@
+#include "pldm_package_util.hpp"
+
+#include "package_parser.hpp"
+#include "types.hpp"
+
+#include <fcntl.h>
+#include <libpldm/base.h>
+#include <libpldm/firmware_update.h>
+#include <libpldm/pldm_types.h>
+#include <libpldm/utils.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/native_types.hpp>
+
+#include <cassert>
+#include <cstring>
+
+using namespace pldm::fw_update;
+
+namespace pldm_package_util
+{
+
+std::shared_ptr<PackageParser> parsePLDMPackage(const uint8_t* buf, size_t size)
+{
+    std::vector<uint8_t> pkgData;
+
+    pkgData.reserve(size);
+    for (size_t i = 0; i < size; i++)
+    {
+        pkgData.push_back(buf[i]);
+    }
+
+    lg2::debug("parsing package header");
+
+    std::unique_ptr<PackageParser> packageParser =
+        pldm::fw_update::parsePackageHeader(pkgData);
+
+    if (packageParser == nullptr)
+    {
+        lg2::error("could not parse package header");
+        return packageParser;
+    }
+
+    lg2::debug("parsing package, pkg header size: {N}", "N",
+               packageParser->pkgHeaderSize);
+
+    std::vector<uint8_t> pkgHeaderOnly;
+    pkgHeaderOnly.insert(
+        pkgHeaderOnly.end(), pkgData.begin(),
+        pkgData.begin() + (long)(packageParser->pkgHeaderSize));
+
+    packageParser->parse(pkgHeaderOnly, pkgData.size());
+
+    return packageParser;
+}
+
+int readImagePackage(FILE* file, uint8_t* package_data,
+                     const size_t package_size)
+{
+    if (file == NULL || package_data == NULL)
+    {
+        return 1;
+    }
+
+    if (package_size == 0)
+    {
+        lg2::error("Package size is 0");
+        return 1;
+    }
+
+    lg2::debug("reading {NBYTES} bytes from file", "NBYTES", package_size);
+
+    // Read the package into memory
+    if (fread(package_data, 1, package_size, file) != package_size)
+    {
+        perror("Failed to read package data");
+        fclose(file);
+        return 1;
+    }
+
+    return 0;
+}
+
+void* mmapImagePackage(sdbusplus::message::unix_fd image, size_t* size_out)
+{
+    lg2::debug("open fd {FD}", "FD", int(image));
+
+    off_t size = lseek(image.fd, 0, SEEK_END);
+
+    if (size < 0)
+    {
+        lg2::error("failed to determine file size");
+        perror("error:");
+        return NULL;
+    }
+
+    *size_out = size;
+
+    lg2::debug("file size: {SIZE}", "SIZE", (uint64_t)size);
+
+    void* data = mmap(nullptr, size, PROT_READ, MAP_SHARED, image.fd, 0);
+
+    if (data == MAP_FAILED)
+    {
+        lg2::error("could not mmap the image");
+        return NULL;
+    }
+
+    return data;
+}
+
+bool fwDeviceIDRecordMatchesCompatible(const FirmwareDeviceIDRecord& record,
+                                       const std::string& compatible)
+{
+    Descriptors desc = std::get<3>(record);
+    if (desc.empty())
+    {
+        return false;
+    }
+
+    if (!desc.contains(0xffff))
+    {
+        return false;
+    }
+
+    auto v = desc[0xffff];
+
+    if (!std::holds_alternative<VendorDefinedDescriptorInfo>(v))
+    {
+        lg2::debug(
+            "descriptor does not have the vendor defined descriptor info");
+        return false;
+    }
+
+    auto data = std::get<VendorDefinedDescriptorInfo>(v);
+
+    std::string actualCompatible = std::get<VendorDefinedDescriptorTitle>(data);
+
+    return compatible == actualCompatible;
+}
+
+bool fwDeviceIDRecordMatchesIANA(const FirmwareDeviceIDRecord& record,
+                                 uint32_t vendorIANA)
+{
+    Descriptors desc = std::get<3>(record);
+
+    if (desc.empty())
+    {
+        return false;
+    }
+
+    if (!desc.contains(0x1))
+    {
+        lg2::error("did not find iana enterprise id");
+        return false;
+    }
+
+    auto viana = desc[0x1];
+
+    if (!std::holds_alternative<DescriptorData>(viana))
+    {
+        lg2::error("did not find iana enterprise id");
+        return false;
+    }
+
+    const DescriptorData& dd = std::get<DescriptorData>(viana);
+
+    if (dd.size() != 4)
+    {
+        lg2::error("descriptor data wrong size ( != 4) for vendor iana");
+        return false;
+    }
+
+    const uint32_t actualIANA = dd[0] | dd[1] << 8 | dd[2] << 16 | dd[3] << 24;
+
+    return actualIANA == vendorIANA;
+}
+
+bool fwDeviceIDRecordMatches(const FirmwareDeviceIDRecord& record,
+                             uint32_t vendorIANA, const std::string& compatible)
+{
+    return fwDeviceIDRecordMatchesIANA(record, vendorIANA) &&
+           fwDeviceIDRecordMatchesCompatible(record, compatible);
+}
+
+ssize_t findMatchingDeviceDescriptorIndex(
+    const FirmwareDeviceIDRecords& records, uint32_t vendorIANA,
+    const std::string& compatible)
+{
+    for (size_t i = 0; i < records.size(); i++)
+    {
+        const FirmwareDeviceIDRecord& record = records[i];
+        if (fwDeviceIDRecordMatches(record, vendorIANA, compatible))
+        {
+            return (ssize_t)i;
+        }
+    }
+    return -1;
+}
+
+int extractMatchingComponentImage(
+    const std::shared_ptr<PackageParser>& packageParser,
+    const std::string& compatible, uint32_t vendorIANA,
+    uint32_t* component_offset_out, size_t* component_size_out)
+{
+    const FirmwareDeviceIDRecords& fwDeviceIdRecords =
+        packageParser->getFwDeviceIDRecords();
+
+    // find fw descriptor matching vendor iana and compatible
+    ssize_t deviceDescriptorIndex = findMatchingDeviceDescriptorIndex(
+        fwDeviceIdRecords, vendorIANA, compatible);
+
+    if (deviceDescriptorIndex < 0)
+    {
+        lg2::error(
+            "did not find a matching device descriptor for {IANA}, {COMPATIBLE}",
+            "IANA", lg2::hex, vendorIANA, "COMPATIBLE", compatible);
+        return EXIT_FAILURE;
+    }
+
+    const FirmwareDeviceIDRecord& descriptor =
+        fwDeviceIdRecords[deviceDescriptorIndex];
+    // find applicable components
+    // iterate over components to find the applicable component
+
+    ApplicableComponents ac = std::get<1>(descriptor);
+
+    if (ac.empty())
+    {
+        lg2::error("did not find an applicable component image for the device");
+        return EXIT_FAILURE;
+    }
+
+    // component is 0 based index
+    const size_t component = ac[0];
+
+    const ComponentImageInfos& cs = packageParser->getComponentImageInfos();
+
+    if (component >= cs.size())
+    {
+        lg2::error("applicable component out of bounds");
+        return EXIT_FAILURE;
+    }
+
+    const ComponentImageInfo& c = cs[component];
+
+    CompLocationOffset off = std::get<5>(c);
+    CompSize size = std::get<6>(c);
+
+    *component_offset_out = off;
+    *component_size_out = size;
+
+    return EXIT_SUCCESS;
+}
+
+} // namespace pldm_package_util
diff --git a/common/pldm/pldm_package_util.hpp b/common/pldm/pldm_package_util.hpp
new file mode 100644
index 0000000..cff49ad
--- /dev/null
+++ b/common/pldm/pldm_package_util.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "package_parser.hpp"
+#include "sdbusplus/message/native_types.hpp"
+
+#include <cstdint>
+#include <memory>
+
+using namespace pldm::fw_update;
+
+namespace pldm_package_util
+{
+
+// @param buf           pointer to the pldm package
+// @param size          size of 'buf'
+// @returns             PackageParser instance
+std::shared_ptr<PackageParser>
+    parsePLDMPackage(const uint8_t* buf, size_t size);
+
+// reads into a buffer, from file
+// @param file            the file to read from
+// @param package_data    the pre-allocated buffer for the package data
+// @param package_size    how many bytes to read from the file
+int readImagePackage(FILE* file, uint8_t* package_data, size_t package_size);
+
+// @param image        file descriptor to the package
+// @param size_out     function will write the size of the package here
+// @returns            a pointer to the mmapped pldm package
+void* mmapImagePackage(sdbusplus::message::unix_fd image, size_t* size_out);
+
+// @param packageParser          PackageParser instance
+// @param compatible             'compatible' string of device
+// @param vendorIANA             vendor iana of device
+// @param component_offset_out   function returns offset of component image
+// @param component_size_out     function returns size of component image
+// @returns                      0 on success
+int extractMatchingComponentImage(
+    const std::shared_ptr<PackageParser>& packageParser,
+    const std::string& compatible, uint32_t vendorIANA,
+    uint32_t* component_offset_out, size_t* size_out);
+
+} // namespace pldm_package_util
diff --git a/common/pldm/types.hpp b/common/pldm/types.hpp
new file mode 100644
index 0000000..8a8f405
--- /dev/null
+++ b/common/pldm/types.hpp
@@ -0,0 +1,92 @@
+#pragma once
+
+#include <sdbusplus/message/types.hpp>
+
+#include <bitset>
+#include <cstdint>
+#include <map>
+#include <string>
+#include <unordered_map>
+#include <variant>
+#include <vector>
+
+// NOLINTBEGIN
+
+namespace pldm
+{
+
+using eid = uint8_t;
+
+namespace fw_update
+{
+
+// Descriptor definition
+using DescriptorType = uint16_t;
+using DescriptorData = std::vector<uint8_t>;
+using VendorDefinedDescriptorTitle = std::string;
+using VendorDefinedDescriptorData = std::vector<uint8_t>;
+using VendorDefinedDescriptorInfo =
+    std::tuple<VendorDefinedDescriptorTitle, VendorDefinedDescriptorData>;
+using Descriptors =
+    std::map<DescriptorType,
+             std::variant<DescriptorData, VendorDefinedDescriptorInfo>>;
+
+using DescriptorMap = std::unordered_map<eid, Descriptors>;
+
+// Component information
+using CompClassification = uint16_t;
+using CompIdentifier = uint16_t;
+using CompKey = std::pair<CompClassification, CompIdentifier>;
+using CompClassificationIndex = uint8_t;
+using ComponentInfo = std::map<CompKey, CompClassificationIndex>;
+using ComponentInfoMap = std::unordered_map<eid, ComponentInfo>;
+
+// PackageHeaderInformation
+using PackageHeaderSize = size_t;
+using PackageVersion = std::string;
+using ComponentBitmapBitLength = uint16_t;
+using PackageHeaderChecksum = uint32_t;
+
+// FirmwareDeviceIDRecords
+using DeviceIDRecordCount = uint8_t;
+using DeviceUpdateOptionFlags = std::bitset<32>;
+using ApplicableComponents = std::vector<size_t>;
+using ComponentImageSetVersion = std::string;
+using FirmwareDevicePackageData = std::vector<uint8_t>;
+using FirmwareDeviceIDRecord =
+    std::tuple<DeviceUpdateOptionFlags, ApplicableComponents,
+               ComponentImageSetVersion, Descriptors,
+               FirmwareDevicePackageData>;
+using FirmwareDeviceIDRecords = std::vector<FirmwareDeviceIDRecord>;
+
+// ComponentImageInformation
+using ComponentImageCount = uint16_t;
+using CompComparisonStamp = uint32_t;
+using CompOptions = std::bitset<16>;
+using ReqCompActivationMethod = std::bitset<16>;
+using CompLocationOffset = uint32_t;
+using CompSize = uint32_t;
+using CompVersion = std::string;
+using ComponentImageInfo =
+    std::tuple<CompClassification, CompIdentifier, CompComparisonStamp,
+               CompOptions, ReqCompActivationMethod, CompLocationOffset,
+               CompSize, CompVersion>;
+using ComponentImageInfos = std::vector<ComponentImageInfo>;
+
+enum class ComponentImageInfoPos : size_t
+{
+    CompClassificationPos = 0,
+    CompIdentifierPos = 1,
+    CompComparisonStampPos = 2,
+    CompOptionsPos = 3,
+    ReqCompActivationMethodPos = 4,
+    CompLocationOffsetPos = 5,
+    CompSizePos = 6,
+    CompVersionPos = 7,
+};
+
+} // namespace fw_update
+
+} // namespace pldm
+
+// NOLINTEND
diff --git a/meson.build b/meson.build
index 9214b48..74245c0 100644
--- a/meson.build
+++ b/meson.build
@@ -71,5 +71,7 @@
 common_include = include_directories('.')
 
 if build_tests.allowed()
+  subdir('common')
   subdir('test')
 endif
+