[cper-lib] Implement CperDecoder and refactor safe accessors Introduces a `CperDecoder` library to safely parse UEFI CPER records and refactors the codebase to centralize safe memory access patterns. This ensures full compatibility with UBSan and strict alignment toolchains. Key Changes: - CperDecoder: Added a new `uefi::cper::CperDecoder` class that provides a robust interface for parsing CPER records. It performs signature validation, boundary checks for section descriptors, and provides safe access to section bodies via std::span. - Centralized getField: Moved the `getField<T>` helper to `include/cper.hpp`. This allows the decoder and unit tests to share a single implementation for safely reading members of unaligned packed structs using memcpy. - Hardened gmtime_r: Added explicit return value checks for `gmtime_r` calls, throwing `std::runtime_error` on failure to ensure reliability when processing timestamps. - Refactored Tests: Updated `cper_encoder_test.cpp` to use the new `CperDecoder` for its assertions. This simplifies the test logic and verifies the decoder's functionality through round-trip testing. - New Test Coverage: Added unit tests for the decoder to verify handling of truncated headers, invalid signatures, and malformed section counts. Tested: - All 9 unit tests passed via Meson inside the gbmc_dev container. - All tests passed via Bazel/Blaze using the Google cc_toolchain. - Built an experimental cli tool on google3 using the decoder on AsicCper logs from sdhb and compared the output to the CFA Google-Bug-Id: 505395424 Change-Id: I02a385a591f7ccee8fc9e3e28ee4e79755170656 Signed-off-by: Aryk Ledet <arykledet@google.com>
diff --git a/include/cper.hpp b/include/cper.hpp index 500cf6e..4f75521 100644 --- a/include/cper.hpp +++ b/include/cper.hpp
@@ -11,6 +11,7 @@ // https://uefi.org/specs/UEFI/2.10/Apx_N_Common_Platform_Error_Record.html#common-platform-error-record-cper namespace uefi::cper { + constexpr size_t kRecordSignatureSize = 0x4; constexpr std::array<uint8_t, kRecordSignatureSize> kRecordSignature = { 'C', 'P', 'E', 'R'};
diff --git a/include/cper_decoder.hpp b/include/cper_decoder.hpp new file mode 100644 index 0000000..498c876 --- /dev/null +++ b/include/cper_decoder.hpp
@@ -0,0 +1,131 @@ +#pragma once + +#include "cper.hpp" + +#include <absl/base/internal/endian.h> + +#include <span> +#include <stdexcept> +#include <vector> + +namespace uefi::cper +{ + +template <typename T> +inline T littleEndianLoad(const void* p) +{ + if constexpr (sizeof(T) == 1) + return *static_cast<const uint8_t*>(p); + if constexpr (sizeof(T) == 2) + return absl::little_endian::Load16(p); + if constexpr (sizeof(T) == 4) + return absl::little_endian::Load32(p); + if constexpr (sizeof(T) == 8) + return absl::little_endian::Load64(p); +} + +class CperDecoder +{ + public: + /** + * @brief Construct a new CperDecoder object + * + * @param[in] data - The CPER record data to decode. + * + * @throws std::invalid_argument If the data is too small to contain a + * RecordHeader or if the CPER signature is missing. + */ + explicit CperDecoder(std::span<const uint8_t> data) : data_(data) + { + if (data_.size() < kRecordHeaderSizeBytes) + { + throw std::invalid_argument("Data too small for RecordHeader"); + } + + const uint8_t* headerPtr = data_.data(); + + // Manual copy for the signature array since Load helpers usually target + // integers + std::array<uint8_t, kRecordSignatureSize> signature; + std::memcpy(signature.data(), + headerPtr + offsetof(RecordHeader, signature), + kRecordSignatureSize); + + if (memcmp(signature.data(), kRecordSignature.data(), + kRecordSignatureSize) != 0) + { + throw std::invalid_argument("Invalid CPER signature"); + } + } + + /** + * @brief Get the RecordHeader from the CPER record. + * + * @return The RecordHeader struct. + */ + RecordHeader getHeader() const + { + RecordHeader header; + memcpy(&header, data_.data(), kRecordHeaderSizeBytes); + return header; + } + + struct Section + { + SectionDescriptor descriptor; + std::span<const uint8_t> body; + }; + + /** + * @brief Get the sections from the CPER record. + * + * @return A vector of Section structs containing the descriptor and body. + * + * @throws std::runtime_error If the record is malformed (e.g. section + * offset or length out of bounds). + */ + std::vector<Section> getSections() const + { + const uint8_t* headerPtr = data_.data(); + const uint16_t sectionCount = littleEndianLoad<uint16_t>( + headerPtr + offsetof(RecordHeader, sectionCount)); + + std::vector<Section> sections; + sections.reserve(sectionCount); + + for (uint16_t i = 0; i < sectionCount; ++i) + { + const uint64_t descriptorOffset = + kRecordHeaderSizeBytes + (i * kSectionDescriptorSizeBytes); + + if (descriptorOffset + kSectionDescriptorSizeBytes > data_.size()) + { + throw std::runtime_error("Section descriptor out of bounds"); + } + + const uint8_t* descriptorPtr = data_.data() + descriptorOffset; + SectionDescriptor descriptor; + memcpy(&descriptor, descriptorPtr, kSectionDescriptorSizeBytes); + + const uint32_t bodyOffset = littleEndianLoad<uint32_t>( + descriptorPtr + offsetof(SectionDescriptor, sectionOffset)); + const uint32_t bodyLength = littleEndianLoad<uint32_t>( + descriptorPtr + offsetof(SectionDescriptor, sectionLength)); + + if (bodyOffset + bodyLength > data_.size()) + { + throw std::runtime_error("Section body out of bounds"); + } + + sections.push_back( + {descriptor, data_.subspan(bodyOffset, bodyLength)}); + } + + return sections; + } + + private: + std::span<const uint8_t> data_; +}; + +} // namespace uefi::cper
diff --git a/meson.build b/meson.build index 1003aba..a951bdf 100644 --- a/meson.build +++ b/meson.build
@@ -8,8 +8,11 @@ cpp_compiler = meson.get_compiler('cpp') +absl_endian_dep = dependency('absl_endian', required : true) + cper_lib_headers = files( 'include/cper_encoder.hpp', + 'include/cper_decoder.hpp', 'include/cper.hpp', 'include/guid.hpp' )
diff --git a/test/cper_decoder_test.cpp b/test/cper_decoder_test.cpp new file mode 100644 index 0000000..a2efa99 --- /dev/null +++ b/test/cper_decoder_test.cpp
@@ -0,0 +1,38 @@ +#include "cper_decoder.hpp" +#include "mock_cper_encoder.hpp" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +namespace uefi::cper +{ + +TEST(CperDecoderTest, DecoderInvalidData) +{ + // Too small for header + std::vector<uint8_t> smallData(kRecordHeaderSizeBytes - 1, 0); + EXPECT_THROW(CperDecoder{smallData}, std::invalid_argument); + + // Invalid signature + std::vector<uint8_t> invalidSignature(kRecordHeaderSizeBytes, 0); + EXPECT_THROW(CperDecoder{invalidSignature}, std::invalid_argument); +} + +TEST(CperDecoderTest, DecoderMalformedSections) +{ + MockCperEncoder<uint8_t> mockEncoder; + const std::array<uint8_t, 10> sectionBody = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + mockEncoder.addSection(sectionBody, kMockSectionFlag, kMockSectionType); + std::vector<uint8_t> cperArray = mockEncoder.serializeToByteArray(); + + // Corrupt section count in header to be 2 instead of 1 + void* headerPtr = cperArray.data(); + uint16_t malformedCount = 2; + memcpy(static_cast<char*>(headerPtr) + offsetof(RecordHeader, sectionCount), + &malformedCount, sizeof(malformedCount)); + + CperDecoder decoder(cperArray); + EXPECT_THROW(decoder.getSections(), std::runtime_error); +} + +} // namespace uefi::cper
diff --git a/test/cper_encoder_test.cpp b/test/cper_encoder_test.cpp index 8a8e7f4..1de0fac 100644 --- a/test/cper_encoder_test.cpp +++ b/test/cper_encoder_test.cpp
@@ -1,8 +1,8 @@ +#include "cper_decoder.hpp" #include "cper_encoder.hpp" #include "mock_cper_encoder.hpp" #include <chrono> -#include <concepts> #include <cstring> #include <gmock/gmock.h> @@ -34,14 +34,6 @@ void SetUp() override {} - template <typename T, typename S> - T getField(const S* s, size_t offset) - { - T val; - std::memcpy(&val, reinterpret_cast<const char*>(s) + offset, sizeof(T)); - return val; - } - bool isArrayEq(const void* arr1, const void* arr2, const uint64_t arr1Size, const uint64_t arr2Size) { @@ -52,136 +44,165 @@ return memcmp(arr1, arr2, arr1Size) == 0; } - void expectValidHeader(const void* data, const uint64_t size, + void expectValidHeader(const RecordHeader& header, const uint16_t numSections, const uint64_t cperSize, const uint64_t timestamp) { - ASSERT_GE(size, sizeof(RecordHeader)); - const RecordHeader* header = - reinterpret_cast<const RecordHeader*>(data); + const void* headerPtr = &header; - const auto signature = - getField<std::array<uint8_t, kRecordSignatureSize>>( - header, offsetof(RecordHeader, signature)); + std::array<uint8_t, kRecordSignatureSize> signature; + std::memcpy(signature.data(), + static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, signature), + kRecordSignatureSize); + EXPECT_EQ(memcmp(signature.data(), kRecordSignature.data(), kRecordSignatureSize), 0); - EXPECT_EQ(getField<uint16_t>(header, offsetof(RecordHeader, revision)), - kRecordRevision); EXPECT_EQ( - getField<uint32_t>(header, offsetof(RecordHeader, signatureEnd)), + littleEndianLoad<uint16_t>(static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, revision)), + kRecordRevision); + EXPECT_EQ( + littleEndianLoad<uint32_t>(static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, signatureEnd)), kRecordSignatureEnd); EXPECT_EQ( - getField<uint16_t>(header, offsetof(RecordHeader, sectionCount)), + littleEndianLoad<uint16_t>(static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, sectionCount)), numSections); EXPECT_EQ( - getField<uint32_t>(header, offsetof(RecordHeader, errorSeverity)), + littleEndianLoad<uint32_t>(static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, errorSeverity)), kMockRecordSeverity); EXPECT_EQ( - getField<uint32_t>(header, offsetof(RecordHeader, validationBits)), + littleEndianLoad<uint32_t>(static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, validationBits)), kMockValidationBits); EXPECT_EQ( - getField<uint32_t>(header, offsetof(RecordHeader, recordLength)), + littleEndianLoad<uint32_t>(static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, recordLength)), cperSize); - EXPECT_EQ(getField<uint64_t>(header, offsetof(RecordHeader, timestamp)), - timestamp); - - EXPECT_EQ(getField<guid_t>(header, offsetof(RecordHeader, platformId)), - kMockPlatformId); - EXPECT_EQ(getField<guid_t>(header, offsetof(RecordHeader, partitionId)), - kMockPartitionId); - EXPECT_EQ(getField<guid_t>(header, offsetof(RecordHeader, creatorId)), - kMockCreatorId); EXPECT_EQ( - getField<guid_t>(header, offsetof(RecordHeader, notificationType)), - kMockNotificationType); + littleEndianLoad<uint64_t>(static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, timestamp)), + timestamp); - EXPECT_EQ(getField<uint64_t>(header, offsetof(RecordHeader, recordId)), - kMockRecordId); - EXPECT_EQ(getField<uint32_t>(header, offsetof(RecordHeader, flags)), - kMockHeaderFlags); - EXPECT_EQ(getField<uint64_t>( - header, offsetof(RecordHeader, persistenceInformation)), + guid_t platformId; + std::memcpy(&platformId, + static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, platformId), + sizeof(guid_t)); + EXPECT_EQ(platformId, kMockPlatformId); + + guid_t partitionId; + std::memcpy(&partitionId, + static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, partitionId), + sizeof(guid_t)); + EXPECT_EQ(partitionId, kMockPartitionId); + + guid_t creatorId; + std::memcpy(&creatorId, + static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, creatorId), + sizeof(guid_t)); + EXPECT_EQ(creatorId, kMockCreatorId); + + guid_t notificationType; + std::memcpy(¬ificationType, + static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, notificationType), + sizeof(guid_t)); + EXPECT_EQ(notificationType, kMockNotificationType); + + EXPECT_EQ( + littleEndianLoad<uint64_t>(static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, recordId)), + kMockRecordId); + EXPECT_EQ( + littleEndianLoad<uint32_t>(static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, flags)), + kMockHeaderFlags); + EXPECT_EQ(littleEndianLoad<uint64_t>( + static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, persistenceInformation)), kMockPersistenceInformation); std::array<uint8_t, 12> expectedReserved = {0}; - const auto reserved = getField<std::array<uint8_t, 12>>( - header, offsetof(RecordHeader, reserved)); + std::array<uint8_t, 12> reserved; + std::memcpy(reserved.data(), + static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, reserved), + 12); EXPECT_EQ( memcmp(reserved.data(), expectedReserved.data(), reserved.size()), 0); } - template <StringOrVector CperType> - void expectValidSectionDescriptor(const CperType& cperArray, - const uint64_t sectionNum, + void expectValidSectionDescriptor(const SectionDescriptor& descriptor, const uint32_t expectedBodyOffset, const uint32_t expectedLength, const uint32_t expectedFlag, const guid_t& expectedSectionType) { - const uint64_t sectionOffset = - kRecordHeaderSizeBytes + (kSectionDescriptorSizeBytes * sectionNum); - ASSERT_GE(cperArray.size(), sectionOffset + sizeof(SectionDescriptor)); - const SectionDescriptor* descriptor = - reinterpret_cast<const SectionDescriptor*>( - &cperArray[sectionOffset]); + const void* descPtr = &descriptor; - EXPECT_EQ(getField<uint32_t>( - descriptor, offsetof(SectionDescriptor, sectionOffset)), + EXPECT_EQ(littleEndianLoad<uint32_t>( + static_cast<const char*>(descPtr) + + offsetof(SectionDescriptor, sectionOffset)), expectedBodyOffset); - EXPECT_EQ(getField<uint32_t>( - descriptor, offsetof(SectionDescriptor, sectionLength)), + EXPECT_EQ(littleEndianLoad<uint32_t>( + static_cast<const char*>(descPtr) + + offsetof(SectionDescriptor, sectionLength)), expectedLength); - EXPECT_EQ(getField<uint16_t>(descriptor, - offsetof(SectionDescriptor, revision)), - kMockSectionRevision); - EXPECT_EQ(getField<uint8_t>( - descriptor, offsetof(SectionDescriptor, validationBits)), + EXPECT_EQ( + littleEndianLoad<uint16_t>(static_cast<const char*>(descPtr) + + offsetof(SectionDescriptor, revision)), + kMockSectionRevision); + EXPECT_EQ(static_cast<const uint8_t*>( + descPtr)[offsetof(SectionDescriptor, validationBits)], kMockSectionValidationBits); - EXPECT_EQ(getField<uint8_t>(descriptor, - offsetof(SectionDescriptor, reserved)), + EXPECT_EQ(static_cast<const uint8_t*>( + descPtr)[offsetof(SectionDescriptor, reserved)], 0); EXPECT_EQ( - getField<uint32_t>(descriptor, offsetof(SectionDescriptor, flags)), + littleEndianLoad<uint32_t>(static_cast<const char*>(descPtr) + + offsetof(SectionDescriptor, flags)), expectedFlag); - EXPECT_EQ(getField<guid_t>(descriptor, - offsetof(SectionDescriptor, sectionType)), - expectedSectionType); - EXPECT_EQ( - getField<guid_t>(descriptor, offsetof(SectionDescriptor, fruId)), - kMockFruId); + guid_t sectionType; + std::memcpy(§ionType, + static_cast<const char*>(descPtr) + + offsetof(SectionDescriptor, sectionType), + sizeof(guid_t)); + EXPECT_EQ(sectionType, expectedSectionType); - EXPECT_EQ(getField<uint32_t>( - descriptor, offsetof(SectionDescriptor, sectionSeverity)), + guid_t fruId; + std::memcpy(&fruId, + static_cast<const char*>(descPtr) + + offsetof(SectionDescriptor, fruId), + sizeof(guid_t)); + EXPECT_EQ(fruId, kMockFruId); + + EXPECT_EQ(littleEndianLoad<uint32_t>( + static_cast<const char*>(descPtr) + + offsetof(SectionDescriptor, sectionSeverity)), kMockSectionSeverity); std::array<uint8_t, 20> expectedFruText = {0}; memcpy(expectedFruText.data(), kMockFruText.data(), kMockFruText.size()); - const auto fruText = getField<std::array<uint8_t, 20>>( - descriptor, offsetof(SectionDescriptor, fruText)); + + std::array<uint8_t, 20> fruText; + std::memcpy(fruText.data(), + static_cast<const char*>(descPtr) + + offsetof(SectionDescriptor, fruText), + 20); EXPECT_EQ( memcmp(fruText.data(), expectedFruText.data(), fruText.size()), 0); } - template <ByteLike BodyType, StringOrVector CperType> - void expectValidSectionBody( - const CperType& cperArray, - const std::span<const BodyType> expectedSectionBody, - const uint32_t sectionOffset) - { - const uint64_t kSectionBodySize = expectedSectionBody.size(); - ASSERT_GE(cperArray.size(), sectionOffset + kSectionBodySize); - CperType sectionBody(kSectionBodySize, 0); - memcpy(sectionBody.data(), &cperArray[sectionOffset], - sectionBody.size()); - - EXPECT_TRUE(isArrayEq(sectionBody.data(), expectedSectionBody.data(), - sectionBody.size(), expectedSectionBody.size())); - } - void expectEqTimestamp( const uint64_t cperTimestamp, const std::chrono::time_point<std::chrono::system_clock> ref, @@ -273,23 +294,29 @@ EXPECT_CALL(mockEncoder, mockCreateSectionDescriptor).Times(kNumSections); const std::vector<uint8_t> cperArray = mockEncoder.serializeToByteArray(); ASSERT_EQ(cperArray.size(), kExpectedCperSize); - expectValidHeader(cperArray.data(), cperArray.size(), kNumSections, - kExpectedCperSize, mockEncoder.timestamp); + + CperDecoder decoder(cperArray); + expectValidHeader(decoder.getHeader(), kNumSections, kExpectedCperSize, + mockEncoder.timestamp); + + auto sections = decoder.getSections(); + ASSERT_EQ(sections.size(), kNumSections); uint32_t expectedBodyOffset = kRecordHeaderSizeBytes + (kNumSections * kSectionDescriptorSizeBytes); - uint64_t sectionIdx = 0; - for (const std::string& body : sectionBodies) + for (size_t i = 0; i < kNumSections; ++i) { - const uint32_t kBodyLength = body.size(); - expectValidSectionDescriptor(cperArray, sectionIdx, expectedBodyOffset, + const uint32_t kBodyLength = sectionBodies[i].size(); + expectValidSectionDescriptor(sections[i].descriptor, expectedBodyOffset, kBodyLength, kMockSectionFlag, kMockSectionType); - expectValidSectionBody<char>(cperArray, body, expectedBodyOffset); + EXPECT_EQ(sections[i].body.size(), kBodyLength); + EXPECT_EQ(memcmp(sections[i].body.data(), sectionBodies[i].data(), + kBodyLength), + 0); expectedBodyOffset += kBodyLength; - sectionIdx++; } // Serialize to a string. @@ -297,24 +324,11 @@ EXPECT_CALL(mockEncoder, mockCreateSectionDescriptor).Times(kNumSections); const std::string cperStr = mockEncoder.serializeToString(); ASSERT_EQ(cperStr.size(), kExpectedCperSize); - expectValidHeader(cperStr.data(), cperStr.size(), kNumSections, - kExpectedCperSize, mockEncoder.timestamp); - expectedBodyOffset = - kRecordHeaderSizeBytes + (kNumSections * kSectionDescriptorSizeBytes); - sectionIdx = 0; - for (const std::string& body : sectionBodies) - { - const uint32_t kBodyLength = body.size(); - expectValidSectionDescriptor(cperStr, sectionIdx, expectedBodyOffset, - kBodyLength, kMockSectionFlag, - kMockSectionType); - - expectValidSectionBody<char>(cperStr, body, expectedBodyOffset); - - expectedBodyOffset += kBodyLength; - sectionIdx++; - } + CperDecoder strDecoder(std::span<const uint8_t>( + reinterpret_cast<const uint8_t*>(cperStr.data()), cperStr.size())); + expectValidHeader(strDecoder.getHeader(), kNumSections, kExpectedCperSize, + mockEncoder.timestamp); } TEST_F(CperEncoderTest, EncodeOneU8SectionToString) @@ -329,55 +343,32 @@ EXPECT_CALL(mockEncoder, mockCreateRecordHeader).Times(1); EXPECT_CALL(mockEncoder, mockCreateSectionDescriptor).Times(kNumSections); - const std::string cperArray = mockEncoder.serializeToString(); + const std::string cperStr = mockEncoder.serializeToString(); const uint64_t kExpectedCperSize = (kSectionBodySize + kSectionDescriptorSizeBytes + kRecordHeaderSizeBytes); - ASSERT_EQ(cperArray.size(), kExpectedCperSize); - expectValidHeader(cperArray.data(), cperArray.size(), kNumSections, - kExpectedCperSize, mockEncoder.timestamp); + ASSERT_EQ(cperStr.size(), kExpectedCperSize); + + CperDecoder decoder(std::span<const uint8_t>( + reinterpret_cast<const uint8_t*>(cperStr.data()), cperStr.size())); + expectValidHeader(decoder.getHeader(), kNumSections, kExpectedCperSize, + mockEncoder.timestamp); + + auto sections = decoder.getSections(); + ASSERT_EQ(sections.size(), kNumSections); const uint32_t kExpectedOffset = kRecordHeaderSizeBytes + (kNumSections * kSectionDescriptorSizeBytes); - expectValidSectionDescriptor(cperArray, 0, kExpectedOffset, + expectValidSectionDescriptor(sections[0].descriptor, kExpectedOffset, kSectionBodySize, kMockSectionFlag, kMockSectionType); - expectValidSectionBody<uint8_t>(cperArray, sectionBody, kExpectedOffset); -} - -TEST_F(CperEncoderTest, EncodeOneCharSectionToArray) -{ - MockCperEncoder<char> mockEncoder; - - const std::string sectionBody = - "Hello world, this is a simplistic example of a CPER Section Body."; - const uint64_t kSectionBodySize = sectionBody.size(); - constexpr uint16_t kNumSections = 1; - - mockEncoder.addSection(sectionBody, kMockSectionFlag, kMockSectionType); - - EXPECT_CALL(mockEncoder, mockCreateRecordHeader).Times(1); - EXPECT_CALL(mockEncoder, mockCreateSectionDescriptor).Times(kNumSections); - const std::vector<uint8_t> cperArray = mockEncoder.serializeToByteArray(); - - const uint64_t kExpectedCperSize = - (kSectionBodySize + kSectionDescriptorSizeBytes + - kRecordHeaderSizeBytes); - - ASSERT_EQ(cperArray.size(), kExpectedCperSize); - expectValidHeader(cperArray.data(), cperArray.size(), kNumSections, - kExpectedCperSize, mockEncoder.timestamp); - - const uint32_t kExpectedOffset = - kRecordHeaderSizeBytes + (kNumSections * kSectionDescriptorSizeBytes); - expectValidSectionDescriptor(cperArray, 0, kExpectedOffset, - kSectionBodySize, kMockSectionFlag, - kMockSectionType); - - expectValidSectionBody<char>(cperArray, sectionBody, kExpectedOffset); + EXPECT_EQ(sections[0].body.size(), kSectionBodySize); + EXPECT_EQ( + memcmp(sections[0].body.data(), sectionBody.data(), kSectionBodySize), + 0); } TEST_F(CperEncoderTest, SeqMemcopy) @@ -416,21 +407,32 @@ TEST_F(CperEncoderTest, SmokeTest) { RecordHeader recordHeader; - const auto signature = getField<std::array<uint8_t, kRecordSignatureSize>>( - &recordHeader, offsetof(RecordHeader, signature)); + const void* headerPtr = &recordHeader; + + std::array<uint8_t, kRecordSignatureSize> signature; + std::memcpy(signature.data(), + static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, signature), + kRecordSignatureSize); + EXPECT_EQ( memcmp(signature.data(), kRecordSignature.data(), kRecordSignatureSize), 0); - EXPECT_EQ( - getField<uint16_t>(&recordHeader, offsetof(RecordHeader, revision)), - 0x0000); - EXPECT_EQ( - getField<uint32_t>(&recordHeader, offsetof(RecordHeader, signatureEnd)), - 0xffffffff); + EXPECT_EQ(littleEndianLoad<uint16_t>(static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, revision)), + 0x0000); + EXPECT_EQ(littleEndianLoad<uint32_t>(static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, signatureEnd)), + 0xffffffff); + std::array<uint8_t, 12> expectedReserved = {0}; - const auto reserved = getField<std::array<uint8_t, 12>>( - &recordHeader, offsetof(RecordHeader, reserved)); + std::array<uint8_t, 12> reserved; + std::memcpy(reserved.data(), + static_cast<const char*>(headerPtr) + + offsetof(RecordHeader, reserved), + 12); EXPECT_EQ(memcmp(reserved.data(), expectedReserved.data(), reserved.size()), 0); } + } // namespace uefi::cper
diff --git a/test/meson.build b/test/meson.build index 1fdc537..66ea45e 100644 --- a/test/meson.build +++ b/test/meson.build
@@ -15,12 +15,14 @@ default_test_deps = [ gmock_dep, + absl_endian_dep, ] test_cpp_args = ['-Db_sanitize=address,undefined,thread'] tests = [ 'cper_encoder_test', + 'cper_decoder_test', ] foreach t : tests