blob: 4258cf1ffd3b43c2f4496911f296898b0a054110 [file] [log] [blame] [edit]
#pragma once
#include "cper.hpp"
#include <chrono>
#include <concepts>
#include <cstdint>
#include <cstring>
#include <string>
namespace uefi::cper
{
template <typename T>
concept ByteLike = std::is_trivially_copyable_v<T> && sizeof(T) == 1;
/**
* @brief Helper function to convert time values into BCD format.
*
* @example You may have minutes represented as base10 20 which is 0x14. BCD
* expects the values to be represented as 0x20.
*
* @param[in] val - The value to convert to BCD.
*
* @returns a byte with the BCD val.
*/
uint8_t convertToBcd(uint8_t val)
{
return (0x10 * (val / 10)) + (val % 10);
}
/**
* @brief Helper function to create a UTC UEFI CPER timestamp from a
* std::chrono time point.
* https://uefi.org/specs/UEFI/2.10/Apx_N_Common_Platform_Error_Record.html#record-header
*
* @param[in] timeOfCollection - A std::chrono time_point representing the time
* the fault was detected by the system software.
* @param[in] isPrecise - Bool which indicates if the time is precise.
*
* @param[out] A uefi cper timestamp as uint64_t.
*/
uint64_t createCperTimestamp(
const std::chrono::time_point<std::chrono::system_clock> timeOfCollection,
const bool isPrecise = true)
{
// Number of years in a century.
constexpr int kCentury = 100;
// The tm_year counts the number of years since 1900.
// https://en.cppreference.com/w/c/chrono/tm
constexpr int kTmYearOffset = 1900;
// UEFI expects the isPrecise byte bits [1:7] to be zero.
const uint8_t isPreciseBit = 0 | (isPrecise & 0x1);
const time_t tt = std::chrono::system_clock::to_time_t(timeOfCollection);
const tm utcTime = *gmtime(&tt);
// The uefi timestamp breaks the calendar year into two bytes
// representing the number of centuries and number of years.
const int calendarYear = utcTime.tm_year + kTmYearOffset;
const uint8_t year = calendarYear % kCentury;
const uint8_t century = calendarYear / kCentury;
const Timestamp timestamp{
.seconds = convertToBcd(utcTime.tm_sec),
.minutes = convertToBcd(utcTime.tm_min),
.hours = convertToBcd(utcTime.tm_hour),
.isPrecise = isPreciseBit,
.day = convertToBcd(utcTime.tm_mday),
// tm_mon is months since January, add one to get current month.
.month = convertToBcd(utcTime.tm_mon + 1),
.year = convertToBcd(year),
.century = convertToBcd(century)};
uint64_t ret;
memcpy(&ret, &timestamp, sizeof(ret));
return ret;
}
template <ByteLike ByteType>
class SeqMemcopy
{
public:
// Prevent the class from outliving the baseDest pointer.
SeqMemcopy() = delete;
SeqMemcopy(const SeqMemcopy&) = delete;
SeqMemcopy& operator=(const SeqMemcopy&) = delete;
/**
* @brief Manages sequentially copying chunks of data to a contiguous memory
* block.
*
* @param[in] baseDest - The memory location to copy to.
*/
SeqMemcopy(std::span<ByteType> baseDest) : baseDest_(baseDest), offset_(0)
{}
/**
* @brief Copies the src to the next section in the dest address.
*
* @param[in] src - The source address to copy from.
* @param[in] srcSize - The source size.
*
* @throws out_of_range If source size goes out of the destinations bounds.
*/
void copy(const void* src, const uint64_t srcSize)
{
if ((offset_ + srcSize) > baseDest_.size_bytes())
{
throw std::out_of_range(
"Attempt to copy out of the destinations bounds");
}
memcpy(baseDest_.data() + offset_, src, srcSize);
offset_ += srcSize;
}
private:
std::span<ByteType> baseDest_;
uint64_t offset_;
};
template <ByteLike BodyType>
class CperEncoder
{
public:
CperEncoder() : totalSectionBodySizeBytes_(0)
{}
/**
* @brief Implementation specific CPER encoders must define how to create
* RecordHeaders.
* https://uefi.org/specs/UEFI/2.10/Apx_N_Common_Platform_Error_Record.html#record-header
*
* @param[in] recordLength - The length in bytes of the entire record.
* @param[in] sectionCount - The number of sections in the record.
*/
virtual RecordHeader createRecordHeader(const uint32_t recordLength,
const uint16_t sectionCount) = 0;
/**
* @brief Implementation specific CPER encoders must define how to create
* new sectionDescriptors.
* https://uefi.org/specs/UEFI/2.10/Apx_N_Common_Platform_Error_Record.html#section-descriptor
*
* @param[in] sectionLength - The length in bytes of the section body.
* @param[in] sectionOffset - The offset in bytes from the section body from
* the base of the record header.
* @param[in] sectionFlags - Information that describes the error section.
* @param[in] sectionType - The ID of the sectionType; useful for
* implementations with multiple section types.
*
* @return A SectionDescriptor struct.
*/
virtual SectionDescriptor createSectionDescriptor(
const uint32_t sectionLength, const uint32_t sectionOffset,
const uint32_t sectionFlags, const guid_t sectionType) = 0;
/**
* @brief Adds a new CPER section for the encoder to track.
*
* @note Cper logs have a max size of 4GiB including the header and
* descriptors.
*
* @param[in] sectionBody - A span containing the section data as a byte
* array.
* @param[in] sectionFlags - The CPER flags for the section.
* @param[in] sectionType - The guid_t for the section.
*
* @warning The CperEncoder class does not own the ptrs of the
* sectionBodies, it merely tracks the addresses and size. It is expected
* that the data added to the encoder exists for the entire lifetime of the
* CperEncoder object.
*
* @returns true on success, false if new section goes over the 4GiB
* threshold.
*/
bool addSection(const std::span<const BodyType> sectionBody,
const uint32_t sectionFlags, const guid_t sectionType)
{
if ((calculateRecordLengthBytes() + sectionBody.size_bytes() +
kSectionDescriptorSizeBytes) >
std::numeric_limits<uint32_t>::max())
{
return false;
}
sectionSeeds_.emplace_back(sectionFlags, sectionType, sectionBody);
totalSectionBodySizeBytes_ += sectionBody.size_bytes();
return true;
}
/**
* @brief Serializes the CPER Log into a vector of bytes.
*
* @return A byte array containing the CPER log contents.
*/
std::vector<uint8_t> serializeToByteArray()
{
const uint64_t totalBytes = calculateRecordLengthBytes();
std::vector<uint8_t> byteArray(totalBytes);
encode<uint8_t>(byteArray);
return byteArray;
}
/**
* @brief Serializes the CPER Log into a string.
*
* @return A string containing the CPER log contents.
*/
std::string serializeToString()
{
const uint64_t totalBytes = calculateRecordLengthBytes();
std::string str(totalBytes, 0);
encode<char>(str);
return str;
}
private:
// Save ~3x memory by only caching the section "seeds" and keeping a
// single full sectionDescriptor on the stack at a time while encoding
// the log.
struct SectionSeed
{
uint32_t sectionFlags;
guid_t sectionType;
std::span<const BodyType> body;
};
/**
* @brief Calculates the total size of the CPER log.
*
* @return The total size in bytes.
*/
uint32_t calculateRecordLengthBytes() const
{
return kRecordHeaderSizeBytes +
(sectionSeeds_.size() * kSectionDescriptorSizeBytes) +
totalSectionBodySizeBytes_;
}
/**
* @brief Encodes the CPER byte array to the destination pointer.
*
* @tparam ByteType - Allows for different cpp byte representations (char,
* unsigned char, std::byte, etc.)
*
* @param[in,out] dest - The destination span to copy the data to.
*
* @throws If unable to successfully encode the CPER log.
*/
template <ByteLike ByteType>
void encode(std::span<ByteType> dest)
{
SeqMemcopy seqMemcpy(dest);
{
const RecordHeader header =
createRecordHeader(dest.size_bytes(), sectionSeeds_.size());
seqMemcpy.copy(&header, kRecordHeaderSizeBytes);
}
// Offset in bytes of the section body from the base of the record
// header.
uint64_t sectionOffset =
kRecordHeaderSizeBytes +
(sectionSeeds_.size() * kSectionDescriptorSizeBytes);
for (const SectionSeed& seed : sectionSeeds_)
{
// Creates a struct with the following modifiable fields - note this
// is a temporary struct which is then sequentially copied to a
// contiguous array.
const SectionDescriptor descriptor =
createSectionDescriptor(seed.body.size_bytes(), sectionOffset,
seed.sectionFlags, seed.sectionType);
sectionOffset += seed.body.size_bytes();
seqMemcpy.copy(&descriptor, kSectionDescriptorSizeBytes);
}
for (const auto& seed : sectionSeeds_)
{
seqMemcpy.copy(seed.body.data(), seed.body.size_bytes());
}
}
uint32_t totalSectionBodySizeBytes_;
std::vector<SectionSeed> sectionSeeds_;
};
} // namespace uefi::cper