buffer: Implement "readEntry"

readEntry reads the entryHeader to figure out how much of the buffer to
read for the entry.

Tested: Unit tested

Signed-off-by: Brandon Kim <brandonkim@google.com>
Change-Id: I390e77c088439c74d100ef4b4cb35e746facd495
diff --git a/include/buffer.hpp b/include/buffer.hpp
index d199a94..240845c 100644
--- a/include/buffer.hpp
+++ b/include/buffer.hpp
@@ -17,6 +17,10 @@
 using boost::endian::little_uint32_t;
 using boost::endian::little_uint64_t;
 
+// EntryPair.first = QueueEntryHeader
+// EntryPair.second = Error entry in vector of bytes
+using EntryPair = std::pair<struct QueueEntryHeader, std::vector<uint8_t>>;
+
 struct CircularBufferHeader
 {
     little_uint32_t bmcInterfaceVersion;        // Offset 0x0
@@ -124,6 +128,14 @@
      * @return the entry header
      */
     virtual struct QueueEntryHeader readEntryHeader(size_t offset) = 0;
+
+    /**
+     * Read the queue entry from the error log queue
+     *
+     * @param[in] offset - offset to read from
+     * @return entry header and entry pair read from buffer
+     */
+    virtual EntryPair readEntry(size_t offset) = 0;
 };
 
 /**
@@ -146,6 +158,7 @@
         wraparoundRead(const uint32_t offset, const uint32_t length,
                        const uint32_t additionalBoundaryCheck = 0) override;
     struct QueueEntryHeader readEntryHeader(size_t offset) override;
+    EntryPair readEntry(size_t offset) override;
 
   private:
     /** @brief The Error log queue starts after the UE region, which is where
diff --git a/src/buffer.cpp b/src/buffer.cpp
index 87d9fa2..fca8837 100644
--- a/src/buffer.cpp
+++ b/src/buffer.cpp
@@ -12,6 +12,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <memory>
+#include <numeric>
 #include <span>
 #include <vector>
 
@@ -170,4 +171,31 @@
     return *reinterpret_cast<struct QueueEntryHeader*>(bytesRead.data());
 }
 
+EntryPair BufferImpl::readEntry(size_t offset)
+{
+    struct QueueEntryHeader entryHeader = readEntryHeader(offset);
+
+    size_t entrySize = entryHeader.entrySize;
+
+    // wraparonudRead may throw if entrySize was bigger than the buffer or if it
+    // was not able to read all bytes, let it propagate up the stack
+    // - Use additionalBoundaryCheck parameter to tighten the boundary check
+    std::vector<uint8_t> entry =
+        wraparoundRead(offset + sizeof(struct QueueEntryHeader), entrySize,
+                       sizeof(struct QueueEntryHeader));
+
+    // Calculate the checksum
+    uint8_t* entryHeaderPtr = reinterpret_cast<uint8_t*>(&entryHeader);
+    uint8_t checksum = std::accumulate(
+        entryHeaderPtr, entryHeaderPtr + sizeof(struct QueueEntryHeader), 0);
+    checksum = std::accumulate(std::begin(entry), std::end(entry), checksum);
+    if (checksum != 0)
+    {
+        throw std::runtime_error(fmt::format(
+            "[readEntry] Checksum was '{}', expected '0'", checksum));
+    }
+
+    return {entryHeader, entry};
+}
+
 } // namespace bios_bmc_smm_error_logger
diff --git a/test/buffer_test.cpp b/test/buffer_test.cpp
index 9042f67..7218f8d 100644
--- a/test/buffer_test.cpp
+++ b/test/buffer_test.cpp
@@ -343,17 +343,17 @@
                 ElementsAreArray(expectedBytes));
 }
 
-class BufferEntryHeaderTest : public BufferWraparoundReadTest
+class BufferEntryTest : public BufferWraparoundReadTest
 {
   protected:
-    BufferEntryHeaderTest()
+    BufferEntryTest()
     {
         testEntryHeader.sequenceId = testSequenceId;
         testEntryHeader.entrySize = testEntrySize;
         testEntryHeader.checksum = testChecksum;
         testEntryHeader.rdeCommandType = testRdeCommandType;
     }
-    ~BufferEntryHeaderTest() override = default;
+    ~BufferEntryTest() override = default;
 
     void wraparoundReadMock(std::span<std::uint8_t> expetedBytesOutput)
     {
@@ -370,7 +370,8 @@
     static constexpr size_t entryHeaderSize = sizeof(struct QueueEntryHeader);
     static constexpr uint16_t testSequenceId = 0;
     static constexpr uint16_t testEntrySize = 0x20;
-    static constexpr uint8_t testChecksum = 1;
+    // Calculated checksum for the header (0x100 - 0 - 0x20 - 0x01) & 0xff
+    static constexpr uint8_t testChecksum = 0xdf;
     static constexpr uint8_t testRdeCommandType = 0x01;
     size_t testOffset = 0x50;
 
@@ -378,7 +379,7 @@
     {};
 };
 
-TEST_F(BufferEntryHeaderTest, ReadEntryHeaderPass)
+TEST_F(BufferEntryTest, ReadEntryHeaderPass)
 {
     uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
     std::vector<uint8_t> testEntryHeaderVector(
@@ -387,5 +388,47 @@
     EXPECT_EQ(bufferImpl->readEntryHeader(testOffset), testEntryHeader);
 }
 
+TEST_F(BufferEntryTest, ReadEntryChecksumFail)
+{
+    InSequence s;
+    // We expect this will bump checksum up by "testEntrySize" = 0x20
+    std::vector<uint8_t> testEntryVector(testEntrySize, 1);
+    // Offset the checksum by 1
+    testEntryHeader.checksum -= (0x20 - 1);
+    uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
+    std::vector<uint8_t> testEntryHeaderVector(
+        testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
+    wraparoundReadMock(testEntryHeaderVector);
+
+    wraparoundReadMock(testEntryVector);
+    EXPECT_THROW(
+        try {
+            bufferImpl->readEntry(testOffset);
+        } catch (const std::runtime_error& e) {
+            EXPECT_STREQ(e.what(),
+                         "[readEntry] Checksum was '1', expected '0'");
+            throw;
+        },
+        std::runtime_error);
+}
+
+TEST_F(BufferEntryTest, ReadEntryPass)
+{
+    InSequence s;
+    // We expect this will bump checksum up by "testEntrySize" = 0x40
+    std::vector<uint8_t> testEntryVector(testEntrySize, 2);
+    testEntryHeader.checksum -= (0x40);
+    uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
+    std::vector<uint8_t> testEntryHeaderVector(
+        testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
+    wraparoundReadMock(testEntryHeaderVector);
+    wraparoundReadMock(testEntryVector);
+
+    EntryPair testedEntryPair;
+    EXPECT_NO_THROW(testedEntryPair = bufferImpl->readEntry(testOffset));
+    EXPECT_EQ(testedEntryPair.first, testEntryHeader);
+    EXPECT_THAT(testedEntryPair.second, ElementsAreArray(testEntryVector));
+}
+
 } // namespace
 } // namespace bios_bmc_smm_error_logger