blob: 1de0facc24c51616b9845985534d12688e6f0305 [file]
#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(&notificationType,
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(&sectionType,
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