fw update: library to create pldm fw package
Implement some helper functions to easily create a pldm fw package in a
few lines of user code.
This is helpful for unit testing the code update flow.
The package created is for testing purpose. The library is not
fully-featured or complete and should not be relied upon for anything
other than the tests in this repo.
https://github.com/openbmc/docs/blob/master/designs/code-update.md
Tested: next few patches in series
Change-Id: If899e5537a2e5ac641544ca80e8876d83549da28
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/bmc/meson.build b/bmc/meson.build
index 84de1c5..0ff1c17 100644
--- a/bmc/meson.build
+++ b/bmc/meson.build
@@ -260,7 +260,6 @@
# If test coverage of source files within the root directory are wanted,
# need to define and build the tests from here
-build_tests = get_option('tests')
if not build_tests.disabled()
gtest = dependency('gtest', main: true, disabler: true, required: build_tests)
include_srcs = declare_dependency(sources: [
diff --git a/meson.build b/meson.build
index e160d5d..5c6c21c 100644
--- a/meson.build
+++ b/meson.build
@@ -60,4 +60,14 @@
systemd = dependency('systemd')
systemd_system_unit_dir = systemd.get_variable('systemdsystemunitdir')
+build_tests = get_option('tests')
+
subdir('bmc')
+
+libpldm_dep = dependency('libpldm')
+
+common_include = include_directories('.')
+
+if not build_tests.disabled()
+ subdir('test')
+endif
diff --git a/test/create_package/component_image_info_area.cpp b/test/create_package/component_image_info_area.cpp
new file mode 100644
index 0000000..52c4cbd
--- /dev/null
+++ b/test/create_package/component_image_info_area.cpp
@@ -0,0 +1,70 @@
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <cstring>
+
+/*
+ * componentLocationOffsetIndex is for backfilling by the caller
+ */
+ssize_t create_pldm_component_image_info_area_v1_0_0(
+ uint8_t* b, ssize_t i, size_t component_image_size,
+ size_t& componentLocationOffsetIndex)
+{
+ // Component Image Count
+ b[i++] = 0x1;
+ b[i++] = 0x0;
+
+ // ComponentImageInformation (Table 5)
+ // (1 for each image)
+
+ // ComponentClassification
+ b[i++] = 0x1; // this is vendor selected value
+ b[i++] = 0x0;
+
+ // ComponentIdentifier
+ b[i++] = 0x1; // this is vendor selected value
+ b[i++] = 0x0;
+
+ // ComponentComparisonStamp
+ b[i++] = 0xff;
+ b[i++] = 0xff;
+ b[i++] = 0xff;
+ b[i++] = 0xff;
+
+ // ComponentOptions
+ b[i++] = 0x00;
+ b[i++] = 0x00;
+
+ // RequestedComponentActivationMethod
+ b[i++] = 0b100000; // AC Power Cycle
+ b[i++] = 0x0;
+
+ // ComponentLocationOffset
+ // (leave blank for now)
+ componentLocationOffsetIndex = i;
+ b[i++] = 0x0;
+ b[i++] = 0x0;
+ b[i++] = 0x0;
+ b[i++] = 0x0;
+
+ // ComponentSize
+ b[i++] = (component_image_size >> 0) & 0xff;
+ b[i++] = (component_image_size >> 8) & 0xff;
+ b[i++] = (component_image_size >> 16) & 0xff;
+ b[i++] = (component_image_size >> 24) & 0xff;
+
+ // ComponentVersionStringType
+ b[i++] = 0x1; // type = Ascii
+
+ const char* buf = (const char*)"mycompversion";
+ // ComponentVersionStringLength
+ b[i++] = strlen(buf);
+
+ // ComponentVersionString
+ for (ssize_t j = 0; j < (ssize_t)strlen(buf); j++)
+ {
+ b[i++] = buf[j];
+ }
+
+ return i;
+}
diff --git a/test/create_package/component_image_info_area.hpp b/test/create_package/component_image_info_area.hpp
new file mode 100644
index 0000000..3a24c20
--- /dev/null
+++ b/test/create_package/component_image_info_area.hpp
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <unistd.h>
+
+#include <cstdint>
+
+/*
+ * componentLocationOffsetIndex is for backfilling by the caller
+ */
+ssize_t create_pldm_component_image_info_area_v1_0_0(
+ uint8_t* b, ssize_t i, size_t component_image_size,
+ size_t& componentLocationOffsetIndex);
diff --git a/test/create_package/create_pldm_fw_package.cpp b/test/create_package/create_pldm_fw_package.cpp
new file mode 100644
index 0000000..c4353bb
--- /dev/null
+++ b/test/create_package/create_pldm_fw_package.cpp
@@ -0,0 +1,145 @@
+
+#include "component_image_info_area.hpp"
+#include "firmware_device_id_area.hpp"
+#include "phosphor-logging/lg2.hpp"
+
+#include <inttypes.h>
+#include <libpldm/firmware_update.h>
+
+#include <cstring>
+#include <fstream>
+#include <limits>
+#include <random>
+
+std::unique_ptr<uint8_t[]> create_pldm_package_buffer(
+ const 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)
+{
+ const size_t size = 512 + component_image_size;
+ auto buffer = std::make_unique<uint8_t[]>(size);
+ uint8_t* b = buffer.get();
+ memset(b, 0, size);
+ ssize_t i = 0; // index
+
+ // populate uuid
+ // must be this value to align with
+ // https://github.com/openbmc/pldm/blob/master/fw-update/package_parser.cpp#L294
+ uint8_t uuid[PLDM_FWUP_UUID_LENGTH] = {
+ 0xF0, 0x18, 0x87, 0x8C, 0xCB, 0x7D, 0x49, 0x43,
+ 0x98, 0x00, 0xA0, 0x2F, 0x05, 0x9A, 0xCA, 0x02};
+ memcpy(b, uuid, PLDM_FWUP_UUID_LENGTH);
+
+ i += PLDM_FWUP_UUID_LENGTH;
+
+ // package header format revision
+ b[i++] = 0x01;
+ // must be 1 to align with
+ // https://github.com/openbmc/pldm/blob/master/fw-update/package_parser.cpp#L294
+
+ // package header size (leave space)
+ ssize_t package_header_size_offset = i;
+ i += 2;
+
+ // package release date time
+ // set timestamp as unknown value
+ b[i + 12] = 15;
+ i += PLDM_TIMESTAMP104_SIZE;
+
+ // component bitmap bit length
+ const uint16_t componentBitmapBitLength = 8;
+ b[i++] = componentBitmapBitLength;
+ b[i++] = 0x00;
+
+ // package_version_string_type
+ b[i++] = 0x01; // type = ASCII
+
+ const char* package_version_str = (const char*)"VersionString1";
+ // package version string length
+ b[i++] = strlen(package_version_str);
+
+ // package version string
+ for (size_t j = 0; j < strlen(package_version_str); j++)
+ {
+ b[i++] = package_version_str[j];
+ }
+
+ // --- Firmware Device Identification Area 1.0.0 ---
+
+ i = create_pldm_firmware_device_identification_area_v1_0_0(
+ b, i, optVendorIANA, optCompatible, componentBitmapBitLength);
+
+ // --- Component Image Information Area 1.0.0 ---
+ size_t componentLocationOffsetIndex;
+ i = create_pldm_component_image_info_area_v1_0_0(
+ b, i, component_image_size, componentLocationOffsetIndex);
+
+ // PackageHeaderChecksum (backfill later)
+ const size_t packageHeaderChecksumOffset = i;
+ i += 4;
+
+ // backfill the PackageHeaderSize
+ b[package_header_size_offset + 0] = (i >> 0) & 0xff;
+ b[package_header_size_offset + 1] = (i >> 8) & 0xff;
+
+ // backfill the ComponentLocationOffset
+ b[componentLocationOffsetIndex + 0] = (i >> 0) & 0xff;
+ b[componentLocationOffsetIndex + 1] = (i >> 8) & 0xff;
+ b[componentLocationOffsetIndex + 2] = (i >> 16) & 0xff;
+ b[componentLocationOffsetIndex + 3] = (i >> 24) & 0xff;
+
+ // backfill PackageHeaderChecksum
+ const uint32_t crc = crc32(b, packageHeaderChecksumOffset);
+ memcpy(b + packageHeaderChecksumOffset, &crc, 4);
+
+ // --- end of the package header ---
+
+ // write the component image
+ for (size_t j = 0; j < component_image_size; j++)
+ {
+ b[i++] = component_image[j];
+ }
+
+ lg2::debug("wrote {NBYTES} bytes for pldm update package", "NBYTES", i);
+
+ size_out = i;
+ return buffer;
+}
+
+static void create_pldm_package_file(std::ofstream& of,
+ const uint8_t* component_image,
+ size_t component_image_size)
+{
+ size_t size;
+ std::unique_ptr<uint8_t[]> buf =
+ create_pldm_package_buffer(component_image, component_image_size,
+ std::nullopt, std::nullopt, size);
+ uint8_t* b = buf.get();
+
+ of.write(reinterpret_cast<char*>(b), (long)size);
+}
+
+std::optional<std::string>
+ create_pldm_package(uint8_t* component_image, size_t component_image_size)
+{
+ std::random_device rd;
+ std::mt19937 gen(rd());
+ std::uniform_int_distribution<> distrib(0, std::numeric_limits<int>::max());
+
+ std::string filename =
+ std::format("/tmp/pldm-package-{}.bin", distrib(gen));
+
+ std::ofstream of(filename, std::ofstream::out);
+
+ if (!of.good())
+ {
+ lg2::error("could not create file: {FILENAME}", "FILENAME", filename);
+ return std::nullopt;
+ }
+
+ create_pldm_package_file(of, component_image, component_image_size);
+
+ of.close();
+
+ return filename;
+}
diff --git a/test/create_package/create_pldm_fw_package.hpp b/test/create_package/create_pldm_fw_package.hpp
new file mode 100644
index 0000000..0d37c6a
--- /dev/null
+++ b/test/create_package/create_pldm_fw_package.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <inttypes.h>
+
+#include <memory>
+#include <optional>
+#include <string>
+
+std::optional<std::string>
+ create_pldm_package(uint8_t* component_image, size_t component_image_size);
+
+std::unique_ptr<uint8_t[]> create_pldm_package_buffer(
+ const 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);
diff --git a/test/create_package/firmware_device_id_area.cpp b/test/create_package/firmware_device_id_area.cpp
new file mode 100644
index 0000000..496340c
--- /dev/null
+++ b/test/create_package/firmware_device_id_area.cpp
@@ -0,0 +1,174 @@
+#include <inttypes.h>
+#include <libpldm/firmware_update.h>
+
+#include <cstring>
+#include <optional>
+#include <string>
+#include <vector>
+
+static ssize_t create_pldm_firmware_device_descriptor_v1_0_0(
+ uint8_t* b, ssize_t i, uint16_t descType, std::vector<uint8_t>& data)
+{
+ // DescriptorType, Table 7. (0x0001 == IANA Enterprise ID, 4 bytes)
+ b[i++] = (descType >> 0) & 0xff;
+ b[i++] = (descType >> 8) & 0xff;
+
+ // DescriptorLength
+ b[i++] = (data.size() >> 0) & 0xff;
+ b[i++] = (data.size() >> 8) & 0xff;
+
+ // DescriptorData
+ for (uint8_t v : data)
+ {
+ b[i++] = v;
+ }
+
+ return i;
+}
+
+static ssize_t create_pldm_firmware_device_vendor_defined_descriptor_v1_0_0(
+ uint8_t* b, ssize_t i, std::vector<uint8_t>& data)
+{
+ const uint16_t descType = 0xffff; // vendor defined
+
+ // DescriptorType, Table 7.
+ b[i++] = (descType >> 0) & 0xff;
+ b[i++] = (descType >> 8) & 0xff;
+
+ // DescriptorLength
+ const uint16_t length = data.size() + 3; // data and the additional fields
+ b[i++] = (length >> 0) & 0xff;
+ b[i++] = (length >> 8) & 0xff;
+
+ // VendorDefinedDescriptorTitleStringType
+ b[i++] = 0x1; // type 1 = ascii
+
+ // VendorDefinedDescriptorTitleStringLength
+ b[i++] = data.size();
+
+ // VendorDefinedDescriptorTitleString
+ for (uint8_t v : data)
+ {
+ b[i++] = v;
+ }
+
+ // VendorDefinedDescriptorData
+ b[i++] = 0x00;
+
+ return i;
+}
+
+static ssize_t create_pldm_firmware_device_descriptors_v1_0_0(
+ uint8_t* b, ssize_t i, const std::optional<uint32_t>& optVendorIANA,
+ const std::optional<std::string>& optCompatible, size_t* actual_count)
+{
+ // RecordDescriptiors. The initial descriptor is restricted.
+ // In our case we use iana for the first descriptor.
+
+ std::vector<uint8_t> descriptorData = {0xaf, 0xaf, 0xaf, 0xaf};
+
+ if (optVendorIANA.has_value())
+ {
+ uint32_t v = optVendorIANA.value();
+ descriptorData[0] = (v >> 0) & 0xff;
+ descriptorData[1] = (v >> 8) & 0xff;
+ descriptorData[2] = (v >> 16) & 0xff;
+ descriptorData[3] = (v >> 24) & 0xff;
+ }
+
+ i = create_pldm_firmware_device_descriptor_v1_0_0(b, i, 0x1,
+ descriptorData);
+
+ *actual_count = 1;
+
+ if (optCompatible.has_value())
+ {
+ std::string comp = optCompatible.value();
+ std::vector<uint8_t> compatible = {};
+ for (char c : comp)
+ {
+ compatible.push_back((uint8_t)c);
+ }
+ i = create_pldm_firmware_device_vendor_defined_descriptor_v1_0_0(
+ b, i, compatible);
+
+ *actual_count = 2;
+ }
+
+ return i;
+}
+
+ssize_t create_pldm_firmware_device_identification_record(
+ uint8_t* b, ssize_t i, const std::optional<uint32_t>& optVendorIANA,
+ const std::optional<std::string>& optCompatible,
+ uint16_t componentBitmapBitLength)
+{
+ const ssize_t startIndex = i;
+ // RecordLength, backfill later
+ const size_t recordLengthOffset = i;
+ b[i++] = 0;
+ b[i++] = 0;
+
+ // DescriptorCount (backfill later)
+ const size_t descriptorCountOffset = i;
+ b[i++] = 0;
+
+ // DeviceUpdateOptionFlags
+ b[i++] = 0;
+ b[i++] = 0;
+ b[i++] = 0;
+ b[i++] = 0;
+
+ // ComponentImageSetVersionStringType
+ b[i++] = 1; // type = Ascii
+
+ const char* str = (const char*)"compimagesetversion1";
+ // ComponentImageSetVersionStringLength
+ b[i++] = strlen(str);
+
+ // FirmwareDevicePackageDataLength
+ b[i++] = 0x0;
+ b[i++] = 0x0;
+
+ // ApplicableComponents
+ for (int j = 0; j < (componentBitmapBitLength / 8); j++)
+ {
+ b[i++] = 0x1;
+ // the first and only component image does apply to this device
+ }
+
+ // ComponentSetVersionString
+ for (size_t j = 0; j < strlen(str); j++)
+ {
+ b[i++] = str[j];
+ }
+
+ // RecordDescriptiors. The initial descriptor is restricted.
+ size_t actual_count = 0;
+ i = create_pldm_firmware_device_descriptors_v1_0_0(
+ b, i, optVendorIANA, optCompatible, &actual_count);
+
+ // backfill DescriptorCount
+ b[descriptorCountOffset] = actual_count;
+
+ // FirmwareDevicePackageData (optional, we make it empty)
+
+ // backfill RecordLength
+ const ssize_t recordLength = i - startIndex;
+ b[recordLengthOffset + 0] = (recordLength >> 0) & 0xff;
+ b[recordLengthOffset + 1] = (recordLength >> 8) & 0xff;
+
+ return i;
+}
+
+ssize_t create_pldm_firmware_device_identification_area_v1_0_0(
+ uint8_t* b, ssize_t i, const std::optional<uint32_t>& optVendorIANA,
+ const std::optional<std::string>& optCompatible,
+ uint16_t componentBitmapBitLength)
+{
+ // Device ID Record Count
+ b[i++] = 1;
+
+ return create_pldm_firmware_device_identification_record(
+ b, i, optVendorIANA, optCompatible, componentBitmapBitLength);
+}
diff --git a/test/create_package/firmware_device_id_area.hpp b/test/create_package/firmware_device_id_area.hpp
new file mode 100644
index 0000000..253ca78
--- /dev/null
+++ b/test/create_package/firmware_device_id_area.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <unistd.h>
+
+#include <cstdint>
+#include <optional>
+#include <string>
+
+ssize_t create_pldm_firmware_device_identification_record(
+ uint8_t* b, ssize_t i, const std::optional<uint32_t>& optVendorIANA,
+ const std::optional<std::string>& optCompatible,
+ uint16_t componentBitmapBitLength);
+
+ssize_t create_pldm_firmware_device_identification_area_v1_0_0(
+ uint8_t* b, ssize_t i, const std::optional<uint32_t>& optVendorIANA,
+ const std::optional<std::string>& optCompatible,
+ uint16_t componentBitmapBitLength);
diff --git a/test/create_package/meson.build b/test/create_package/meson.build
new file mode 100644
index 0000000..44cc9fe
--- /dev/null
+++ b/test/create_package/meson.build
@@ -0,0 +1,12 @@
+
+libpldmcreatepkg = static_library('pldmcreatepkg',
+ 'create_pldm_fw_package.cpp',
+ 'firmware_device_id_area.cpp',
+ 'component_image_info_area.cpp',
+ include_directories: ['.'],
+ dependencies: [
+ phosphor_logging_dep,
+ libpldm_dep,
+ ],
+ install: false,
+)
diff --git a/test/meson.build b/test/meson.build
new file mode 100644
index 0000000..20b6879
--- /dev/null
+++ b/test/meson.build
@@ -0,0 +1 @@
+subdir('create_package')