buffer: Implement "readErrorLogs"

This is the API that will be called by the main to read all of the error
logs. Found a cornercase in wraparoundRead, so added a unit test to
guard it as well.

Tested:
Unit tested and tested on a real HW (with the following changes)

Signed-off-by: Brandon Kim <brandonkim@google.com>
Change-Id: I94cd3a2f53ee7da2c145b751c1c3b5989f4c5b23
diff --git a/include/buffer.hpp b/include/buffer.hpp
index d42b843..d676165 100644
--- a/include/buffer.hpp
+++ b/include/buffer.hpp
@@ -136,6 +136,14 @@
      * * @return entry header and entry pair read from buffer
      */
     virtual EntryPair readEntry(size_t relativeOffset) = 0;
+
+    /**
+     * Read the buffer - this API should be used instead of individual functions
+     * above
+     *
+     * @return vector of EntryPair which consists of entry header and entry
+     */
+    virtual std::vector<EntryPair> readErrorLogs() = 0;
 };
 
 /**
@@ -158,6 +166,7 @@
                                         const uint32_t length) override;
     struct QueueEntryHeader readEntryHeader(size_t relativeOffset) override;
     EntryPair readEntry(size_t relativeOffset) override;
+    std::vector<EntryPair> readErrorLogs() 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 836049f..278f8cf 100644
--- a/src/buffer.cpp
+++ b/src/buffer.cpp
@@ -78,7 +78,7 @@
 {
     size_t headerSize = sizeof(struct CircularBufferHeader);
     std::vector<uint8_t> bytesRead =
-        dataInterface->read(/* offset */ 0, headerSize);
+        dataInterface->read(/*offset=*/0, headerSize);
 
     if (bytesRead.size() != headerSize)
     {
@@ -166,6 +166,12 @@
                         bytesRead.size(), numBytesToReadTillQueueEnd));
     }
     size_t updatedReadPtr = relativeOffset + numBytesToReadTillQueueEnd;
+    if (updatedReadPtr == queueSize)
+    {
+        // If we read all the way up to the end of the queue, we need to
+        // manually wrap the updateReadPtr around to 0
+        updatedReadPtr = 0;
+    }
 
     // If there are any more bytes to be read beyond the buffer, wrap around and
     // read from the beginning of the buffer (offset by the queueOffset)
@@ -226,4 +232,71 @@
     return {entryHeader, entry};
 }
 
+std::vector<EntryPair> BufferImpl::readErrorLogs()
+{
+    // Reading the buffer header will update the cachedBufferHeader
+    readBufferHeader();
+
+    const size_t queueSize =
+        boost::endian::little_to_native(cachedBufferHeader.queueSize);
+    size_t currentBiosWritePtr =
+        boost::endian::little_to_native(cachedBufferHeader.biosWritePtr);
+    if (currentBiosWritePtr > queueSize)
+    {
+        throw std::runtime_error(fmt::format(
+            "[readErrorLogs] currentBiosWritePtr was '{}' which was bigger "
+            "than queueSize '{}'",
+            currentBiosWritePtr, queueSize));
+    }
+    size_t currentReadPtr =
+        boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
+    if (currentReadPtr > queueSize)
+    {
+        throw std::runtime_error(fmt::format(
+            "[readErrorLogs] currentReadPtr was '{}' which was bigger "
+            "than queueSize '{}'",
+            currentReadPtr, queueSize));
+    }
+
+    size_t bytesToRead;
+    if (currentBiosWritePtr == currentReadPtr)
+    {
+        // No new payload was detected, return an empty vector gracefully
+        return {};
+    }
+
+    if (currentBiosWritePtr > currentReadPtr)
+    {
+        // Simply subtract in this case
+        bytesToRead = currentBiosWritePtr - currentReadPtr;
+    }
+    else
+    {
+        // Calculate the bytes to the "end" (QueueSize - ReadPtr) +
+        // bytes to read from the "beginning" (0 +  WritePtr)
+        bytesToRead = (queueSize - currentReadPtr) + currentBiosWritePtr;
+    }
+
+    size_t byteRead = 0;
+    std::vector<EntryPair> entryPairs;
+    while (byteRead < bytesToRead)
+    {
+        EntryPair entryPair = readEntry(currentReadPtr);
+        byteRead += sizeof(struct QueueEntryHeader) + entryPair.second.size();
+        entryPairs.push_back(entryPair);
+
+        // Note: readEntry() will update cachedBufferHeader.bmcReadPtr
+        currentReadPtr =
+            boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
+    }
+    if (currentBiosWritePtr != currentReadPtr)
+    {
+        throw std::runtime_error(fmt::format(
+            "[readErrorLogs] biosWritePtr '{}' and bmcReaddPtr '{}' "
+            "are not identical after reading through all the logs",
+            currentBiosWritePtr, currentReadPtr));
+    }
+    return entryPairs;
+}
+
 } // namespace bios_bmc_smm_error_logger
diff --git a/test/buffer_test.cpp b/test/buffer_test.cpp
index 6f886f0..577e322 100644
--- a/test/buffer_test.cpp
+++ b/test/buffer_test.cpp
@@ -210,6 +210,10 @@
   protected:
     BufferWraparoundReadTest()
     {
+        initializeFuncMock();
+    }
+    void initializeFuncMock()
+    {
         // Initialize the memory and the cachedBufferHeader
         InSequence s;
         EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
@@ -221,11 +225,7 @@
                     write(0, ElementsAreArray(emptyArray)))
             .WillOnce(Return(testProposedBufferSize));
 
-        uint8_t* testInitializationHeaderPtr =
-            reinterpret_cast<uint8_t*>(&testInitializationHeader);
-        EXPECT_CALL(*dataInterfaceMockPtr,
-                    write(0, ElementsAreArray(testInitializationHeaderPtr,
-                                              bufferHeaderSize)))
+        EXPECT_CALL(*dataInterfaceMockPtr, write(0, _))
             .WillOnce(Return(bufferHeaderSize));
         EXPECT_NO_THROW(bufferImpl->initialize(testBmcInterfaceVersion,
                                                testQueueSize, testUeRegionSize,
@@ -234,6 +234,9 @@
     static constexpr size_t expectedWriteSize = 2;
     static constexpr uint8_t expectedBmcReadPtrOffset = 0x20;
     static constexpr size_t expectedqueueOffset = 0x30 + testUeRegionSize;
+
+    uint8_t* testInitializationHeaderPtr =
+        reinterpret_cast<uint8_t*>(&testInitializationHeader);
 };
 
 TEST_F(BufferWraparoundReadTest, ParamsTooBigFail)
@@ -383,6 +386,35 @@
               testBytesLeft);
 }
 
+TEST_F(BufferWraparoundReadTest, WrapAroundCornerCasePass)
+{
+    InSequence s;
+    size_t testBytesLeft = 0;
+    size_t testLength = 4;
+    size_t testOffset = testQueueSize - (testLength - testBytesLeft);
+
+    // Read to the very end of the queue
+    std::vector<std::uint8_t> testBytes{4, 3, 2, 1};
+    EXPECT_CALL(*dataInterfaceMockPtr,
+                read(testOffset + expectedqueueOffset, testLength))
+        .WillOnce(Return(testBytes));
+
+    // Call to updateReadPtr is triggered, since we read to the very end of the
+    // buffer, update the readPtr up around to 0
+    const std::vector<uint8_t> expectedReadPtr{0x0, 0x0};
+    EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
+                                             ElementsAreArray(expectedReadPtr)))
+        .WillOnce(Return(expectedWriteSize));
+
+    EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
+                ElementsAreArray(testBytes));
+    struct CircularBufferHeader cachedBufferHeader =
+        bufferImpl->getCachedBufferHeader();
+    // The bmcReadPtr should have been updated to reflect the wraparound
+    EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
+              0);
+}
+
 class BufferEntryTest : public BufferWraparoundReadTest
 {
   protected:
@@ -500,5 +532,166 @@
               testEntrySize - 1);
 }
 
+class BufferReadErrorLogsTest : public BufferEntryTest
+{
+  protected:
+    BufferReadErrorLogsTest() = default;
+
+    uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
+    size_t entryAndHeaderSize = entryHeaderSize + testEntrySize;
+};
+
+TEST_F(BufferReadErrorLogsTest, PtrsTooBigFail)
+{
+    InSequence s;
+    // Set the biosWritePtr too big
+    testInitializationHeader.biosWritePtr =
+        boost::endian::native_to_little((testQueueSize + 1));
+    initializeFuncMock();
+
+    EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
+        .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
+                                              testInitializationHeaderPtr +
+                                                  bufferHeaderSize)));
+    EXPECT_THROW(
+        try {
+            bufferImpl->readErrorLogs();
+        } catch (const std::runtime_error& e) {
+            EXPECT_STREQ(e.what(),
+                         "[readErrorLogs] currentBiosWritePtr was '257' "
+                         "which was bigger than queueSize '256'");
+            throw;
+        },
+        std::runtime_error);
+
+    // Reset the biosWritePtr and set the bmcReadPtr too big
+    testInitializationHeader.biosWritePtr = 0;
+    initializeFuncMock();
+    testInitializationHeader.bmcReadPtr =
+        boost::endian::native_to_little((testQueueSize + 1));
+    initializeFuncMock();
+
+    EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
+        .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
+                                              testInitializationHeaderPtr +
+                                                  bufferHeaderSize)));
+    EXPECT_THROW(
+        try {
+            bufferImpl->readErrorLogs();
+        } catch (const std::runtime_error& e) {
+            EXPECT_STREQ(e.what(), "[readErrorLogs] currentReadPtr was '257' "
+                                   "which was bigger than queueSize '256'");
+            throw;
+        },
+        std::runtime_error);
+}
+
+TEST_F(BufferReadErrorLogsTest, IdenticalPtrsPass)
+{
+    EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
+        .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
+                                              testInitializationHeaderPtr +
+                                                  bufferHeaderSize)));
+    EXPECT_NO_THROW(bufferImpl->readErrorLogs());
+}
+
+TEST_F(BufferReadErrorLogsTest, NoWraparoundPass)
+{
+    InSequence s;
+    // Set the biosWritePtr to 1 entryHeader + entry size
+    testInitializationHeader.biosWritePtr =
+        boost::endian::native_to_little((entryAndHeaderSize));
+    initializeFuncMock();
+    EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
+        .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
+                                              testInitializationHeaderPtr +
+                                                  bufferHeaderSize)));
+    std::vector<uint8_t> testEntryHeaderVector(
+        testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
+    std::vector<uint8_t> testEntryVector(testEntrySize);
+    wraparoundReadMock(/*relativeOffset=*/0, testEntryHeaderVector);
+    wraparoundReadMock(/*relativeOffset=*/0 + entryHeaderSize, testEntryVector);
+
+    std::vector<EntryPair> entryPairs;
+    EXPECT_NO_THROW(entryPairs = bufferImpl->readErrorLogs());
+
+    // Check that we only read one entryPair and that the content is correct
+    EXPECT_EQ(entryPairs.size(), 1);
+    EXPECT_EQ(entryPairs[0].first, testEntryHeader);
+    EXPECT_THAT(entryPairs[0].second, ElementsAreArray(testEntryVector));
+}
+
+TEST_F(BufferReadErrorLogsTest, WraparoundMultiplEntryPass)
+{
+    InSequence s;
+    // Set the bmcReadPtr to 1 entryHeader + entry size from the "end" exactly
+    uint32_t entryAndHeaderSizeAwayFromEnd = testQueueSize - entryAndHeaderSize;
+    testInitializationHeader.bmcReadPtr =
+        boost::endian::native_to_little(entryAndHeaderSizeAwayFromEnd);
+    // Set the biosWritePtr to 1 entryHeader + entry size from the "beginning"
+    testInitializationHeader.biosWritePtr = entryAndHeaderSize;
+    initializeFuncMock();
+    EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
+        .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
+                                              testInitializationHeaderPtr +
+                                                  bufferHeaderSize)));
+
+    std::vector<uint8_t> testEntryHeaderVector(
+        testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
+    std::vector<uint8_t> testEntryVector(testEntrySize);
+    wraparoundReadMock(/*relativeOffset=*/entryAndHeaderSizeAwayFromEnd,
+                       testEntryHeaderVector);
+    wraparoundReadMock(/*relativeOffset=*/entryAndHeaderSizeAwayFromEnd +
+                           entryHeaderSize,
+                       testEntryVector);
+    wraparoundReadMock(/*relativeOffset=*/0 + entryAndHeaderSize,
+                       testEntryHeaderVector);
+    wraparoundReadMock(/*relativeOffset=*/0 + entryAndHeaderSize +
+                           entryHeaderSize,
+                       testEntryVector);
+
+    std::vector<EntryPair> entryPairs;
+    EXPECT_NO_THROW(entryPairs = bufferImpl->readErrorLogs());
+
+    // Check that we only read one entryPair and that the content is correct
+    EXPECT_EQ(entryPairs.size(), 2);
+    EXPECT_EQ(entryPairs[0].first, testEntryHeader);
+    EXPECT_EQ(entryPairs[1].first, testEntryHeader);
+    EXPECT_THAT(entryPairs[0].second, ElementsAreArray(testEntryVector));
+    EXPECT_THAT(entryPairs[1].second, ElementsAreArray(testEntryVector));
+}
+
+TEST_F(BufferReadErrorLogsTest, WraparoundMismatchingPtrsFail)
+{
+    InSequence s;
+    testInitializationHeader.bmcReadPtr = boost::endian::native_to_little(0);
+    // Make the biosWritePtr intentially 1 smaller than expected
+    testInitializationHeader.biosWritePtr =
+        boost::endian::native_to_little(entryAndHeaderSize - 1);
+    initializeFuncMock();
+    EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
+        .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
+                                              testInitializationHeaderPtr +
+                                                  bufferHeaderSize)));
+
+    std::vector<uint8_t> testEntryHeaderVector(
+        testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
+    std::vector<uint8_t> testEntryVector(testEntrySize);
+    wraparoundReadMock(/*relativeOffset=*/0, testEntryHeaderVector);
+    wraparoundReadMock(/*relativeOffset=*/0 + entryHeaderSize, testEntryVector);
+
+    EXPECT_THROW(
+        try {
+            bufferImpl->readErrorLogs();
+        } catch (const std::runtime_error& e) {
+            EXPECT_STREQ(
+                e.what(),
+                "[readErrorLogs] biosWritePtr '37' and bmcReaddPtr '38' "
+                "are not identical after reading through all the logs");
+            throw;
+        },
+        std::runtime_error);
+}
+
 } // namespace
 } // namespace bios_bmc_smm_error_logger