| #include "cper_decoder.hpp" |
| #include "cper_encoder.hpp" |
| #include "mock_cper_encoder.hpp" |
| |
| #include <chrono> |
| #include <cstring> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| using testing::_; |
| using testing::ElementsAreArray; |
| |
| namespace uefi::cper |
| { |
| template <typename T> |
| concept StringOrVector = |
| std::is_same_v<T, std::string> || std::is_same_v<T, std::vector<uint8_t>>; |
| |
| uint8_t bcdToDecimal(const uint8_t bcd) |
| { |
| uint8_t tens = bcd >> 4; |
| uint8_t ones = bcd & 0x0F; |
| if (ones > 9 || tens > 9) |
| { |
| return 0; |
| } |
| return (tens * 10) + ones; |
| } |
| |
| class CperEncoderTest : public testing::Test |
| { |
| public: |
| void SetUp() override |
| {} |
| |
| bool isArrayEq(const void* arr1, const void* arr2, const uint64_t arr1Size, |
| const uint64_t arr2Size) |
| { |
| if (arr1Size != arr2Size) |
| { |
| return false; |
| } |
| return memcmp(arr1, arr2, arr1Size) == 0; |
| } |
| |
| void expectValidHeader(const RecordHeader& header, |
| const uint16_t numSections, const uint64_t cperSize, |
| const uint64_t timestamp) |
| { |
| const void* headerPtr = &header; |
| |
| 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( |
| 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( |
| littleEndianLoad<uint16_t>(static_cast<const char*>(headerPtr) + |
| offsetof(RecordHeader, sectionCount)), |
| numSections); |
| EXPECT_EQ( |
| littleEndianLoad<uint32_t>(static_cast<const char*>(headerPtr) + |
| offsetof(RecordHeader, errorSeverity)), |
| kMockRecordSeverity); |
| EXPECT_EQ( |
| littleEndianLoad<uint32_t>(static_cast<const char*>(headerPtr) + |
| offsetof(RecordHeader, validationBits)), |
| kMockValidationBits); |
| EXPECT_EQ( |
| littleEndianLoad<uint32_t>(static_cast<const char*>(headerPtr) + |
| offsetof(RecordHeader, recordLength)), |
| cperSize); |
| EXPECT_EQ( |
| littleEndianLoad<uint64_t>(static_cast<const char*>(headerPtr) + |
| offsetof(RecordHeader, timestamp)), |
| timestamp); |
| |
| 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}; |
| 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); |
| } |
| |
| void expectValidSectionDescriptor(const SectionDescriptor& descriptor, |
| const uint32_t expectedBodyOffset, |
| const uint32_t expectedLength, |
| const uint32_t expectedFlag, |
| const guid_t& expectedSectionType) |
| { |
| const void* descPtr = &descriptor; |
| |
| EXPECT_EQ(littleEndianLoad<uint32_t>( |
| static_cast<const char*>(descPtr) + |
| offsetof(SectionDescriptor, sectionOffset)), |
| expectedBodyOffset); |
| EXPECT_EQ(littleEndianLoad<uint32_t>( |
| static_cast<const char*>(descPtr) + |
| offsetof(SectionDescriptor, sectionLength)), |
| expectedLength); |
| 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(static_cast<const uint8_t*>( |
| descPtr)[offsetof(SectionDescriptor, reserved)], |
| 0); |
| EXPECT_EQ( |
| littleEndianLoad<uint32_t>(static_cast<const char*>(descPtr) + |
| offsetof(SectionDescriptor, flags)), |
| expectedFlag); |
| |
| guid_t sectionType; |
| std::memcpy(§ionType, |
| static_cast<const char*>(descPtr) + |
| offsetof(SectionDescriptor, sectionType), |
| sizeof(guid_t)); |
| EXPECT_EQ(sectionType, expectedSectionType); |
| |
| 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()); |
| |
| 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); |
| } |
| |
| void expectEqTimestamp( |
| const uint64_t cperTimestamp, |
| const std::chrono::time_point<std::chrono::system_clock> ref, |
| const bool isPrecise = true) |
| { |
| Timestamp structTimestamp; |
| memcpy(&structTimestamp, &cperTimestamp, sizeof(structTimestamp)); |
| |
| structTimestamp.seconds = bcdToDecimal(structTimestamp.seconds); |
| structTimestamp.minutes = bcdToDecimal(structTimestamp.minutes); |
| structTimestamp.hours = bcdToDecimal(structTimestamp.hours); |
| structTimestamp.day = bcdToDecimal(structTimestamp.day); |
| structTimestamp.month = bcdToDecimal(structTimestamp.month) - 1; |
| structTimestamp.year = bcdToDecimal(structTimestamp.year); |
| structTimestamp.century = bcdToDecimal(structTimestamp.century); |
| |
| time_t tt = std::chrono::system_clock::to_time_t(ref); |
| tm utcTime; |
| ASSERT_NE(gmtime_r(&tt, &utcTime), nullptr); |
| |
| EXPECT_EQ(structTimestamp.seconds, utcTime.tm_sec); |
| EXPECT_EQ(structTimestamp.minutes, utcTime.tm_min); |
| EXPECT_EQ(structTimestamp.hours, utcTime.tm_hour); |
| EXPECT_EQ(structTimestamp.isPrecise, (isPrecise & 0x1)); |
| EXPECT_EQ(structTimestamp.day, utcTime.tm_mday); |
| EXPECT_EQ(structTimestamp.month, utcTime.tm_mon); |
| EXPECT_EQ(structTimestamp.year, (utcTime.tm_year + 1900) % 100); |
| EXPECT_EQ(structTimestamp.century, (utcTime.tm_year + 1900) / 100); |
| } |
| }; |
| |
| TEST_F(CperEncoderTest, TooMuchData) |
| { |
| MockCperEncoder<uint8_t> mockEncoder; |
| uint64_t totalSize = kRecordHeaderSizeBytes; |
| |
| for (uint64_t i = 0; i < std::numeric_limits<uint16_t>::max(); i++) |
| { |
| std::array<uint8_t, std::numeric_limits<uint16_t>::max()> sectionBody; |
| totalSize += sectionBody.size() + kSectionDescriptorSizeBytes; |
| |
| if (totalSize < std::numeric_limits<uint32_t>::max()) |
| { |
| EXPECT_TRUE(mockEncoder.addSection(sectionBody, kMockSectionFlag, |
| kMockSectionType)); |
| } |
| else |
| { |
| EXPECT_FALSE(mockEncoder.addSection(sectionBody, kMockSectionFlag, |
| kMockSectionType)); |
| return; |
| } |
| } |
| |
| FAIL() << "Should never be able to complete the loop"; |
| } |
| |
| TEST_F(CperEncoderTest, EncodeMultipleCharSectionsToArrayAndString) |
| { |
| MockCperEncoder<char> mockEncoder; |
| |
| std::vector<std::string> sectionBodies; |
| sectionBodies.push_back("The first principle is that you must not fool " |
| "yourself and you are the easiest person to fool."); |
| sectionBodies.push_back("Spread love everywhere you go."); |
| sectionBodies.push_back("The only thing we have to fear is fear itself."); |
| sectionBodies.push_back("If you can't explain it to a six year old, you " |
| "don't understand it yourself."); |
| sectionBodies.push_back("I have no wings, so I guess I'll look up at this " |
| "sky, and crawl along the Earth."); |
| sectionBodies.push_back("If I have seen further, it is by standing on the " |
| "shoulders of giants."); |
| |
| const uint16_t kNumSections = sectionBodies.size(); |
| |
| uint64_t totalSectionBodySize = 0; |
| for (const std::string& body : sectionBodies) |
| { |
| mockEncoder.addSection(body, kMockSectionFlag, kMockSectionType); |
| totalSectionBodySize += body.size(); |
| } |
| |
| const uint64_t kExpectedCperSize = |
| (kRecordHeaderSizeBytes + (kSectionDescriptorSizeBytes * kNumSections) + |
| totalSectionBodySize); |
| |
| // Serialize to an array. |
| EXPECT_CALL(mockEncoder, mockCreateRecordHeader).Times(1); |
| EXPECT_CALL(mockEncoder, mockCreateSectionDescriptor).Times(kNumSections); |
| const std::vector<uint8_t> cperArray = mockEncoder.serializeToByteArray(); |
| ASSERT_EQ(cperArray.size(), kExpectedCperSize); |
| |
| 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); |
| for (size_t i = 0; i < kNumSections; ++i) |
| { |
| const uint32_t kBodyLength = sectionBodies[i].size(); |
| expectValidSectionDescriptor(sections[i].descriptor, expectedBodyOffset, |
| kBodyLength, kMockSectionFlag, |
| kMockSectionType); |
| |
| EXPECT_EQ(sections[i].body.size(), kBodyLength); |
| EXPECT_EQ(memcmp(sections[i].body.data(), sectionBodies[i].data(), |
| kBodyLength), |
| 0); |
| |
| expectedBodyOffset += kBodyLength; |
| } |
| |
| // Serialize to a string. |
| EXPECT_CALL(mockEncoder, mockCreateRecordHeader).Times(1); |
| EXPECT_CALL(mockEncoder, mockCreateSectionDescriptor).Times(kNumSections); |
| const std::string cperStr = mockEncoder.serializeToString(); |
| ASSERT_EQ(cperStr.size(), kExpectedCperSize); |
| |
| 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) |
| { |
| MockCperEncoder<uint8_t> mockEncoder; |
| |
| const std::array<uint8_t, 10> sectionBody = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; |
| 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::string cperStr = mockEncoder.serializeToString(); |
| |
| const uint64_t kExpectedCperSize = |
| (kSectionBodySize + kSectionDescriptorSizeBytes + |
| kRecordHeaderSizeBytes); |
| |
| 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(sections[0].descriptor, kExpectedOffset, |
| kSectionBodySize, kMockSectionFlag, |
| kMockSectionType); |
| |
| EXPECT_EQ(sections[0].body.size(), kSectionBodySize); |
| EXPECT_EQ( |
| memcmp(sections[0].body.data(), sectionBody.data(), kSectionBodySize), |
| 0); |
| } |
| |
| TEST_F(CperEncoderTest, SeqMemcopy) |
| { |
| std::string section1 = "Hello"; |
| std::string section2 = " "; |
| std::string section3 = "World"; |
| |
| std::string dest(section1.size() + section2.size() + section3.size(), 0); |
| |
| SeqMemcopy<char> seqMemcpy(dest); |
| |
| EXPECT_NO_THROW(seqMemcpy.copy(section1.data(), section1.size())); |
| EXPECT_NO_THROW(seqMemcpy.copy(section2.data(), section2.size())); |
| EXPECT_NO_THROW(seqMemcpy.copy(section3.data(), section3.size())); |
| |
| EXPECT_EQ(dest, section1 + section2 + section3); |
| |
| std::string section4 = "This is an attempted buffer overflow!"; |
| |
| EXPECT_THROW(seqMemcpy.copy(section4.data(), section4.size()), |
| std::out_of_range); |
| } |
| |
| TEST_F(CperEncoderTest, CreateCperTimestamp) |
| { |
| const auto now = std::chrono::system_clock::now(); |
| |
| const uint64_t preciseTimestamp = createCperTimestamp(now); |
| expectEqTimestamp(preciseTimestamp, now); |
| |
| const uint64_t impreciseTimestamp = createCperTimestamp(now, false); |
| expectEqTimestamp(impreciseTimestamp, now, false); |
| } |
| |
| TEST_F(CperEncoderTest, SmokeTest) |
| { |
| RecordHeader recordHeader; |
| 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(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}; |
| 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 |