| #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 |