buffer: Implement "initialize" and add unit tests

Add buffer headers and the initialization process

Tested: Unit Tested

Signed-off-by: Brandon Kim <brandonkim@google.com>
Change-Id: Iaf3c26ce01f7109000266cdbc7efa77988eae73b
diff --git a/include/buffer.hpp b/include/buffer.hpp
new file mode 100644
index 0000000..972884e
--- /dev/null
+++ b/include/buffer.hpp
@@ -0,0 +1,84 @@
+#pragma once
+
+#include "pci_handler.hpp"
+
+#include <array>
+#include <cstdint>
+#include <memory>
+
+namespace bios_bmc_smm_error_logger
+{
+
+struct CircularBufferHeader
+{
+    uint32_t bmcInterfaceVersion;        // Offset 0x0
+    uint32_t biosInterfaceVersion;       // Offset 0x4
+    std::array<uint32_t, 4> magicNumber; // Offset 0x8
+    uint16_t queueSize;                  // Offset 0x18
+    uint16_t ueRegionSize;               // Offset 0x1a
+    uint32_t bmcFlags;                   // Offset 0x1c
+    uint16_t bmcReadPtr;                 // Offset 0x20
+    std::array<uint8_t, 6> padding1;     // Offset 0x22
+    uint32_t biosFlags;                  // Offset 0x28
+    uint16_t biosWritePtr;               // Offset 0x2c
+    std::array<uint8_t, 2> padding2;     // Offset 0x2e
+    // UE reserved region:                  Offset 0x30
+    // Error log queue:                     Offset 0x30 + UE reserved region
+
+    bool operator==(const CircularBufferHeader& other) const
+    {
+        return this->bmcInterfaceVersion == other.bmcInterfaceVersion &&
+               this->biosInterfaceVersion == other.biosInterfaceVersion &&
+               this->magicNumber == other.magicNumber &&
+               this->queueSize == other.queueSize &&
+               this->ueRegionSize == other.ueRegionSize &&
+               this->bmcFlags == other.bmcFlags &&
+               this->bmcReadPtr == other.bmcReadPtr &&
+               /* Skip comparing padding1 */
+               this->biosFlags == other.biosFlags &&
+               this->biosWritePtr == other.biosWritePtr;
+        /* Skip comparing padding2 */
+    }
+} __attribute__((__packed__));
+
+/**
+ * An interface class for the buffer helper APIs
+ */
+class BufferInterface
+{
+  public:
+    virtual ~BufferInterface() = default;
+
+    /**
+     * Zero out the buffer first before populating the header
+     *
+     * @param[in] bmcInterfaceVersion - Used to initialize the header
+     * @param[in] queueSize - Used to initialize the header
+     * @param[in] ueRegionSize - Used to initialize the header
+     * @param[in] magicNumber - Used to initialize the header
+     * @return true if successful
+     */
+    virtual void initialize(uint32_t bmcInterfaceVersion, uint16_t queueSize,
+                            uint16_t ueRegionSize,
+                            const std::array<uint32_t, 4>& magicNumber) = 0;
+};
+
+/**
+ * Buffer implementation class
+ */
+class BufferImpl : public BufferInterface
+{
+  public:
+    /** @brief Constructor for BufferImpl
+     *  @param[in] dataInterface     - DataInterface for this object
+     */
+    explicit BufferImpl(std::unique_ptr<DataInterface> dataInterface);
+    void initialize(uint32_t bmcInterfaceVersion, uint16_t queueSize,
+                    uint16_t ueRegionSize,
+                    const std::array<uint32_t, 4>& magicNumber) override;
+
+  private:
+    std::unique_ptr<DataInterface> dataInterface;
+};
+
+} // namespace bios_bmc_smm_error_logger
diff --git a/src/buffer.cpp b/src/buffer.cpp
new file mode 100644
index 0000000..a0491eb
--- /dev/null
+++ b/src/buffer.cpp
@@ -0,0 +1,55 @@
+#include "buffer.hpp"
+
+#include "pci_handler.hpp"
+
+#include <fmt/format.h>
+
+#include <array>
+#include <cstdint>
+#include <memory>
+#include <span>
+#include <vector>
+
+namespace bios_bmc_smm_error_logger
+{
+
+BufferImpl::BufferImpl(std::unique_ptr<DataInterface> dataInterface) :
+    dataInterface(std::move(dataInterface)){};
+
+void BufferImpl::initialize(uint32_t bmcInterfaceVersion, uint16_t queueSize,
+                            uint16_t ueRegionSize,
+                            const std::array<uint32_t, 4>& magicNumber)
+{
+    // Initialize the whole buffer with 0x00
+    const size_t memoryRegionSize = dataInterface->getMemoryRegionSize();
+    const std::vector<uint8_t> emptyVector(memoryRegionSize, 0);
+    size_t byteWritten = dataInterface->write(0, emptyVector);
+    if (byteWritten != memoryRegionSize)
+    {
+        throw std::runtime_error(
+            fmt::format("Buffer initialization only erased '{}'", byteWritten));
+    }
+
+    // Create an initial buffer header and write to it
+    struct CircularBufferHeader initializationHeader = {};
+    initializationHeader.bmcInterfaceVersion = bmcInterfaceVersion;
+    initializationHeader.queueSize = queueSize;
+    initializationHeader.ueRegionSize = ueRegionSize;
+    initializationHeader.magicNumber = magicNumber;
+
+    uint8_t* initializationHeaderPtr =
+        reinterpret_cast<uint8_t*>(&initializationHeader);
+    size_t initializationHeaderSize = sizeof(initializationHeader);
+    byteWritten = dataInterface->write(
+        0, std::span<const uint8_t>(initializationHeaderPtr,
+                                    initializationHeaderPtr +
+                                        initializationHeaderSize));
+    if (byteWritten != initializationHeaderSize)
+    {
+        throw std::runtime_error(fmt::format(
+            "Buffer initialization buffer header write only wrote '{}'",
+            byteWritten));
+    }
+}
+
+} // namespace bios_bmc_smm_error_logger
diff --git a/src/meson.build b/src/meson.build
index c7f76bd..fd0ead4 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -24,6 +24,7 @@
 bios_bmc_smm_error_logger_lib = static_library(
   'bios_bmc_smm_error_logger',
   'pci_handler.cpp',
+  'buffer.cpp',
   implicit_include_directories: false,
   dependencies: bios_bmc_smm_error_logger_pre)
 
diff --git a/test/buffer_test.cpp b/test/buffer_test.cpp
new file mode 100644
index 0000000..ee03163
--- /dev/null
+++ b/test/buffer_test.cpp
@@ -0,0 +1,114 @@
+#include "buffer.hpp"
+#include "data_interface_mock.hpp"
+
+#include <array>
+#include <cstdint>
+#include <memory>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace bios_bmc_smm_error_logger
+{
+namespace
+{
+
+using ::testing::_;
+using ::testing::ElementsAreArray;
+using ::testing::InSequence;
+using ::testing::Return;
+
+class BufferTest : public ::testing::Test
+{
+  protected:
+    BufferTest() :
+        dataInterfaceMock(std::make_unique<DataInterfaceMock>()),
+        dataInterfaceMockPtr(dataInterfaceMock.get())
+    {
+        bufferImpl = std::make_unique<BufferImpl>(std::move(dataInterfaceMock));
+        testInitializationHeader.bmcInterfaceVersion = testBmcInterfaceVersion;
+        testInitializationHeader.queueSize = testQueueSize;
+        testInitializationHeader.ueRegionSize = testUeRegionSize;
+        testInitializationHeader.magicNumber = testMagicNumber;
+    }
+    ~BufferTest() override = default;
+
+    // CircularBufferHeader size is 0x30, ensure the test region is bigger
+    static constexpr size_t testRegionSize = 0x200;
+    static constexpr uint32_t testBmcInterfaceVersion = 123;
+    static constexpr uint16_t testQueueSize = 0x100;
+    static constexpr uint16_t testUeRegionSize = 0x50;
+    static constexpr std::array<uint32_t, 4> testMagicNumber = {
+        0x12345678, 0x22345678, 0x32345678, 0x42345678};
+    struct CircularBufferHeader testInitializationHeader
+    {};
+
+    std::unique_ptr<DataInterfaceMock> dataInterfaceMock;
+    DataInterfaceMock* dataInterfaceMockPtr;
+
+    std::unique_ptr<BufferImpl> bufferImpl;
+};
+
+TEST_F(BufferTest, BufferInitializeEraseFail)
+{
+    InSequence s;
+
+    EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
+        .WillOnce(Return(testRegionSize));
+    const std::vector<uint8_t> emptyArray(testRegionSize, 0);
+    // Return a smaller write than the intended testRegionSize to test the error
+    EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
+        .WillOnce(Return(testRegionSize - 1));
+    EXPECT_THROW(
+        try {
+            bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize,
+                                   testUeRegionSize, testMagicNumber);
+        } catch (const std::runtime_error& e) {
+            EXPECT_STREQ(e.what(), "Buffer initialization only erased '511'");
+            throw;
+        },
+        std::runtime_error);
+
+    EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
+        .WillOnce(Return(testRegionSize));
+    EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
+        .WillOnce(Return(testRegionSize));
+    // Return a smaller write than the intended initializationHeader to test the
+    // error
+    EXPECT_CALL(*dataInterfaceMockPtr, write(0, _)).WillOnce(Return(0));
+    EXPECT_THROW(
+        try {
+            bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize,
+                                   testUeRegionSize, testMagicNumber);
+        } catch (const std::runtime_error& e) {
+            EXPECT_STREQ(
+                e.what(),
+                "Buffer initialization buffer header write only wrote '0'");
+            throw;
+        },
+        std::runtime_error);
+}
+
+TEST_F(BufferTest, BufferInitializePass)
+{
+    InSequence s;
+    EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
+        .WillOnce(Return(testRegionSize));
+    const std::vector<uint8_t> emptyArray(testRegionSize, 0);
+    EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
+        .WillOnce(Return(testRegionSize));
+
+    uint8_t* testInitializationHeaderPtr =
+        reinterpret_cast<uint8_t*>(&testInitializationHeader);
+    size_t initializationHeaderSize = sizeof(testInitializationHeader);
+    EXPECT_CALL(*dataInterfaceMockPtr,
+                write(0, ElementsAreArray(testInitializationHeaderPtr,
+                                          initializationHeaderSize)))
+        .WillOnce(Return(initializationHeaderSize));
+    EXPECT_NO_THROW(bufferImpl->initialize(testBmcInterfaceVersion,
+                                           testQueueSize, testUeRegionSize,
+                                           testMagicNumber));
+}
+
+} // namespace
+} // namespace bios_bmc_smm_error_logger
diff --git a/test/include/data_interface_mock.hpp b/test/include/data_interface_mock.hpp
new file mode 100644
index 0000000..942103e
--- /dev/null
+++ b/test/include/data_interface_mock.hpp
@@ -0,0 +1,24 @@
+#pragma once
+#include "data_interface.hpp"
+
+#include <cstdint>
+#include <span>
+#include <vector>
+
+#include <gmock/gmock.h>
+
+namespace bios_bmc_smm_error_logger
+{
+
+class DataInterfaceMock : public DataInterface
+{
+  public:
+    MOCK_METHOD(std::vector<uint8_t>, read,
+                (const uint32_t offset, const uint32_t length), (override));
+    MOCK_METHOD(uint32_t, write,
+                (const uint32_t offset, const std::span<const uint8_t> bytes),
+                (override));
+    MOCK_METHOD(uint32_t, getMemoryRegionSize, (), (override));
+};
+
+} // namespace bios_bmc_smm_error_logger
diff --git a/test/meson.build b/test/meson.build
index 081fcd7..6f72d9f 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -17,13 +17,19 @@
   endif
 endif
 
+test_dep = declare_dependency(
+  include_directories: include_directories('include'),
+  dependencies: [bios_bmc_smm_error_logger_dep, gtest, gmock, rde_dep]
+)
+
 gtests = [
   'pci_handler',
   'rde_dictionary_manager',
+  'buffer',
 ]
 foreach t : gtests
   test(t, executable(t.underscorify(), t + '_test.cpp',
                      build_by_default: false,
                      implicit_include_directories: false,
-                     dependencies: [bios_bmc_smm_error_logger_dep, gtest, gmock, rde_dep]))
+                     dependencies: test_dep))
 endforeach