fw update: pldm package parser tests
Test that the package parser correctly parses a pldm fw package.
https://github.com/openbmc/docs/blob/master/designs/code-update.md
Tested: unit tests pass
Change-Id: If21cd4c7136a7a0297eb4b3cadbec92c277af27c
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/test/common/meson.build b/test/common/meson.build
new file mode 100644
index 0000000..f808149
--- /dev/null
+++ b/test/common/meson.build
@@ -0,0 +1 @@
+subdir('pldm')
diff --git a/test/common/pldm/meson.build b/test/common/pldm/meson.build
new file mode 100644
index 0000000..519d110
--- /dev/null
+++ b/test/common/pldm/meson.build
@@ -0,0 +1,27 @@
+
+testcases = [
+ 'test_pldm_package_parser',
+]
+
+foreach t : testcases
+ test(
+ t,
+ executable(
+ t,
+ f'@t@.cpp',
+ include_directories: [
+ common_include,
+ ],
+ dependencies: [
+ libpldm_dep,
+ phosphor_logging_dep,
+ gtest,
+ ],
+ link_with: [
+ libpldmutil,
+ libpldmcreatepkg,
+ ]
+ )
+ )
+endforeach
+
diff --git a/test/common/pldm/test_pldm_package_parser.cpp b/test/common/pldm/test_pldm_package_parser.cpp
new file mode 100644
index 0000000..5f81e6a
--- /dev/null
+++ b/test/common/pldm/test_pldm_package_parser.cpp
@@ -0,0 +1,283 @@
+#include "common/pldm/package_parser.hpp"
+#include "common/pldm/pldm_package_util.hpp"
+#include "common/pldm/types.hpp"
+#include "test/create_package/create_pldm_fw_package.hpp"
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <cassert>
+#include <cstdlib>
+#include <cstring>
+
+#include "gtest/gtest.h"
+
+std::string getCompatibleFromPLDMPackage(
+ const std::shared_ptr<PackageParser>& packageParser)
+{
+ auto fwDeviceIdRecords = packageParser->getFwDeviceIDRecords();
+ if (fwDeviceIdRecords.size() != 1)
+ {
+ lg2::error(
+ "fw device id records != 1, rejecting package, incompatible.");
+ return "";
+ }
+
+ lg2::debug("package has {N} fw device id records", "N",
+ fwDeviceIdRecords.size());
+
+ assert(!fwDeviceIdRecords.empty());
+
+ for (FirmwareDeviceIDRecord& record : fwDeviceIdRecords)
+ {
+ Descriptors desc = std::get<3>(record);
+ if (desc.empty())
+ {
+ continue;
+ }
+
+ lg2::debug("fw device id record has {N} descriptors", "N", desc.size());
+
+ if (!desc.contains(0xffff))
+ {
+ continue;
+ }
+
+ auto v = desc[0xffff];
+
+ if (!std::holds_alternative<VendorDefinedDescriptorInfo>(v))
+ {
+ lg2::debug(
+ "descriptor does not have the vendor defined descriptor info");
+ continue;
+ }
+
+ auto data = std::get<VendorDefinedDescriptorInfo>(v);
+
+ std::string res = std::get<VendorDefinedDescriptorTitle>(data);
+
+ return res;
+ }
+
+ return "";
+}
+
+std::shared_ptr<PackageParser>
+ createPackageCommon(uint8_t* component_image, size_t component_image_size,
+ const std::optional<uint32_t>& optVendorIANA,
+ const std::optional<std::string>& optCompatible)
+{
+ size_t size_out = 0;
+ std::unique_ptr<uint8_t[]> buf =
+ create_pldm_package_buffer(component_image, component_image_size,
+ optVendorIANA, optCompatible, size_out);
+
+ const std::shared_ptr<PackageParser> packageParser =
+ pldm_package_util::parsePLDMPackage(buf.get(), size_out);
+
+ return packageParser;
+};
+
+TEST(PLDM_PACKAGE_PARSER, getCompatible)
+{
+ uint8_t component_image[] = {0x12, 0x34, 0x83, 0x21};
+
+ const std::shared_ptr<PackageParser> packageParser = createPackageCommon(
+ component_image, sizeof(component_image), std::nullopt,
+ std::optional<std::string>("com.my.compatible"));
+
+ if (packageParser == nullptr)
+ {
+ lg2::error("could not parse PLDM Package");
+ assert(false);
+ }
+
+ std::string compatible = getCompatibleFromPLDMPackage(packageParser);
+
+ if (compatible.empty())
+ {
+ lg2::error(
+ "could not extract compatible string from PLDM FW Update Package");
+ assert(false);
+ }
+
+ lg2::debug("compatible = {COMPATIBLE}", "COMPATIBLE", compatible);
+
+ assert(compatible == "com.my.compatible");
+}
+
+TEST(PLDM_PACKAGE_PARSER, getComponentImage)
+{
+ int status = 0;
+ uint8_t component_image[] = {0xab, 0xba, 0xcd, 0xef};
+ const size_t component_image_size = 4;
+
+ std::optional<std::string> optFilename =
+ create_pldm_package(component_image, component_image_size);
+
+ assert(optFilename.has_value());
+
+ FILE* file = fopen(optFilename.value().c_str(), "r");
+
+ assert(file != NULL);
+
+ const size_t size = std::filesystem::file_size(optFilename.value());
+
+ auto buf = std::make_shared<uint8_t[]>(size);
+
+ status = pldm_package_util::readImagePackage(file, buf.get(), size);
+
+ fclose(file);
+
+ assert(status == 0);
+
+ std::shared_ptr<PackageParser> packageParser =
+ pldm_package_util::parsePLDMPackage(buf.get(), size);
+
+ assert(packageParser != nullptr);
+
+ auto compImages = packageParser->getComponentImageInfos();
+
+ lg2::debug("found {N} component images", "N", compImages.size());
+
+ assert(compImages.size() == 1);
+
+ const ComponentImageInfo& compImage = compImages[0];
+
+ const uint16_t compClass = std::get<0>(compImage);
+ lg2::debug("component classification: {VALUE}", "VALUE", compClass);
+
+ const uint16_t compIdentifier = std::get<1>(compImage);
+ lg2::debug("component identifier: {VALUE}", "VALUE", compIdentifier);
+
+ const uint32_t compLocationOffset = std::get<5>(compImage);
+ lg2::debug("component location offset: {VALUE}", "VALUE",
+ compLocationOffset);
+
+ const uint32_t compSize = std::get<6>(compImage);
+ lg2::debug("component size: {VALUE}", "VALUE", compSize);
+
+ assert(compSize == component_image_size);
+
+ std::string compVersion = std::get<7>(compImage);
+ lg2::debug("component version: {VALUE}", "VALUE", compVersion);
+
+ uint8_t* p = buf.get() + compLocationOffset;
+ lg2::debug("first few bytes of component image: {1} {2} {3} {4}", "1",
+ lg2::hex, p[0], "2", lg2::hex, p[1], "3", lg2::hex, p[2], "4",
+ lg2::hex, p[3]);
+
+ assert(p[0] == 0xab);
+ assert(p[1] == 0xba);
+ assert(p[2] == 0xcd);
+ assert(p[3] == 0xef);
+
+ assert(status == 0);
+}
+
+TEST(PLDM_PACKAGE_PARSER, getFirmwareDeviceId)
+{
+ int status = 0;
+ uint8_t component_image[] = {0xab, 0xba, 0xcd, 0xef};
+
+ const std::shared_ptr<PackageParser> packageParser = createPackageCommon(
+ component_image, sizeof(component_image), std::nullopt, std::nullopt);
+
+ assert(packageParser != nullptr);
+
+ auto fwDeviceIdRecords = packageParser->getFwDeviceIDRecords();
+ lg2::debug("found {N} fw device id records", "N", fwDeviceIdRecords.size());
+
+ const FirmwareDeviceIDRecords records =
+ packageParser->getFwDeviceIDRecords();
+
+ assert(records.size() == 1);
+
+ const FirmwareDeviceIDRecord& record = records[0];
+
+ const DeviceUpdateOptionFlags optFlags = std::get<0>(record);
+
+ // assert we do not continue component updates after failure
+ assert(!optFlags[0]);
+
+ const ApplicableComponents ac = std::get<1>(record);
+
+ assert(ac.size() == 1);
+
+ // assert that the one component image is applicable to this device
+ assert(ac[0] == 0);
+
+ const ComponentImageSetVersion cisv = std::get<2>(record);
+
+ assert(!cisv.empty());
+
+ Descriptors desc = std::get<3>(record);
+
+ assert(desc.size() == 1);
+
+ assert(desc.contains(1)); // iana type descriptor
+
+ auto v = desc[1];
+
+ const DescriptorData data = std::get<DescriptorData>(v);
+
+ // iana id is 4 bytes
+ assert(data.size() == 4);
+
+ assert(status == 0);
+}
+
+TEST(PLDM_PACKAGE_PARSER, getIANA)
+{
+ uint8_t component_image[] = {0x12, 0x34, 0xab};
+
+ const std::shared_ptr<PackageParser> packageParser =
+ createPackageCommon(component_image, sizeof(component_image),
+ std::optional<uint32_t>(0xdcbaff), std::nullopt);
+
+ assert(packageParser != nullptr);
+
+ auto fwDeviceIdRecords = packageParser->getFwDeviceIDRecords();
+ if (fwDeviceIdRecords.size() != 1)
+ {
+ lg2::error(
+ "fw device id records != 1, rejecting package, incompatible.");
+ assert(false);
+ }
+
+ const FirmwareDeviceIDRecord& record = fwDeviceIdRecords[0];
+
+ Descriptors desc = std::get<3>(record);
+ if (desc.empty())
+ {
+ lg2::error("there is no descriptor for the applicable device");
+ assert(false);
+ }
+
+ if (!desc.contains(1))
+ {
+ // iana type descriptor
+ lg2::error("the package does not have an IANA type descriptor");
+ assert(false);
+ }
+
+ auto v = desc[1];
+
+ const DescriptorData data = std::get<DescriptorData>(v);
+
+ if (data.size() != 4)
+ {
+ lg2::error("iana id should be 4 bytes");
+ assert(false);
+ }
+
+ const uint32_t iana = data[0] | (data[1] << 8) | (data[2] << 16) |
+ (data[3] << 24);
+
+ lg2::debug("iana = {IANA}", "IANA", lg2::hex, iana);
+
+ assert(iana == 0xdcbaff);
+}
diff --git a/test/meson.build b/test/meson.build
index 20b6879..6c436e2 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -1 +1,9 @@
subdir('create_package')
+
+gtest_main = dependency(
+ 'gtest_main',
+ main:true,
+ required:true,
+)
+
+subdir('common')