fw-update: Implement firmware update package parser

PackageParser implements parsing the common elements across version
1 and version 2 of the PackageHeader. PackageParserV1 handles parsing
the package header version 1 as defined in DSP0267_1.0.0 and
DSP0267_1.0.1.

Tested: Unit tests added

Signed-off-by: Tom Joseph <rushtotom@gmail.com>
Change-Id: Iddabd435755f462c042a85a286b1b0a50eb346b1
diff --git a/fw-update/package_parser.cpp b/fw-update/package_parser.cpp
new file mode 100644
index 0000000..13f40bd
--- /dev/null
+++ b/fw-update/package_parser.cpp
@@ -0,0 +1,321 @@
+#include "package_parser.hpp"
+
+#include "libpldm/firmware_update.h"
+#include "libpldm/utils.h"
+
+#include "common/utils.hpp"
+
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <iostream>
+#include <memory>
+
+namespace pldm
+{
+
+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)
+        {
+            std::cerr << "Decoding firmware device ID record failed, RC=" << rc
+                      << "\n";
+            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)
+            {
+                std::cerr
+                    << "Decoding descriptor type, length and value failed, RC="
+                    << rc << "\n";
+                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)
+                {
+                    std::cerr << "Decoding Vendor-defined descriptor value "
+                                 "failed, RC="
+                              << rc << "\n";
+                    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)
+        {
+            std::cerr << "Decoding component image information failed, RC="
+                      << rc << "\n";
+            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)
+        {
+            std::cerr << "Validating the component location offset failed, "
+                         "COMP_VERSION="
+                      << std::get<static_cast<size_t>(
+                             ComponentImageInfoPos::CompVersionPos)>(
+                             componentImageInfo)
+                      << "\n";
+            throw InternalFailure();
+        }
+
+        calcPkgSize += compSize;
+    }
+
+    if (calcPkgSize != pkgSize)
+    {
+        std::cerr
+            << "Package size does not match calculated package size, PKG_SIZE="
+            << pkgSize << " ,CALC_PKG_SIZE=" << calcPkgSize << "\n";
+        throw InternalFailure();
+    }
+}
+
+void PackageParserV1::parse(const std::vector<uint8_t>& pkgHdr,
+                            uintmax_t pkgSize)
+{
+    if (pkgHeaderSize != pkgHdr.size())
+    {
+        std::cerr << "Package header size is invalid, PKG_HDR_SIZE="
+                  << pkgHeaderSize << "\n";
+        throw InternalFailure();
+    }
+
+    size_t offset = sizeof(pldm_package_header_information) + pkgVersion.size();
+    if (offset + sizeof(DeviceIDRecordCount) >= pkgHeaderSize)
+    {
+        std::cerr << "Parsing package header failed, PKG_HDR_SIZE="
+                  << pkgHeaderSize << "\n";
+        throw InternalFailure();
+    }
+
+    auto deviceIdRecCount = static_cast<DeviceIDRecordCount>(pkgHdr[offset]);
+    offset += sizeof(DeviceIDRecordCount);
+
+    offset = parseFDIdentificationArea(deviceIdRecCount, pkgHdr, offset);
+    if (deviceIdRecCount != fwDeviceIDRecords.size())
+    {
+        std::cerr
+            << "DeviceIDRecordCount entries not found, DEVICE_ID_REC_COUNT="
+            << deviceIdRecCount << "\n";
+        throw InternalFailure();
+    }
+    if (offset + sizeof(ComponentImageCount) >= pkgHeaderSize)
+    {
+        std::cerr << "Parsing package header failed, PKG_HDR_SIZE="
+                  << pkgHeaderSize << "\n";
+        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())
+    {
+        std::cerr << "ComponentImageCount entries not found, COMP_IMAGE_COUNT="
+                  << compImageCount << "\n";
+        throw InternalFailure();
+    }
+
+    if (offset + sizeof(PackageHeaderChecksum) != pkgHeaderSize)
+    {
+        std::cerr << "Parsing package header failed, PKG_HDR_SIZE="
+                  << pkgHeaderSize << "\n";
+        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)
+    {
+        std::cerr << "Parsing package header failed, CALC_CHECKSUM="
+                  << calcChecksum << ", PKG_HDR_CHECKSUM=" << checksum << "\n";
+        throw InternalFailure();
+    }
+
+    validatePkgTotalSize(pkgSize);
+}
+
+std::unique_ptr<PackageParser> parsePkgHeader(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)
+    {
+        std::cerr << "Decoding PLDM package header information failed, RC="
+                  << rc << "\n";
+        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
\ No newline at end of file
diff --git a/fw-update/package_parser.hpp b/fw-update/package_parser.hpp
new file mode 100644
index 0000000..7878f4d
--- /dev/null
+++ b/fw-update/package_parser.hpp
@@ -0,0 +1,188 @@
+#pragma once
+
+#include "libpldm/firmware_update.h"
+
+#include "common/types.hpp"
+
+#include <array>
+#include <cstdint>
+#include <memory>
+#include <tuple>
+#include <vector>
+
+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&&) = default;
+    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&&) = default;
+    ~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> parsePkgHeader(std::vector<uint8_t>& pkgHdrInfo);
+
+} // namespace fw_update
+
+} // namespace pldm
diff --git a/fw-update/test/meson.build b/fw-update/test/meson.build
index e88466d..24dc068 100644
--- a/fw-update/test/meson.build
+++ b/fw-update/test/meson.build
@@ -1,12 +1,14 @@
 fw_update_test_src = declare_dependency(
           sources: [
             '../inventory_manager.cpp',
+            '../package_parser.cpp',
             '../../common/utils.cpp',
             '../../pldmd/dbus_impl_requester.cpp',
             '../../pldmd/instance_id.cpp'])
 
 tests = [
   'inventory_manager_test',
+  'package_parser_test',
 ]
 
 foreach t : tests
diff --git a/fw-update/test/package_parser_test.cpp b/fw-update/test/package_parser_test.cpp
new file mode 100644
index 0000000..b20777e
--- /dev/null
+++ b/fw-update/test/package_parser_test.cpp
@@ -0,0 +1,180 @@
+#include "fw-update/package_parser.hpp"
+
+#include <typeinfo>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using namespace pldm::fw_update;
+
+TEST(PackageParser, ValidPkgSingleDescriptorSingleComponent)
+{
+    std::vector<uint8_t> fwPkgHdr{
+        0xF0, 0x18, 0x87, 0x8C, 0xCB, 0x7D, 0x49, 0x43, 0x98, 0x00, 0xA0, 0x2F,
+        0x05, 0x9A, 0xCA, 0x02, 0x01, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x19, 0x0C, 0xE5, 0x07, 0x00, 0x08, 0x00, 0x01, 0x0E,
+        0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E,
+        0x67, 0x31, 0x01, 0x2E, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0E,
+        0x00, 0x00, 0x01, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74,
+        0x72, 0x69, 0x6E, 0x67, 0x32, 0x02, 0x00, 0x10, 0x00, 0x16, 0x20, 0x23,
+        0xC9, 0x3E, 0xC5, 0x41, 0x15, 0x95, 0xF4, 0x48, 0x70, 0x1D, 0x49, 0xD6,
+        0x75, 0x01, 0x00, 0x0A, 0x00, 0x64, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+        0x00, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x01,
+        0x0E, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69,
+        0x6E, 0x67, 0x33, 0x4F, 0x96, 0xAE, 0x56};
+
+    constexpr uintmax_t pkgSize = 166;
+    constexpr std::string_view pkgVersion{"VersionString1"};
+    auto parser = parsePkgHeader(fwPkgHdr);
+    EXPECT_EQ(typeid(*parser).name(), typeid(PackageParserV1).name());
+    EXPECT_EQ(parser->pkgHeaderSize, fwPkgHdr.size());
+    EXPECT_EQ(parser->pkgVersion, pkgVersion);
+
+    parser->parse(fwPkgHdr, pkgSize);
+    auto outfwDeviceIDRecords = parser->getFwDeviceIDRecords();
+    FirmwareDeviceIDRecords fwDeviceIDRecords{
+        {1,
+         {0},
+         "VersionString2",
+         {{PLDM_FWUP_UUID,
+           std::vector<uint8_t>{0x16, 0x20, 0x23, 0xC9, 0x3E, 0xC5, 0x41, 0x15,
+                                0x95, 0xF4, 0x48, 0x70, 0x1D, 0x49, 0xD6,
+                                0x75}}},
+         {}},
+    };
+    EXPECT_EQ(outfwDeviceIDRecords, fwDeviceIDRecords);
+
+    auto outCompImageInfos = parser->getComponentImageInfos();
+    ComponentImageInfos compImageInfos{
+        {10, 100, 0xFFFFFFFF, 0, 0, 139, 27, "VersionString3"}};
+    EXPECT_EQ(outCompImageInfos, compImageInfos);
+}
+
+TEST(PackageParser, ValidPkgMultipleDescriptorsMultipleComponents)
+{
+    std::vector<uint8_t> fwPkgHdr{
+        0xF0, 0x18, 0x87, 0x8C, 0xCB, 0x7D, 0x49, 0x43, 0x98, 0x00, 0xA0, 0x2F,
+        0x05, 0x9A, 0xCA, 0x02, 0x01, 0x46, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x19, 0x0C, 0xE5, 0x07, 0x00, 0x08, 0x00, 0x01, 0x0E,
+        0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E,
+        0x67, 0x31, 0x03, 0x45, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0E,
+        0x00, 0x00, 0x03, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74,
+        0x72, 0x69, 0x6E, 0x67, 0x32, 0x02, 0x00, 0x10, 0x00, 0x12, 0x44, 0xD2,
+        0x64, 0x8D, 0x7D, 0x47, 0x18, 0xA0, 0x30, 0xFC, 0x8A, 0x56, 0x58, 0x7D,
+        0x5B, 0x01, 0x00, 0x04, 0x00, 0x47, 0x16, 0x00, 0x00, 0xFF, 0xFF, 0x0B,
+        0x00, 0x01, 0x07, 0x4F, 0x70, 0x65, 0x6E, 0x42, 0x4D, 0x43, 0x12, 0x34,
+        0x2E, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00, 0x07,
+        0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E,
+        0x67, 0x33, 0x02, 0x00, 0x10, 0x00, 0x12, 0x44, 0xD2, 0x64, 0x8D, 0x7D,
+        0x47, 0x18, 0xA0, 0x30, 0xFC, 0x8A, 0x56, 0x58, 0x7D, 0x5C, 0x2E, 0x00,
+        0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00, 0x01, 0x56, 0x65,
+        0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x34,
+        0x02, 0x00, 0x10, 0x00, 0x12, 0x44, 0xD2, 0x64, 0x8D, 0x7D, 0x47, 0x18,
+        0xA0, 0x30, 0xFC, 0x8A, 0x56, 0x58, 0x7D, 0x5D, 0x03, 0x00, 0x0A, 0x00,
+        0x64, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x46, 0x01,
+        0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x56, 0x65, 0x72, 0x73,
+        0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x35, 0x0A, 0x00,
+        0xC8, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x00, 0x61, 0x01,
+        0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x56, 0x65, 0x72, 0x73,
+        0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x36, 0x10, 0x00,
+        0x2C, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x0C, 0x00, 0x7C, 0x01,
+        0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x56, 0x65, 0x72, 0x73,
+        0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x37, 0xF1, 0x90,
+        0x9C, 0x71};
+
+    constexpr uintmax_t pkgSize = 407;
+    constexpr std::string_view pkgVersion{"VersionString1"};
+    auto parser = parsePkgHeader(fwPkgHdr);
+    EXPECT_EQ(typeid(*parser).name(), typeid(PackageParserV1).name());
+    EXPECT_EQ(parser->pkgHeaderSize, fwPkgHdr.size());
+    EXPECT_EQ(parser->pkgVersion, pkgVersion);
+
+    parser->parse(fwPkgHdr, pkgSize);
+    auto outfwDeviceIDRecords = parser->getFwDeviceIDRecords();
+    FirmwareDeviceIDRecords fwDeviceIDRecords{
+        {1,
+         {0, 1},
+         "VersionString2",
+         {{PLDM_FWUP_UUID,
+           std::vector<uint8_t>{0x12, 0x44, 0xD2, 0x64, 0x8D, 0x7D, 0x47, 0x18,
+                                0xA0, 0x30, 0xFC, 0x8A, 0x56, 0x58, 0x7D,
+                                0x5B}},
+          {PLDM_FWUP_IANA_ENTERPRISE_ID,
+           std::vector<uint8_t>{0x47, 0x16, 0x00, 0x00}},
+          {PLDM_FWUP_VENDOR_DEFINED,
+           std::make_tuple("OpenBMC", std::vector<uint8_t>{0x12, 0x34})}},
+         {}},
+        {0,
+         {0, 1, 2},
+         "VersionString3",
+         {{PLDM_FWUP_UUID,
+           std::vector<uint8_t>{0x12, 0x44, 0xD2, 0x64, 0x8D, 0x7D, 0x47, 0x18,
+                                0xA0, 0x30, 0xFC, 0x8A, 0x56, 0x58, 0x7D,
+                                0x5C}}},
+         {}},
+        {0,
+         {0},
+         "VersionString4",
+         {{PLDM_FWUP_UUID,
+           std::vector<uint8_t>{0x12, 0x44, 0xD2, 0x64, 0x8D, 0x7D, 0x47, 0x18,
+                                0xA0, 0x30, 0xFC, 0x8A, 0x56, 0x58, 0x7D,
+                                0x5D}}},
+         {}},
+    };
+    EXPECT_EQ(outfwDeviceIDRecords, fwDeviceIDRecords);
+
+    auto outCompImageInfos = parser->getComponentImageInfos();
+    ComponentImageInfos compImageInfos{
+        {10, 100, 0xFFFFFFFF, 0, 0, 326, 27, "VersionString5"},
+        {10, 200, 0xFFFFFFFF, 0, 1, 353, 27, "VersionString6"},
+        {16, 300, 0xFFFFFFFF, 1, 12, 380, 27, "VersionString7"}};
+    EXPECT_EQ(outCompImageInfos, compImageInfos);
+}
+
+TEST(PackageParser, InvalidPkgHeaderInfoIncomplete)
+{
+    std::vector<uint8_t> fwPkgHdr{0xF0, 0x18, 0x87, 0x8C, 0xCB, 0x7D,
+                                  0x49, 0x43, 0x98, 0x00, 0xA0, 0x2F,
+                                  0x05, 0x9A, 0xCA, 0x02};
+
+    auto parser = parsePkgHeader(fwPkgHdr);
+    EXPECT_EQ(parser, nullptr);
+}
+
+TEST(PackageParser, InvalidPkgNotSupportedHeaderFormat)
+{
+    std::vector<uint8_t> fwPkgHdr{
+        0x12, 0x44, 0xD2, 0x64, 0x8D, 0x7D, 0x47, 0x18, 0xA0, 0x30,
+        0xFC, 0x8A, 0x56, 0x58, 0x7D, 0x5B, 0x02, 0x8B, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x0C, 0xE5,
+        0x07, 0x00, 0x08, 0x00, 0x01, 0x0E, 0x56, 0x65, 0x72, 0x73,
+        0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x31};
+
+    auto parser = parsePkgHeader(fwPkgHdr);
+    EXPECT_EQ(parser, nullptr);
+}
+
+TEST(PackageParser, InvalidPkgBadChecksum)
+{
+    std::vector<uint8_t> fwPkgHdr{
+        0xF0, 0x18, 0x87, 0x8C, 0xCB, 0x7D, 0x49, 0x43, 0x98, 0x00, 0xA0, 0x2F,
+        0x05, 0x9A, 0xCA, 0x02, 0x01, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x19, 0x0C, 0xE5, 0x07, 0x00, 0x08, 0x00, 0x01, 0x0E,
+        0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69, 0x6E,
+        0x67, 0x31, 0x01, 0x2E, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0E,
+        0x00, 0x00, 0x01, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74,
+        0x72, 0x69, 0x6E, 0x67, 0x32, 0x02, 0x00, 0x10, 0x00, 0x16, 0x20, 0x23,
+        0xC9, 0x3E, 0xC5, 0x41, 0x15, 0x95, 0xF4, 0x48, 0x70, 0x1D, 0x49, 0xD6,
+        0x75, 0x01, 0x00, 0x0A, 0x00, 0x64, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+        0x00, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x01,
+        0x0E, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x72, 0x69,
+        0x6E, 0x67, 0x33, 0x4F, 0x96, 0xAE, 0x57};
+
+    constexpr uintmax_t pkgSize = 166;
+    constexpr std::string_view pkgVersion{"VersionString1"};
+    auto parser = parsePkgHeader(fwPkgHdr);
+    EXPECT_EQ(typeid(*parser).name(), typeid(PackageParserV1).name());
+    EXPECT_EQ(parser->pkgHeaderSize, fwPkgHdr.size());
+    EXPECT_EQ(parser->pkgVersion, pkgVersion);
+    EXPECT_THROW(parser->parse(fwPkgHdr, pkgSize), std::exception);
+}
\ No newline at end of file