Add a IPMI blob handler for SMBIOS tables

smbios-mdr has dependencies on intel-ipmi-oem which makes BIOS sending
SMBIOS tables to BMC through VGA shared memory. For platforms without
intel-ipmi-oem, implement a IPMI blob hanler so that BIOS can send
SMBIOS tables through IPMI blob interfaces.

Test:
Unit tests for the IPMI blob handler.
Manual test that transfers SMBIOS tables to BMC through IPMI blob
interfaces on a platform host.

Signed-off-by: Jie Yang <jjy@google.com>
Change-Id: I9bc1ae7e9bfaa793e47e38fa19049f0f69355189
diff --git a/src/smbios-ipmi-blobs/test/handler_open_unittest.cpp b/src/smbios-ipmi-blobs/test/handler_open_unittest.cpp
new file mode 100644
index 0000000..22223cc
--- /dev/null
+++ b/src/smbios-ipmi-blobs/test/handler_open_unittest.cpp
@@ -0,0 +1,32 @@
+#include "handler_unittest.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+
+#include <cstdint>
+
+namespace blobs
+{
+
+class SmbiosBlobHandlerOpenTest : public SmbiosBlobHandlerTest
+{};
+
+TEST_F(SmbiosBlobHandlerOpenTest, OpenWithBadFlagsFails)
+{
+    // SMBIOS blob handler disables read flag
+
+    EXPECT_FALSE(handler.open(session, blobs::OpenFlags::read, expectedBlobId));
+}
+
+TEST_F(SmbiosBlobHandlerOpenTest, OpenEverythingSucceeds)
+{
+    EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+}
+
+TEST_F(SmbiosBlobHandlerOpenTest, CannotOpenSameSessionTwice)
+{
+    EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+    EXPECT_FALSE(
+        handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+}
+
+} // namespace blobs
diff --git a/src/smbios-ipmi-blobs/test/handler_readwrite_unittest.cpp b/src/smbios-ipmi-blobs/test/handler_readwrite_unittest.cpp
new file mode 100644
index 0000000..5cd3abc
--- /dev/null
+++ b/src/smbios-ipmi-blobs/test/handler_readwrite_unittest.cpp
@@ -0,0 +1,102 @@
+#include "handler_unittest.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+
+#include <cstdint>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using ::testing::IsEmpty;
+
+namespace blobs
+{
+
+class SmbiosBlobHandlerReadWriteTest : public SmbiosBlobHandlerTest
+{};
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, InvalidSessionWriteIsRejected)
+{
+    // Verify the handler checks for a valid session.
+
+    std::vector<uint8_t> data = {0x1, 0x2};
+    EXPECT_FALSE(handler.write(session, 0, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, NoWriteFlagRejected)
+{
+    // Verify the handler checks the write flag;
+
+    EXPECT_TRUE(handler.open(session, 0, expectedBlobId));
+
+    std::vector<uint8_t> data = {0x1, 0x2};
+    EXPECT_FALSE(handler.write(session, 0, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, WritingTooMuchByOneByteFails)
+{
+    int bytes = handlerMaxBufferSize + 1;
+    std::vector<uint8_t> data(bytes, 0x11);
+
+    EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+    EXPECT_FALSE(handler.write(session, 0, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, WritingTooMuchByOffsetOfOne)
+{
+    std::vector<uint8_t> data(handlerMaxBufferSize, 0x11);
+
+    EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+    EXPECT_FALSE(handler.write(session, 1, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, WritingOneByteBeyondEndFromOffsetFails)
+{
+    std::vector<uint8_t> data = {0x01, 0x02};
+
+    EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+    EXPECT_FALSE(handler.write(session, handlerMaxBufferSize - 1, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, WritingOneByteAtOffsetBeyondEndFails)
+{
+    std::vector<uint8_t> data = {0x01};
+
+    EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+    EXPECT_FALSE(handler.write(session, handlerMaxBufferSize, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, WritingFullBufferAtOffsetZeroSucceeds)
+{
+    std::vector<uint8_t> data(handlerMaxBufferSize, 0x01);
+
+    EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+    EXPECT_TRUE(handler.write(session, 0, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, WritingOneByteToTheLastOffsetSucceeds)
+{
+    std::vector<uint8_t> data = {0x01};
+
+    EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+    EXPECT_TRUE(handler.write(session, handlerMaxBufferSize - 1, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, ReadAlwaysReturnsEmpty)
+{
+    const uint32_t testOffset = 0;
+    const std::vector<uint8_t> testData = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+    EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+    EXPECT_TRUE(handler.write(session, testOffset, testData));
+
+    EXPECT_THAT(handler.read(session, testOffset, testData.size()), IsEmpty());
+
+    for (size_t i = 0; i < testData.size(); ++i)
+    {
+        EXPECT_THAT(handler.read(session, i, 1), IsEmpty());
+    }
+}
+
+} // namespace blobs
diff --git a/src/smbios-ipmi-blobs/test/handler_statclose_unittest.cpp b/src/smbios-ipmi-blobs/test/handler_statclose_unittest.cpp
new file mode 100644
index 0000000..b8d32cd
--- /dev/null
+++ b/src/smbios-ipmi-blobs/test/handler_statclose_unittest.cpp
@@ -0,0 +1,78 @@
+#include "handler_unittest.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+
+#include <cstdint>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+class SmbiosBlobHandlerStatCloseTest : public SmbiosBlobHandlerTest
+{
+  protected:
+    blobs::BlobMeta meta;
+
+    // Initialize expected_meta_ with empty members
+    blobs::BlobMeta expected_meta_session = {};
+    blobs::BlobMeta expected_meta_path = {};
+};
+
+TEST_F(SmbiosBlobHandlerStatCloseTest, InvalidSessionStatIsRejected)
+{
+    EXPECT_FALSE(handler.stat(session, &meta));
+}
+
+TEST_F(SmbiosBlobHandlerStatCloseTest, SessionStatAlwaysInitialReadAndWrite)
+{
+    // Verify the session stat returns the information for a session.
+
+    EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+
+    EXPECT_TRUE(handler.stat(session, &meta));
+    expected_meta_session.blobState = blobs::StateFlags::open_write;
+    EXPECT_EQ(meta, expected_meta_session);
+
+    EXPECT_TRUE(handler.stat(expectedBlobId, &meta));
+    expected_meta_path.blobState = blobs::StateFlags::open_write;
+    EXPECT_EQ(meta, expected_meta_path);
+}
+
+TEST_F(SmbiosBlobHandlerStatCloseTest, AfterWriteMetadataLengthMatches)
+{
+    // Verify that after writes, the length returned matches.
+
+    std::vector<uint8_t> data = {0x01};
+    EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+    EXPECT_TRUE(handler.write(session, handlerMaxBufferSize - 1, data));
+
+    // We wrote one byte to the last index, making the length the buffer size.
+    EXPECT_TRUE(handler.stat(session, &meta));
+    expected_meta_session.size = handlerMaxBufferSize;
+    expected_meta_session.blobState = blobs::StateFlags::open_write;
+    EXPECT_EQ(meta, expected_meta_session);
+
+    EXPECT_TRUE(handler.stat(expectedBlobId, &meta));
+    expected_meta_path.size = handlerMaxBufferSize;
+    expected_meta_path.blobState = blobs::StateFlags::open_write;
+    EXPECT_EQ(meta, expected_meta_path);
+}
+
+TEST_F(SmbiosBlobHandlerStatCloseTest, CloseWithInvalidSessionFails)
+{
+    // Verify you cannot close an invalid session.
+
+    EXPECT_FALSE(handler.close(session));
+}
+
+TEST_F(SmbiosBlobHandlerStatCloseTest, CloseWithValidSessionSuccess)
+{
+    // Verify you can close a valid session.
+
+    EXPECT_TRUE(handler.open(session, 0, expectedBlobId));
+
+    EXPECT_TRUE(handler.close(session));
+}
+} // namespace blobs
diff --git a/src/smbios-ipmi-blobs/test/handler_unittest.cpp b/src/smbios-ipmi-blobs/test/handler_unittest.cpp
new file mode 100644
index 0000000..9395e23
--- /dev/null
+++ b/src/smbios-ipmi-blobs/test/handler_unittest.cpp
@@ -0,0 +1,38 @@
+#include "handler_unittest.hpp"
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+class SmbiosBlobHandlerBasicTest : public SmbiosBlobHandlerTest
+{};
+
+TEST_F(SmbiosBlobHandlerBasicTest, CanHandleBlobChecksNameInvalid)
+{
+    // Verify canHandleBlob checks and returns false on an invalid name.
+
+    EXPECT_FALSE(handler.canHandleBlob("asdf"));
+    EXPECT_FALSE(handler.canHandleBlob("smbios"));
+    EXPECT_FALSE(handler.canHandleBlob("/smbios0"));
+    EXPECT_FALSE(handler.canHandleBlob("/smbios/0"));
+}
+
+TEST_F(SmbiosBlobHandlerBasicTest, CanHandleBlobChecksNameVaild)
+{
+    // Verify canHandleBlob checks and returns true on the valid name.
+
+    EXPECT_TRUE(handler.canHandleBlob(expectedBlobId));
+}
+
+TEST_F(SmbiosBlobHandlerBasicTest, GetblobIdsAsExpected)
+{
+    // Verify getBlobIds returns the expected blob list.
+
+    EXPECT_EQ(handler.getBlobIds(), expectedBlobIdList);
+}
+
+} // namespace blobs
diff --git a/src/smbios-ipmi-blobs/test/handler_unittest.hpp b/src/smbios-ipmi-blobs/test/handler_unittest.hpp
new file mode 100644
index 0000000..0b02a79
--- /dev/null
+++ b/src/smbios-ipmi-blobs/test/handler_unittest.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "handler.hpp"
+
+#include <ipmid/api.h>
+#include <systemd/sd-bus.h>
+
+#include <gtest/gtest.h>
+
+sd_bus* ipmid_get_sd_bus_connection()
+{
+    return nullptr;
+}
+
+namespace blobs
+{
+
+class SmbiosBlobHandlerTest : public ::testing::Test
+{
+  protected:
+    SmbiosBlobHandlerTest() = default;
+
+    SmbiosBlobHandler handler;
+
+    const uint16_t session = 0;
+    const std::string expectedBlobId = "/smbios";
+    const std::vector<std::string> expectedBlobIdList = {"/smbios"};
+    const uint32_t handlerMaxBufferSize = 64 * 1024;
+};
+} // namespace blobs