blob: f38cc722a0088a09ae6a5758811336a2acfda742 [file] [log] [blame]
#include "cper_encoder.hpp"
#include "mock_cper_encoder.hpp"
#include <chrono>
#include <concepts>
#include "gtest/gtest.h"
#include <gmock/gmock.h>
using testing::_;
using testing::Contains;
namespace uefi::cper
{
template <typename T>
concept StringOrVector =
std::is_same_v<T, std::string> || std::is_same_v<T, std::vector<uint8_t>>;
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;
}
template <StringOrVector CperType>
void expectValidHeader(const CperType& cperArray,
const uint16_t numSections, const uint64_t cperSize,
const uint64_t timestamp)
{
RecordHeader header;
memcpy(&header, cperArray.data(), sizeof(header));
EXPECT_EQ(header.signature, kRecordSignature);
EXPECT_EQ(header.revision, kRecordRevision);
EXPECT_EQ(header.signatureEnd, kRecordSignatureEnd);
EXPECT_EQ(header.sectionCount, numSections);
EXPECT_EQ(header.errorSeverity, kMockRecordSeverity);
EXPECT_EQ(header.validationBits, kMockValidationBits);
EXPECT_EQ(header.recordLength, cperSize);
EXPECT_EQ(header.timestamp, timestamp);
EXPECT_EQ(header.platformId, kMockPlatformId);
EXPECT_EQ(header.partitionId, kMockPartitionId);
EXPECT_EQ(header.creatorId, kMockCreatorId);
EXPECT_EQ(header.notificationType, kMockNotificationType);
EXPECT_EQ(header.recordId, kMockRecordId);
EXPECT_EQ(header.flags, kMockHeaderFlags);
EXPECT_EQ(header.persistenceInformation, kMockPersistenceInformation);
EXPECT_THAT(header.reserved, Contains(0).Times(header.reserved.size()));
}
template <StringOrVector CperType>
void expectValidSectionDescriptor(const CperType& cperArray,
const uint64_t sectionNum,
const uint32_t expectedOffset,
const uint32_t expectedLength,
const uint32_t expectedFlag,
const guid_t expectedSectionType)
{
const uint64_t sectionOffset =
kRecordHeaderSizeBytes + (kSectionDescriptorSizeBytes * sectionNum);
SectionDescriptor descriptor;
memcpy(&descriptor, &cperArray[sectionOffset], sizeof(descriptor));
EXPECT_EQ(descriptor.sectionOffset, expectedOffset);
EXPECT_EQ(descriptor.sectionLength, expectedLength);
EXPECT_EQ(descriptor.revision, kMockSectionRevision);
EXPECT_EQ(descriptor.validationBits, kMockSectionValidationBits);
EXPECT_EQ(descriptor.reserved, 0);
EXPECT_EQ(descriptor.flags, expectedFlag);
EXPECT_EQ(descriptor.sectionType, expectedSectionType);
EXPECT_EQ(descriptor.fruId, kMockFruId);
EXPECT_EQ(descriptor.sectionSeverity, kMockSectionSeverity);
EXPECT_EQ(descriptor.fruText, kMockFruText);
}
template <ByteLike BodyType, StringOrVector CperType>
void expectValidSectionBody(
const CperType& cperArray,
const std::span<const BodyType> expectedSectionBody,
const uint64_t totalSections, const uint32_t sectionOffset)
{
// The offset set in the section descriptors is the offset in bytes
// after the header and descriptors.
const uint32_t kTrueBodyOffset =
sectionOffset + kRecordHeaderSizeBytes +
(kSectionDescriptorSizeBytes * totalSections);
const uint64_t kSectionBodySize = expectedSectionBody.size();
ASSERT_GE(cperArray.size(), (kTrueBodyOffset + kSectionBodySize));
CperType sectionBody(kSectionBodySize, 0);
memcpy(sectionBody.data(), &cperArray[kTrueBodyOffset],
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,
const bool isPrecise = true)
{
Timestamp structTimestamp;
memcpy(&structTimestamp, &cperTimestamp, sizeof(structTimestamp));
time_t tt = std::chrono::system_clock::to_time_t(ref);
tm utcTime = *gmtime(&tt);
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;
// Exceed the 4GiB CPER Log max size by adding 2^16 * 2^16 Bytes worth of
// section bodies along with their descriptors.
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;
// Note the encoder does not own the sectionBody, it just tracks the
// body ptrs and size. This would never work in a real application since
// the sectionBody only exists within each loops iteration.
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;
// Fill our CPER Log with some quotes to get through the work week.
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();
// Add the sections to our encoder.
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);
// Check that the serialized arrays header contains the same contents we
// configured the encoder to use.
expectValidHeader(cperArray, kNumSections, kExpectedCperSize,
mockEncoder.timestamp);
// Check that the section descriptors and bodies all contain the correct
// information about their respective section.
uint32_t expectedOffset = 0;
uint64_t sectionIdx = 0;
for (const std::string& body : sectionBodies)
{
const uint32_t kBodyLength = body.size();
expectValidSectionDescriptor(cperArray, sectionIdx, expectedOffset,
kBodyLength, kMockSectionFlag,
kMockSectionType);
expectValidSectionBody<char>(cperArray, body, kNumSections,
expectedOffset);
expectedOffset += kBodyLength;
sectionIdx++;
}
// 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);
// Check that the serialized arrays header contains the same contents we
// configured the encoder to use.
expectValidHeader(cperStr, kNumSections, kExpectedCperSize,
mockEncoder.timestamp);
// Check that the section descriptors and bodies all contain the correct
// information about their respective section.
expectedOffset = 0;
sectionIdx = 0;
for (const std::string& body : sectionBodies)
{
const uint32_t kBodyLength = body.size();
expectValidSectionDescriptor(cperStr, sectionIdx, expectedOffset,
kBodyLength, kMockSectionFlag,
kMockSectionType);
expectValidSectionBody<char>(cperStr, body, kNumSections,
expectedOffset);
expectedOffset += kBodyLength;
sectionIdx++;
}
}
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;
// Add 1 section
mockEncoder.addSection(sectionBody, kMockSectionFlag, kMockSectionType);
// Serialize the data into a CPER byte array.
EXPECT_CALL(mockEncoder, mockCreateRecordHeader).Times(1);
EXPECT_CALL(mockEncoder, mockCreateSectionDescriptor).Times(kNumSections);
const std::string cperArray = mockEncoder.serializeToString();
// If this bricks, abort ship - there is likely memory corruption.
const uint64_t kExpectedCperSize =
(kSectionBodySize + kSectionDescriptorSizeBytes +
kRecordHeaderSizeBytes);
ASSERT_EQ(cperArray.size(), kExpectedCperSize);
// Check that the serialized arrays header contains the same contents we
// configured the encoder to use.
expectValidHeader(cperArray, kNumSections, kExpectedCperSize,
mockEncoder.timestamp);
// Check that the section descriptor contains the correct information about
// the section.
const uint32_t kExpectedOffset = 0;
expectValidSectionDescriptor(cperArray, 0, kExpectedOffset,
kSectionBodySize, kMockSectionFlag,
kMockSectionType);
expectValidSectionBody<uint8_t>(cperArray, sectionBody, kNumSections,
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;
// Add 1 section
mockEncoder.addSection(sectionBody, kMockSectionFlag, kMockSectionType);
// Serialize the data into a CPER byte array.
EXPECT_CALL(mockEncoder, mockCreateRecordHeader).Times(1);
EXPECT_CALL(mockEncoder, mockCreateSectionDescriptor).Times(kNumSections);
const std::vector<uint8_t> cperArray = mockEncoder.serializeToByteArray();
// If this bricks, abort ship - there is likely memory corruption.
const uint64_t kExpectedCperSize =
(kSectionBodySize + kSectionDescriptorSizeBytes +
kRecordHeaderSizeBytes);
ASSERT_EQ(cperArray.size(), kExpectedCperSize);
// Check that the serialized arrays header contains the same contents we
// configured the encoder to use.
expectValidHeader(cperArray, kNumSections, kExpectedCperSize,
mockEncoder.timestamp);
// Check that the section descriptor contains the correct information about
// the section.
const uint32_t kExpectedOffset = 0;
expectValidSectionDescriptor(cperArray, 0, kExpectedOffset,
kSectionBodySize, kMockSectionFlag,
kMockSectionType);
expectValidSectionBody<char>(cperArray, sectionBody, kNumSections,
kExpectedOffset);
}
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 std::array<uint8_t, 4> kExpectedSignature = {'C', 'P', 'E', 'R'};
const std::array<uint8_t, 12> kExpectedReserved = {0};
EXPECT_EQ(recordHeader.signature, kExpectedSignature);
EXPECT_EQ(recordHeader.revision, 0x0000);
EXPECT_EQ(recordHeader.signatureEnd, 0xffffffff);
EXPECT_EQ(recordHeader.reserved, kExpectedReserved);
// Check for proper packing by putting a needle in the haystack.
recordHeader.flags = 0xBEEF;
const size_t flagsOffset = 104;
std::array<uint8_t, sizeof(recordHeader)> headerBin;
memcpy(&headerBin, &recordHeader, sizeof(headerBin));
uint32_t flagsValue;
memcpy(&flagsValue, &headerBin[flagsOffset], sizeof(flagsValue));
EXPECT_EQ(recordHeader.flags, flagsValue);
}
} // namespace uefi::cper