initial drop of phosphor-ipmi-blobs

This implements a majority of the OEM IPMI BLOBS protocol.  The only
piece missing from this is the timed expiration of sessions.

Change-Id: I82c9d17b625c94fc3340edcfabbbf1ffeb5ad7ac
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..29586bf
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,110 @@
+AM_CPPFLAGS = -I$(top_srcdir)/ \
+	$(GTEST_CFLAGS) \
+	$(GMOCK_CFLAGS)
+AM_CXXFLAGS = \
+	$(GTEST_MAIN_CFLAGS)
+AM_LDFLAGS = \
+	$(GMOCK_LIBS) \
+	$(GTEST_MAIN_LIBS) \
+	$(OESDK_TESTCASE_FLAGS)
+
+# Run all 'check' test programs
+check_PROGRAMS = \
+	ipmi_unittest \
+	ipmi_getcount_unittest \
+	ipmi_enumerate_unittest \
+	ipmi_open_unittest \
+	ipmi_close_unittest \
+	ipmi_delete_unittest \
+	ipmi_stat_unittest \
+	ipmi_sessionstat_unittest \
+	ipmi_commit_unittest \
+	ipmi_read_unittest \
+	ipmi_write_unittest \
+	ipmi_validate_unittest \
+	manager_unittest \
+	manager_getsession_unittest \
+	manager_open_unittest \
+	manager_stat_unittest \
+	manager_sessionstat_unittest \
+	manager_commit_unittest \
+	manager_close_unittest \
+	manager_delete_unittest \
+	manager_write_unittest \
+	manager_read_unittest \
+	process_unittest \
+	crc_unittest
+TESTS = $(check_PROGRAMS)
+
+ipmi_unittest_SOURCES = ipmi_unittest.cpp
+ipmi_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_getcount_unittest_SOURCES = ipmi_getcount_unittest.cpp
+ipmi_getcount_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_enumerate_unittest_SOURCES = ipmi_enumerate_unittest.cpp
+ipmi_enumerate_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_open_unittest_SOURCES = ipmi_open_unittest.cpp
+ipmi_open_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_close_unittest_SOURCES = ipmi_close_unittest.cpp
+ipmi_close_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_delete_unittest_SOURCES = ipmi_delete_unittest.cpp
+ipmi_delete_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_stat_unittest_SOURCES = ipmi_stat_unittest.cpp
+ipmi_stat_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_sessionstat_unittest_SOURCES = ipmi_sessionstat_unittest.cpp
+ipmi_sessionstat_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_commit_unittest_SOURCES = ipmi_commit_unittest.cpp
+ipmi_commit_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_read_unittest_SOURCES = ipmi_read_unittest.cpp
+ipmi_read_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_write_unittest_SOURCES = ipmi_write_unittest.cpp
+ipmi_write_unittest_LDADD = $(top_builddir)/ipmi.o
+
+ipmi_validate_unittest_SOURCES = ipmi_validate_unittest.cpp
+ipmi_validate_unittest_LDADD = $(top_builddir)/ipmi.o
+
+manager_unittest_SOURCES = manager_unittest.cpp
+manager_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_getsession_unittest_SOURCES = manager_getsession_unittest.cpp
+manager_getsession_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_open_unittest_SOURCES = manager_open_unittest.cpp
+manager_open_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_stat_unittest_SOURCES = manager_stat_unittest.cpp
+manager_stat_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_sessionstat_unittest_SOURCES = manager_sessionstat_unittest.cpp
+manager_sessionstat_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_commit_unittest_SOURCES = manager_commit_unittest.cpp
+manager_commit_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_close_unittest_SOURCES = manager_close_unittest.cpp
+manager_close_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_delete_unittest_SOURCES = manager_delete_unittest.cpp
+manager_delete_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_write_unittest_SOURCES = manager_write_unittest.cpp
+manager_write_unittest_LDADD = $(top_builddir)/manager.o
+
+manager_read_unittest_SOURCES = manager_read_unittest.cpp
+manager_read_unittest_LDADD = $(top_builddir)/manager.o
+
+process_unittest_SOURCES = process_unittest.cpp
+process_unittest_LDADD = $(top_builddir)/process.o $(top_builddir)/ipmi.o \
+	$(top_builddir)/crc.o
+
+crc_unittest_SOURCES = crc_unittest.cpp
+crc_unittest_LDADD = $(top_builddir)/crc.o
diff --git a/test/blob_mock.hpp b/test/blob_mock.hpp
new file mode 100644
index 0000000..6c21c65
--- /dev/null
+++ b/test/blob_mock.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "blobs.hpp"
+
+#include <gmock/gmock.h>
+
+namespace blobs
+{
+
+class BlobMock : public GenericBlobInterface
+{
+  public:
+    virtual ~BlobMock() = default;
+
+    MOCK_METHOD1(canHandleBlob, bool(const std::string&));
+    MOCK_METHOD0(getBlobIds, std::vector<std::string>());
+    MOCK_METHOD1(deleteBlob, bool(const std::string&));
+    MOCK_METHOD2(stat, bool(const std::string&, struct BlobMeta*));
+    MOCK_METHOD3(open, bool(uint16_t, uint16_t, const std::string&));
+    MOCK_METHOD3(read, std::vector<uint8_t>(uint16_t, uint32_t, uint32_t));
+    MOCK_METHOD3(write, bool(uint16_t, uint32_t, const std::vector<uint8_t>&));
+    MOCK_METHOD2(commit, bool(uint16_t, const std::vector<uint8_t>&));
+    MOCK_METHOD1(close, bool(uint16_t));
+    MOCK_METHOD2(stat, bool(uint16_t, struct BlobMeta*));
+    MOCK_METHOD1(expire, bool(uint16_t));
+};
+} // namespace blobs
diff --git a/test/crc_mock.hpp b/test/crc_mock.hpp
new file mode 100644
index 0000000..1562200
--- /dev/null
+++ b/test/crc_mock.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "crc.hpp"
+
+#include <gmock/gmock.h>
+
+namespace blobs
+{
+
+class CrcMock : public CrcInterface
+{
+  public:
+    virtual ~CrcMock() = default;
+
+    MOCK_METHOD0(clear, void());
+    MOCK_METHOD2(compute, void(const uint8_t*, uint32_t));
+    MOCK_CONST_METHOD0(get, uint16_t());
+};
+} // namespace blobs
diff --git a/test/crc_unittest.cpp b/test/crc_unittest.cpp
new file mode 100644
index 0000000..fb69cb4
--- /dev/null
+++ b/test/crc_unittest.cpp
@@ -0,0 +1,44 @@
+#include "crc.hpp"
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+TEST(Crc16Test, VerifyCrcValue)
+{
+    // Verify the crc16 is producing the value we expect.
+
+    // Origin: security/crypta/ipmi/portable/ipmi_utils_test.cc
+    struct CrcTestVector
+    {
+        std::string input;
+        uint16_t output;
+    };
+
+    std::string longString =
+        "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+        "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+        "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+        "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+        "AAAAAAAAAAAAAAAA";
+
+    std::vector<CrcTestVector> vectors({{"", 0x1D0F},
+                                        {"A", 0x9479},
+                                        {"123456789", 0xE5CC},
+                                        {longString, 0xE938}});
+
+    Crc16 crc;
+
+    for (const CrcTestVector& testVector : vectors)
+    {
+        crc.clear();
+        auto data = reinterpret_cast<const uint8_t*>(testVector.input.data());
+        crc.compute(data, testVector.input.size());
+        EXPECT_EQ(crc.get(), testVector.output);
+    }
+}
+} // namespace blobs
diff --git a/test/ipmi_close_unittest.cpp b/test/ipmi_close_unittest.cpp
new file mode 100644
index 0000000..e34f731
--- /dev/null
+++ b/test/ipmi_close_unittest.cpp
@@ -0,0 +1,66 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::Invoke;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::StrEq;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobCloseTest, ManagerRejectsCloseReturnsFailure)
+{
+    // The session manager returned failure to close, which we need to pass on.
+
+    ManagerMock mgr;
+    uint16_t sessionId = 0x54;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    struct BmcBlobCloseTx req;
+
+    req.cmd = BlobOEMCommands::bmcBlobClose;
+    req.crc = 0;
+    req.sessionId = sessionId;
+
+    dataLen = sizeof(req);
+
+    std::memcpy(request, &req, sizeof(req));
+
+    EXPECT_CALL(mgr, close(sessionId)).WillOnce(Return(false));
+    EXPECT_EQ(IPMI_CC_INVALID, closeBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobCloseTest, BlobClosedReturnsSuccess)
+{
+    // Verify that if all goes right, success is returned.
+
+    ManagerMock mgr;
+    uint16_t sessionId = 0x54;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    struct BmcBlobCloseTx req;
+
+    req.cmd = BlobOEMCommands::bmcBlobClose;
+    req.crc = 0;
+    req.sessionId = sessionId;
+
+    dataLen = sizeof(req);
+
+    std::memcpy(request, &req, sizeof(req));
+
+    EXPECT_CALL(mgr, close(sessionId)).WillOnce(Return(true));
+    EXPECT_EQ(IPMI_CC_OK, closeBlob(&mgr, request, reply, &dataLen));
+}
+} // namespace blobs
diff --git a/test/ipmi_commit_unittest.cpp b/test/ipmi_commit_unittest.cpp
new file mode 100644
index 0000000..1cc47a4
--- /dev/null
+++ b/test/ipmi_commit_unittest.cpp
@@ -0,0 +1,112 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::ElementsAreArray;
+using ::testing::Return;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobCommitTest, InvalidCommitDataLengthReturnsFailure)
+{
+    // The commit command supports an optional commit blob.  This test verifies
+    // we sanity check the length of that blob.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobCommitTx*>(request);
+
+    req->cmd = BlobOEMCommands::bmcBlobCommit;
+    req->crc = 0;
+    req->sessionId = 0x54;
+    req->commitDataLen =
+        1; // It's one byte, but that's more than the packet size.
+
+    dataLen = sizeof(struct BmcBlobCommitTx);
+
+    EXPECT_EQ(IPMI_CC_INVALID, commitBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobCommitTest, ValidCommitNoDataHandlerRejectsReturnsFailure)
+{
+    // The commit packet is valid and the manager's commit call returns failure.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobCommitTx*>(request);
+
+    req->cmd = BlobOEMCommands::bmcBlobCommit;
+    req->crc = 0;
+    req->sessionId = 0x54;
+    req->commitDataLen = 0;
+
+    dataLen = sizeof(struct BmcBlobCommitTx);
+
+    EXPECT_CALL(mgr, commit(req->sessionId, _)).WillOnce(Return(false));
+
+    EXPECT_EQ(IPMI_CC_INVALID, commitBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobCommitTest, ValidCommitNoDataHandlerAcceptsReturnsSuccess)
+{
+    // Commit called with no data and everything returns success.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobCommitTx*>(request);
+
+    req->cmd = BlobOEMCommands::bmcBlobCommit;
+    req->crc = 0;
+    req->sessionId = 0x54;
+    req->commitDataLen = 0;
+
+    dataLen = sizeof(struct BmcBlobCommitTx);
+
+    EXPECT_CALL(mgr, commit(req->sessionId, _)).WillOnce(Return(true));
+
+    EXPECT_EQ(IPMI_CC_OK, commitBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobCommitTest, ValidCommitWithDataHandlerAcceptsReturnsSuccess)
+{
+    // Commit called with extra data and everything returns success.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobCommitTx*>(request);
+
+    uint8_t expectedBlob[4] = {0x25, 0x33, 0x45, 0x67};
+
+    req->cmd = BlobOEMCommands::bmcBlobCommit;
+    req->crc = 0;
+    req->sessionId = 0x54;
+    req->commitDataLen = sizeof(expectedBlob);
+    std::memcpy(req->commitData, &expectedBlob[0], sizeof(expectedBlob));
+
+    dataLen = sizeof(struct BmcBlobCommitTx) + sizeof(expectedBlob);
+
+    EXPECT_CALL(mgr,
+                commit(req->sessionId,
+                       ElementsAreArray(expectedBlob, sizeof(expectedBlob))))
+        .WillOnce(Return(true));
+
+    EXPECT_EQ(IPMI_CC_OK, commitBlob(&mgr, request, reply, &dataLen));
+}
+} // namespace blobs
diff --git a/test/ipmi_delete_unittest.cpp b/test/ipmi_delete_unittest.cpp
new file mode 100644
index 0000000..25fb06b
--- /dev/null
+++ b/test/ipmi_delete_unittest.cpp
@@ -0,0 +1,89 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Return;
+using ::testing::StrEq;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobDeleteTest, InvalidRequestLengthReturnsFailure)
+{
+    // There is a minimum blobId length of one character, this test verifies
+    // we check that.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobDeleteTx*>(request);
+    std::string blobId = "abc";
+
+    req->cmd = BlobOEMCommands::bmcBlobDelete;
+    req->crc = 0;
+    // length() doesn't include the nul-terminator.
+    std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+    dataLen = sizeof(struct BmcBlobDeleteTx) + blobId.length();
+
+    EXPECT_EQ(IPMI_CC_INVALID, deleteBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobDeleteTest, RequestRejectedReturnsFailure)
+{
+    // The blobId is rejected for any reason.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobDeleteTx*>(request);
+    std::string blobId = "a";
+
+    req->cmd = BlobOEMCommands::bmcBlobDelete;
+    req->crc = 0;
+    // length() doesn't include the nul-terminator, request buff is initialized
+    // to 0s
+    std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+    dataLen = sizeof(struct BmcBlobDeleteTx) + blobId.length() + 1;
+
+    EXPECT_CALL(mgr, deleteBlob(StrEq(blobId))).WillOnce(Return(false));
+
+    EXPECT_EQ(IPMI_CC_INVALID, deleteBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobDeleteTest, BlobDeleteReturnsOk)
+{
+    // The boring case where the blobId is deleted.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobDeleteTx*>(request);
+    std::string blobId = "a";
+
+    req->cmd = BlobOEMCommands::bmcBlobDelete;
+    req->crc = 0;
+    // length() doesn't include the nul-terminator, request buff is initialized
+    // to 0s
+    std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+    dataLen = sizeof(struct BmcBlobDeleteTx) + blobId.length() + 1;
+
+    EXPECT_CALL(mgr, deleteBlob(StrEq(blobId))).WillOnce(Return(true));
+
+    EXPECT_EQ(IPMI_CC_OK, deleteBlob(&mgr, request, reply, &dataLen));
+}
+} // namespace blobs
diff --git a/test/ipmi_enumerate_unittest.cpp b/test/ipmi_enumerate_unittest.cpp
new file mode 100644
index 0000000..232fe7a
--- /dev/null
+++ b/test/ipmi_enumerate_unittest.cpp
@@ -0,0 +1,65 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::Return;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobEnumerateTest, VerifyIfRequestByIdInvalidReturnsFailure)
+{
+    // This tests to verify that if the index is invalid, it'll return failure.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    struct BmcBlobEnumerateTx req;
+    uint8_t* request = reinterpret_cast<uint8_t*>(&req);
+
+    req.cmd = BlobOEMCommands::bmcBlobEnumerate;
+    req.blobIdx = 0;
+    dataLen = sizeof(struct BmcBlobEnumerateTx);
+
+    EXPECT_CALL(mgr, getBlobId(req.blobIdx)).WillOnce(Return(""));
+
+    EXPECT_EQ(IPMI_CC_INVALID, enumerateBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobEnumerateTest, BoringRequestByIdAndReceive)
+{
+    // This tests that if an index into the blob_id cache is valid, the command
+    // will return the blobId.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    struct BmcBlobEnumerateTx req;
+    struct BmcBlobEnumerateRx* rep;
+    uint8_t* request = reinterpret_cast<uint8_t*>(&req);
+    std::string blobId = "/asdf";
+
+    req.cmd = BlobOEMCommands::bmcBlobEnumerate;
+    req.blobIdx = 0;
+    dataLen = sizeof(struct BmcBlobEnumerateTx);
+
+    EXPECT_CALL(mgr, getBlobId(req.blobIdx)).WillOnce(Return(blobId));
+
+    EXPECT_EQ(IPMI_CC_OK, enumerateBlob(&mgr, request, reply, &dataLen));
+
+    // We're expecting this as a response.
+    // blobId.length + 1 + sizeof(uint16_t);
+    EXPECT_EQ(blobId.length() + 1 + sizeof(uint16_t), dataLen);
+
+    rep = reinterpret_cast<struct BmcBlobEnumerateRx*>(reply);
+    EXPECT_EQ(0, std::memcmp(rep->blobId, blobId.c_str(), blobId.length() + 1));
+}
+} // namespace blobs
diff --git a/test/ipmi_getcount_unittest.cpp b/test/ipmi_getcount_unittest.cpp
new file mode 100644
index 0000000..c6d74e6
--- /dev/null
+++ b/test/ipmi_getcount_unittest.cpp
@@ -0,0 +1,72 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::Return;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+// the request here is only the subcommand byte and therefore there's no invalid
+// length check, etc to handle within the method.
+
+TEST(BlobCountTest, ReturnsZeroBlobs)
+{
+    // Calling BmcBlobGetCount if there are no handlers registered should just
+    // return that there are 0 blobs.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    struct BmcBlobCountTx req;
+    struct BmcBlobCountRx rep;
+    uint8_t* request = reinterpret_cast<uint8_t*>(&req);
+
+    req.cmd = BlobOEMCommands::bmcBlobGetCount;
+    dataLen = sizeof(req);
+
+    rep.crc = 0;
+    rep.blobCount = 0;
+
+    EXPECT_CALL(mgr, buildBlobList()).WillOnce(Return(0));
+
+    EXPECT_EQ(IPMI_CC_OK, getBlobCount(&mgr, request, reply, &dataLen));
+
+    EXPECT_EQ(sizeof(rep), dataLen);
+    EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep)));
+}
+
+TEST(BlobCountTest, ReturnsTwoBlobs)
+{
+    // Calling BmcBlobGetCount with one handler registered that knows of two
+    // blobs will return that it found two blobs.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    struct BmcBlobCountTx req;
+    struct BmcBlobCountRx rep;
+    uint8_t* request = reinterpret_cast<uint8_t*>(&req);
+
+    req.cmd = BlobOEMCommands::bmcBlobGetCount;
+    dataLen = sizeof(req);
+
+    rep.crc = 0;
+    rep.blobCount = 2;
+
+    EXPECT_CALL(mgr, buildBlobList()).WillOnce(Return(2));
+
+    EXPECT_EQ(IPMI_CC_OK, getBlobCount(&mgr, request, reply, &dataLen));
+
+    EXPECT_EQ(sizeof(rep), dataLen);
+    EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep)));
+}
+} // namespace blobs
diff --git a/test/ipmi_open_unittest.cpp b/test/ipmi_open_unittest.cpp
new file mode 100644
index 0000000..db2a34f
--- /dev/null
+++ b/test/ipmi_open_unittest.cpp
@@ -0,0 +1,108 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::StrEq;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobOpenTest, InvalidRequestLengthReturnsFailure)
+{
+    // There is a minimum blobId length of one character, this test verifies
+    // we check that.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobOpenTx*>(request);
+    std::string blobId = "abc";
+
+    req->cmd = BlobOEMCommands::bmcBlobOpen;
+    req->crc = 0;
+    req->flags = 0;
+    // length() doesn't include the nul-terminator.
+    std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+    dataLen = sizeof(struct BmcBlobOpenTx) + blobId.length();
+
+    EXPECT_EQ(IPMI_CC_INVALID, openBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobOpenTest, RequestRejectedReturnsFailure)
+{
+    // The blobId is rejected for any reason.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobOpenTx*>(request);
+    std::string blobId = "a";
+
+    req->cmd = BlobOEMCommands::bmcBlobOpen;
+    req->crc = 0;
+    req->flags = 0;
+    // length() doesn't include the nul-terminator, request buff is initialized
+    // to 0s
+    std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+    dataLen = sizeof(struct BmcBlobOpenTx) + blobId.length() + 1;
+
+    EXPECT_CALL(mgr, open(req->flags, StrEq(blobId), _))
+        .WillOnce(Return(false));
+
+    EXPECT_EQ(IPMI_CC_INVALID, openBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobOpenTest, BlobOpenReturnsOk)
+{
+    // The boring case where the blobId opens.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobOpenTx*>(request);
+    struct BmcBlobOpenRx rep;
+    std::string blobId = "a";
+
+    req->cmd = BlobOEMCommands::bmcBlobOpen;
+    req->crc = 0;
+    req->flags = 0;
+    // length() doesn't include the nul-terminator, request buff is initialized
+    // to 0s
+    std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+    dataLen = sizeof(struct BmcBlobOpenTx) + blobId.length() + 1;
+    uint16_t returnedSession = 0x54;
+
+    EXPECT_CALL(mgr, open(req->flags, StrEq(blobId), NotNull()))
+        .WillOnce(Invoke(
+            [&](uint16_t flags, const std::string& path, uint16_t* session) {
+                (*session) = returnedSession;
+                return true;
+            }));
+
+    EXPECT_EQ(IPMI_CC_OK, openBlob(&mgr, request, reply, &dataLen));
+
+    rep.crc = 0;
+    rep.sessionId = returnedSession;
+
+    EXPECT_EQ(sizeof(rep), dataLen);
+    EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep)));
+}
+} // namespace blobs
diff --git a/test/ipmi_read_unittest.cpp b/test/ipmi_read_unittest.cpp
new file mode 100644
index 0000000..b6dab55
--- /dev/null
+++ b/test/ipmi_read_unittest.cpp
@@ -0,0 +1,78 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::Return;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobReadTest, ManagerReturnsNoData)
+{
+    // Verify that if no data is returned the IPMI command reply has no
+    // payload.  The manager, in all failures, will just return 0 bytes.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobReadTx*>(request);
+
+    req->cmd = BlobOEMCommands::bmcBlobRead;
+    req->crc = 0;
+    req->sessionId = 0x54;
+    req->offset = 0x100;
+    req->requestedSize = 0x10;
+
+    dataLen = sizeof(struct BmcBlobReadTx);
+
+    std::vector<uint8_t> data;
+
+    EXPECT_CALL(mgr, read(req->sessionId, req->offset, req->requestedSize))
+        .WillOnce(Return(data));
+
+    EXPECT_EQ(IPMI_CC_OK, readBlob(&mgr, request, reply, &dataLen));
+    EXPECT_EQ(sizeof(struct BmcBlobReadRx), dataLen);
+}
+
+TEST(BlobReadTest, ManagerReturnsData)
+{
+    // Verify that if data is returned, it's placed in the expected location.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobReadTx*>(request);
+
+    req->cmd = BlobOEMCommands::bmcBlobRead;
+    req->crc = 0;
+    req->sessionId = 0x54;
+    req->offset = 0x100;
+    req->requestedSize = 0x10;
+
+    dataLen = sizeof(struct BmcBlobReadTx);
+
+    std::vector<uint8_t> data = {0x02, 0x03, 0x05, 0x06};
+
+    EXPECT_CALL(mgr, read(req->sessionId, req->offset, req->requestedSize))
+        .WillOnce(Return(data));
+
+    EXPECT_EQ(IPMI_CC_OK, readBlob(&mgr, request, reply, &dataLen));
+    EXPECT_EQ(sizeof(struct BmcBlobReadRx) + data.size(), dataLen);
+    EXPECT_EQ(0, std::memcmp(&reply[sizeof(struct BmcBlobReadRx)], data.data(),
+                             data.size()));
+}
+
+/* TODO(venture): We need a test that handles other checks such as if the size
+ * requested won't fit into a packet response.
+ */
+} // namespace blobs
diff --git a/test/ipmi_sessionstat_unittest.cpp b/test/ipmi_sessionstat_unittest.cpp
new file mode 100644
index 0000000..e1e1aad
--- /dev/null
+++ b/test/ipmi_sessionstat_unittest.cpp
@@ -0,0 +1,121 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Matcher;
+using ::testing::NotNull;
+using ::testing::Return;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobSessionStatTest, RequestRejectedByManagerReturnsFailure)
+{
+    // If the session ID is invalid, the request must fail.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobSessionStatTx*>(request);
+    req->cmd = BlobOEMCommands::bmcBlobSessionStat;
+    req->crc = 0;
+    req->sessionId = 0x54;
+
+    dataLen = sizeof(struct BmcBlobSessionStatTx);
+
+    EXPECT_CALL(mgr, stat(Matcher<uint16_t>(req->sessionId),
+                          Matcher<struct BlobMeta*>(_)))
+        .WillOnce(Return(false));
+
+    EXPECT_EQ(IPMI_CC_INVALID, sessionStatBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobSessionStatTest, RequestSucceedsNoMetadata)
+{
+    // Stat request succeeeds but there were no metadata bytes.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobSessionStatTx*>(request);
+    req->cmd = BlobOEMCommands::bmcBlobSessionStat;
+    req->crc = 0;
+    req->sessionId = 0x54;
+
+    dataLen = sizeof(struct BmcBlobSessionStatTx);
+
+    struct BmcBlobStatRx rep;
+    rep.crc = 0x00;
+    rep.blobState = 0x01;
+    rep.size = 0x100;
+    rep.metadataLen = 0x00;
+
+    EXPECT_CALL(mgr, stat(Matcher<uint16_t>(req->sessionId),
+                          Matcher<struct BlobMeta*>(NotNull())))
+        .WillOnce(Invoke([&](uint16_t session, struct BlobMeta* meta) {
+            meta->blobState = rep.blobState;
+            meta->size = rep.size;
+            return true;
+        }));
+
+    EXPECT_EQ(IPMI_CC_OK, sessionStatBlob(&mgr, request, reply, &dataLen));
+
+    EXPECT_EQ(sizeof(rep), dataLen);
+    EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep)));
+}
+
+TEST(BlobSessionStatTest, RequestSucceedsWithMetadata)
+{
+    // Stat request succeeds and there were metadata bytes.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobSessionStatTx*>(request);
+    req->cmd = BlobOEMCommands::bmcBlobSessionStat;
+    req->crc = 0;
+    req->sessionId = 0x54;
+
+    dataLen = sizeof(struct BmcBlobSessionStatTx);
+
+    struct BlobMeta lmeta;
+    lmeta.blobState = 0x01;
+    lmeta.size = 0x100;
+    lmeta.metadata.push_back(0x01);
+    lmeta.metadata.push_back(0x02);
+    lmeta.metadata.push_back(0x03);
+    lmeta.metadata.push_back(0x04);
+
+    struct BmcBlobStatRx rep;
+    rep.crc = 0x00;
+    rep.blobState = lmeta.blobState;
+    rep.size = lmeta.size;
+    rep.metadataLen = lmeta.metadata.size();
+
+    EXPECT_CALL(mgr, stat(Matcher<uint16_t>(req->sessionId),
+                          Matcher<struct BlobMeta*>(NotNull())))
+        .WillOnce(Invoke([&](uint16_t session, struct BlobMeta* meta) {
+            (*meta) = lmeta;
+            return true;
+        }));
+
+    EXPECT_EQ(IPMI_CC_OK, sessionStatBlob(&mgr, request, reply, &dataLen));
+
+    EXPECT_EQ(sizeof(rep) + lmeta.metadata.size(), dataLen);
+    EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep)));
+    EXPECT_EQ(0, std::memcmp(reply + sizeof(rep), lmeta.metadata.data(),
+                             lmeta.metadata.size()));
+}
+} // namespace blobs
diff --git a/test/ipmi_stat_unittest.cpp b/test/ipmi_stat_unittest.cpp
new file mode 100644
index 0000000..a6f1dfe
--- /dev/null
+++ b/test/ipmi_stat_unittest.cpp
@@ -0,0 +1,157 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Matcher;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::StrEq;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobStatTest, InvalidRequestLengthReturnsFailure)
+{
+    // There is a minimum blobId length of one character, this test verifies
+    // we check that.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobStatTx*>(request);
+    std::string blobId = "abc";
+
+    req->cmd = BlobOEMCommands::bmcBlobStat;
+    req->crc = 0;
+    // length() doesn't include the nul-terminator.
+    std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+    dataLen = sizeof(struct BmcBlobStatTx) + blobId.length();
+
+    EXPECT_EQ(IPMI_CC_INVALID, statBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobStatTest, RequestRejectedReturnsFailure)
+{
+    // The blobId is rejected for any reason.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobStatTx*>(request);
+    std::string blobId = "a";
+
+    req->cmd = BlobOEMCommands::bmcBlobStat;
+    req->crc = 0;
+    // length() doesn't include the nul-terminator, request buff is initialized
+    // to 0s
+    std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+    dataLen = sizeof(struct BmcBlobStatTx) + blobId.length() + 1;
+
+    EXPECT_CALL(mgr, stat(Matcher<const std::string&>(StrEq(blobId)),
+                          Matcher<struct BlobMeta*>(_)))
+        .WillOnce(Return(false));
+
+    EXPECT_EQ(IPMI_CC_INVALID, statBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobStatTest, RequestSucceedsNoMetadata)
+{
+    // Stat request succeeeds but there were no metadata bytes.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobStatTx*>(request);
+    std::string blobId = "a";
+
+    req->cmd = BlobOEMCommands::bmcBlobStat;
+    req->crc = 0;
+    // length() doesn't include the nul-terminator, request buff is initialized
+    // to 0s
+    std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+    dataLen = sizeof(struct BmcBlobStatTx) + blobId.length() + 1;
+
+    struct BmcBlobStatRx rep;
+    rep.crc = 0x00;
+    rep.blobState = 0x01;
+    rep.size = 0x100;
+    rep.metadataLen = 0x00;
+
+    EXPECT_CALL(mgr, stat(Matcher<const std::string&>(StrEq(blobId)),
+                          Matcher<struct BlobMeta*>(NotNull())))
+        .WillOnce(Invoke([&](const std::string& path, struct BlobMeta* meta) {
+            meta->blobState = rep.blobState;
+            meta->size = rep.size;
+            return true;
+        }));
+
+    EXPECT_EQ(IPMI_CC_OK, statBlob(&mgr, request, reply, &dataLen));
+
+    EXPECT_EQ(sizeof(rep), dataLen);
+    EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep)));
+}
+
+TEST(BlobStatTest, RequestSucceedsWithMetadata)
+{
+    // Stat request succeeds and there were metadata bytes.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobStatTx*>(request);
+    std::string blobId = "a";
+
+    req->cmd = BlobOEMCommands::bmcBlobStat;
+    req->crc = 0;
+    // length() doesn't include the nul-terminator, request buff is initialized
+    // to 0s
+    std::memcpy(req->blobId, blobId.c_str(), blobId.length());
+
+    dataLen = sizeof(struct BmcBlobStatTx) + blobId.length() + 1;
+
+    struct BlobMeta lmeta;
+    lmeta.blobState = 0x01;
+    lmeta.size = 0x100;
+    lmeta.metadata.push_back(0x01);
+    lmeta.metadata.push_back(0x02);
+    lmeta.metadata.push_back(0x03);
+    lmeta.metadata.push_back(0x04);
+
+    struct BmcBlobStatRx rep;
+    rep.crc = 0x00;
+    rep.blobState = lmeta.blobState;
+    rep.size = lmeta.size;
+    rep.metadataLen = lmeta.metadata.size();
+
+    EXPECT_CALL(mgr, stat(Matcher<const std::string&>(StrEq(blobId)),
+                          Matcher<struct BlobMeta*>(NotNull())))
+        .WillOnce(Invoke([&](const std::string& path, struct BlobMeta* meta) {
+            (*meta) = lmeta;
+            return true;
+        }));
+
+    EXPECT_EQ(IPMI_CC_OK, statBlob(&mgr, request, reply, &dataLen));
+
+    EXPECT_EQ(sizeof(rep) + lmeta.metadata.size(), dataLen);
+    EXPECT_EQ(0, std::memcmp(reply, &rep, sizeof(rep)));
+    EXPECT_EQ(0, std::memcmp(reply + sizeof(rep), lmeta.metadata.data(),
+                             lmeta.metadata.size()));
+}
+} // namespace blobs
diff --git a/test/ipmi_unittest.cpp b/test/ipmi_unittest.cpp
new file mode 100644
index 0000000..8f27ed7
--- /dev/null
+++ b/test/ipmi_unittest.cpp
@@ -0,0 +1,60 @@
+#include "ipmi.hpp"
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(StringInputTest, NullPointerInput)
+{
+    // The method should verify it did receive a non-null input pointer.
+
+    EXPECT_STREQ("", stringFromBuffer(NULL, 5).c_str());
+}
+
+TEST(StringInputTest, ZeroBytesInput)
+{
+    // Verify that if the input length is 0 that it'll return the empty string.
+
+    const char* request = "asdf";
+    EXPECT_STREQ("", stringFromBuffer(request, 0).c_str());
+}
+
+TEST(StringInputTest, NulTerminatorNotFound)
+{
+    // Verify that if there isn't a nul-terminator found in an otherwise valid
+    // string, it'll return the emptry string.
+
+    char request[MAX_IPMI_BUFFER];
+    std::memset(request, 'a', sizeof(request));
+    EXPECT_STREQ("", stringFromBuffer(request, sizeof(request)).c_str());
+}
+
+TEST(StringInputTest, TwoNulsFound)
+{
+    // Verify it makes you use the entire data region for the string.
+    char request[MAX_IPMI_BUFFER];
+    request[0] = 'a';
+    request[1] = 0;
+    std::memset(&request[2], 'b', sizeof(request) - 2);
+    request[MAX_IPMI_BUFFER - 1] = 0;
+
+    // This case has two strings, and the last character is a nul-terminator.
+    EXPECT_STREQ("", stringFromBuffer(request, sizeof(request)).c_str());
+}
+
+TEST(StringInputTest, NulTerminatorFound)
+{
+    // Verify that if it's provided a valid nul-terminated string, it'll
+    // return it.
+
+    const char* request = "asdf";
+    EXPECT_STREQ("asdf", stringFromBuffer(request, 5).c_str());
+}
+} // namespace blobs
diff --git a/test/ipmi_validate_unittest.cpp b/test/ipmi_validate_unittest.cpp
new file mode 100644
index 0000000..6bf4200
--- /dev/null
+++ b/test/ipmi_validate_unittest.cpp
@@ -0,0 +1,44 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+TEST(IpmiValidateTest, VerifyCommandMinimumLengths)
+{
+
+    struct TestCase
+    {
+        BlobOEMCommands cmd;
+        size_t len;
+        bool expect;
+    };
+
+    std::vector<TestCase> tests = {
+        {BlobOEMCommands::bmcBlobClose, sizeof(struct BmcBlobCloseTx) - 1,
+         false},
+        {BlobOEMCommands::bmcBlobCommit, sizeof(struct BmcBlobCommitTx) - 1,
+         false},
+        {BlobOEMCommands::bmcBlobDelete, sizeof(struct BmcBlobDeleteTx) + 1,
+         false},
+        {BlobOEMCommands::bmcBlobEnumerate,
+         sizeof(struct BmcBlobEnumerateTx) - 1, false},
+        {BlobOEMCommands::bmcBlobOpen, sizeof(struct BmcBlobOpenTx) + 1, false},
+        {BlobOEMCommands::bmcBlobRead, sizeof(struct BmcBlobReadTx) - 1, false},
+        {BlobOEMCommands::bmcBlobSessionStat,
+         sizeof(struct BmcBlobSessionStatTx) - 1, false},
+        {BlobOEMCommands::bmcBlobStat, sizeof(struct BmcBlobStatTx) + 1, false},
+        {BlobOEMCommands::bmcBlobWrite, sizeof(struct BmcBlobWriteTx), false},
+    };
+
+    for (const auto& test : tests)
+    {
+        bool result = validateRequestLength(test.cmd, test.len);
+        EXPECT_EQ(result, test.expect);
+    }
+}
+} // namespace blobs
diff --git a/test/ipmi_write_unittest.cpp b/test/ipmi_write_unittest.cpp
new file mode 100644
index 0000000..55a1e3b
--- /dev/null
+++ b/test/ipmi_write_unittest.cpp
@@ -0,0 +1,73 @@
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::ElementsAreArray;
+using ::testing::Return;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+TEST(BlobWriteTest, ManagerReturnsFailureReturnsFailure)
+{
+    // This verifies a failure from the manager is passed back.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobWriteTx*>(request);
+
+    req->cmd = BlobOEMCommands::bmcBlobWrite;
+    req->crc = 0;
+    req->sessionId = 0x54;
+    req->offset = 0x100;
+
+    uint8_t expectedBytes[2] = {0x66, 0x67};
+    std::memcpy(req->data, &expectedBytes[0], sizeof(expectedBytes));
+
+    dataLen = sizeof(struct BmcBlobWriteTx) + sizeof(expectedBytes);
+
+    EXPECT_CALL(mgr,
+                write(req->sessionId, req->offset,
+                      ElementsAreArray(expectedBytes, sizeof(expectedBytes))))
+        .WillOnce(Return(false));
+
+    EXPECT_EQ(IPMI_CC_INVALID, writeBlob(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobWriteTest, ManagerReturnsTrueWriteSucceeds)
+{
+    // The case where everything works.
+
+    ManagerMock mgr;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    auto req = reinterpret_cast<struct BmcBlobWriteTx*>(request);
+
+    req->cmd = BlobOEMCommands::bmcBlobWrite;
+    req->crc = 0;
+    req->sessionId = 0x54;
+    req->offset = 0x100;
+
+    uint8_t expectedBytes[2] = {0x66, 0x67};
+    std::memcpy(req->data, &expectedBytes[0], sizeof(expectedBytes));
+
+    dataLen = sizeof(struct BmcBlobWriteTx) + sizeof(expectedBytes);
+
+    EXPECT_CALL(mgr,
+                write(req->sessionId, req->offset,
+                      ElementsAreArray(expectedBytes, sizeof(expectedBytes))))
+        .WillOnce(Return(true));
+
+    EXPECT_EQ(IPMI_CC_OK, writeBlob(&mgr, request, reply, &dataLen));
+}
+} // namespace blobs
diff --git a/test/manager_close_unittest.cpp b/test/manager_close_unittest.cpp
new file mode 100644
index 0000000..47c9264
--- /dev/null
+++ b/test/manager_close_unittest.cpp
@@ -0,0 +1,66 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Return;
+
+TEST(ManagerCloseTest, CloseNoSessionReturnsFalse)
+{
+    // Calling Close on a session that doesn't exist should return false.
+
+    BlobManager mgr;
+    uint16_t sess = 1;
+
+    EXPECT_FALSE(mgr.close(sess));
+}
+
+TEST(ManagerCloseTest, CloseSessionFoundButHandlerReturnsFalse)
+{
+    // The handler was found but it returned failure.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t flags = OpenFlags::read, sess;
+    std::string path = "/asdf/asdf";
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+    EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+    EXPECT_CALL(*m1ptr, close(sess)).WillOnce(Return(false));
+
+    EXPECT_FALSE(mgr.close(sess));
+
+    // TODO(venture): The session wasn't closed, need to verify.  Could call
+    // public GetHandler method.
+}
+
+TEST(ManagerCloseTest, CloseSessionFoundAndHandlerReturnsSuccess)
+{
+    // The handler was found and returned success.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t flags = OpenFlags::read, sess;
+    std::string path = "/asdf/asdf";
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+    EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+    EXPECT_CALL(*m1ptr, close(sess)).WillOnce(Return(true));
+
+    EXPECT_TRUE(mgr.close(sess));
+}
+} // namespace blobs
diff --git a/test/manager_commit_unittest.cpp b/test/manager_commit_unittest.cpp
new file mode 100644
index 0000000..b1b3c8c
--- /dev/null
+++ b/test/manager_commit_unittest.cpp
@@ -0,0 +1,68 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Return;
+
+TEST(ManagerCommitTest, CommitNoSessionReturnsFalse)
+{
+    // Calling Commit on a session that doesn't exist should return false.
+
+    BlobManager mgr;
+    uint16_t sess = 1;
+    std::vector<uint8_t> data;
+
+    EXPECT_FALSE(mgr.commit(sess, data));
+}
+
+TEST(ManagerCommitTest, CommitSessionFoundButHandlerReturnsFalse)
+{
+    // The handler was found but it returned failure.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t flags = OpenFlags::write, sess;
+    std::string path = "/asdf/asdf";
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+    EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+    std::vector<uint8_t> data;
+    EXPECT_CALL(*m1ptr, commit(sess, data)).WillOnce(Return(false));
+
+    EXPECT_FALSE(mgr.commit(sess, data));
+}
+
+TEST(ManagerCommitTest, CommitSessionFoundAndHandlerReturnsSuccess)
+{
+    // The handler was found and returned success.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t flags = OpenFlags::write, sess;
+    std::string path = "/asdf/asdf";
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+    EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+    std::vector<uint8_t> data;
+    EXPECT_CALL(*m1ptr, commit(sess, data)).WillOnce(Return(true));
+
+    EXPECT_TRUE(mgr.commit(sess, data));
+}
+} // namespace blobs
diff --git a/test/manager_delete_unittest.cpp b/test/manager_delete_unittest.cpp
new file mode 100644
index 0000000..9ad3afd
--- /dev/null
+++ b/test/manager_delete_unittest.cpp
@@ -0,0 +1,87 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Return;
+
+TEST(ManagerDeleteTest, FileIsOpenReturnsFailure)
+{
+    // The blob manager maintains a naive list of open files and will
+    // return failure if you try to delete an open file.
+
+    // Open the file.
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t flags = OpenFlags::read, sess;
+    std::string path = "/asdf/asdf";
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillRepeatedly(Return(true));
+    EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+    EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+    // Try to delete the file.
+    EXPECT_FALSE(mgr.deleteBlob(path));
+}
+
+TEST(ManagerDeleteTest, FileHasNoHandler)
+{
+    // The blob manager cannot find any handler.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    std::string path = "/asdf/asdf";
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(false));
+
+    // Try to delete the file.
+    EXPECT_FALSE(mgr.deleteBlob(path));
+}
+
+TEST(ManagerDeleteTest, FileIsNotOpenButHandlerDeleteFails)
+{
+    // The Blob manager finds the handler but the handler returns failure
+    // on delete.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    std::string path = "/asdf/asdf";
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, deleteBlob(path)).WillOnce(Return(false));
+
+    // Try to delete the file.
+    EXPECT_FALSE(mgr.deleteBlob(path));
+}
+
+TEST(ManagerDeleteTest, FileIsNotOpenAndHandlerSucceeds)
+{
+    // The Blob manager finds the handler and the handler returns success.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    std::string path = "/asdf/asdf";
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, deleteBlob(path)).WillOnce(Return(true));
+
+    // Try to delete the file.
+    EXPECT_TRUE(mgr.deleteBlob(path));
+}
+} // namespace blobs
diff --git a/test/manager_getsession_unittest.cpp b/test/manager_getsession_unittest.cpp
new file mode 100644
index 0000000..e66729a
--- /dev/null
+++ b/test/manager_getsession_unittest.cpp
@@ -0,0 +1,24 @@
+#include "manager.hpp"
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+TEST(ManagerGetSessionTest, NextSessionReturned)
+{
+    // This test verifies the next session ID is returned.
+    BlobManager mgr;
+
+    uint16_t first, second;
+    EXPECT_TRUE(mgr.getSession(&first));
+    EXPECT_TRUE(mgr.getSession(&second));
+    EXPECT_FALSE(first == second);
+}
+
+TEST(ManagerGetSessionTest, SessionsCheckedAgainstList)
+{
+    // TODO(venture): Need a test that verifies the session ids are checked
+    // against open sessions.
+}
+} // namespace blobs
diff --git a/test/manager_mock.hpp b/test/manager_mock.hpp
new file mode 100644
index 0000000..41979ac
--- /dev/null
+++ b/test/manager_mock.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "blobs.hpp"
+#include "manager.hpp"
+
+#include <memory>
+#include <string>
+
+#include <gmock/gmock.h>
+
+namespace blobs
+{
+
+class ManagerMock : public ManagerInterface
+{
+  public:
+    virtual ~ManagerMock() = default;
+
+    MOCK_METHOD1(registerHandler, bool(std::unique_ptr<GenericBlobInterface>));
+    MOCK_METHOD0(buildBlobList, uint32_t());
+    MOCK_METHOD1(getBlobId, std::string(uint32_t));
+    MOCK_METHOD3(open, bool(uint16_t, const std::string&, uint16_t*));
+    MOCK_METHOD2(stat, bool(const std::string&, struct BlobMeta*));
+    MOCK_METHOD2(stat, bool(uint16_t, struct BlobMeta*));
+    MOCK_METHOD2(commit, bool(uint16_t, const std::vector<uint8_t>&));
+    MOCK_METHOD1(close, bool(uint16_t));
+    MOCK_METHOD3(read, std::vector<uint8_t>(uint16_t, uint32_t, uint32_t));
+    MOCK_METHOD3(write, bool(uint16_t, uint32_t, const std::vector<uint8_t>&));
+    MOCK_METHOD1(deleteBlob, bool(const std::string&));
+};
+} // namespace blobs
diff --git a/test/manager_open_unittest.cpp b/test/manager_open_unittest.cpp
new file mode 100644
index 0000000..309d3f6
--- /dev/null
+++ b/test/manager_open_unittest.cpp
@@ -0,0 +1,85 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Return;
+
+TEST(ManagerOpenTest, OpenButNoHandler)
+{
+    // No handler claims to be able to open the file.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t flags = OpenFlags::read, sess;
+    std::string path = "/asdf/asdf";
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(false));
+    EXPECT_FALSE(mgr.open(flags, path, &sess));
+}
+
+TEST(ManagerOpenTest, OpenButHandlerFailsOpen)
+{
+    // The handler is found but Open fails.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t flags = OpenFlags::read, sess;
+    std::string path = "/asdf/asdf";
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(false));
+    EXPECT_FALSE(mgr.open(flags, path, &sess));
+}
+
+TEST(ManagerOpenTest, OpenFailsMustSupplyAtLeastReadOrWriteFlag)
+{
+    // One must supply either read or write in the flags for the session to
+    // open.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t flags = 0, sess;
+    std::string path = "/asdf/asdf";
+
+    /* It checks if someone can handle the blob before it checks the flags. */
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+
+    EXPECT_FALSE(mgr.open(flags, path, &sess));
+}
+
+TEST(ManagerOpenTest, OpenSucceeds)
+{
+    // The handler is found and Open succeeds.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t flags = OpenFlags::read, sess;
+    std::string path = "/asdf/asdf";
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+    EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+    // TODO(venture): Need a way to verify the session is associated with it,
+    // maybe just call Read() or SessionStat()
+}
+} // namespace blobs
diff --git a/test/manager_read_unittest.cpp b/test/manager_read_unittest.cpp
new file mode 100644
index 0000000..1d40f5d
--- /dev/null
+++ b/test/manager_read_unittest.cpp
@@ -0,0 +1,78 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Return;
+
+TEST(ManagerReadTest, ReadNoSessionReturnsFalse)
+{
+    // Calling Read on a session that doesn't exist should return false.
+
+    BlobManager mgr;
+    uint16_t sess = 1;
+    uint32_t ofs = 0x54;
+    uint32_t requested = 0x100;
+
+    std::vector<uint8_t> result = mgr.read(sess, ofs, requested);
+    EXPECT_EQ(0, result.size());
+}
+
+TEST(ManagerReadTest, ReadFromWriteOnlyFails)
+{
+    // The session manager will not route a Read call to a blob if the session
+    // was opened as write-only.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t sess = 1;
+    uint32_t ofs = 0x54;
+    uint32_t requested = 0x100;
+    uint16_t flags = OpenFlags::write;
+    std::string path = "/asdf/asdf";
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+    EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+    std::vector<uint8_t> result = mgr.read(sess, ofs, requested);
+    EXPECT_EQ(0, result.size());
+}
+
+TEST(ManagerReadTest, ReadFromHandlerReturnsData)
+{
+    // There is no logic in this as it's just as a pass-thru command, however
+    // we want to verify this behavior doesn't change.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t sess = 1;
+    uint32_t ofs = 0x54;
+    uint32_t requested = 0x100;
+    uint16_t flags = OpenFlags::read;
+    std::string path = "/asdf/asdf";
+    std::vector<uint8_t> data = {0x12, 0x14, 0x15, 0x16};
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+    EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+    EXPECT_CALL(*m1ptr, read(sess, ofs, requested)).WillOnce(Return(data));
+
+    std::vector<uint8_t> result = mgr.read(sess, ofs, requested);
+    EXPECT_EQ(data.size(), result.size());
+    EXPECT_EQ(result, data);
+}
+} // namespace blobs
diff --git a/test/manager_sessionstat_unittest.cpp b/test/manager_sessionstat_unittest.cpp
new file mode 100644
index 0000000..6ae27d3
--- /dev/null
+++ b/test/manager_sessionstat_unittest.cpp
@@ -0,0 +1,66 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Return;
+
+TEST(ManagerSessionStatTest, StatNoSessionReturnsFalse)
+{
+    // Calling Stat on a session that doesn't exist should return false.
+
+    BlobManager mgr;
+    struct BlobMeta meta;
+    uint16_t sess = 1;
+
+    EXPECT_FALSE(mgr.stat(sess, &meta));
+}
+
+TEST(ManagerSessionStatTest, StatSessionFoundButHandlerReturnsFalse)
+{
+    // The handler was found but it returned failure.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t flags = OpenFlags::read, sess;
+    std::string path = "/asdf/asdf";
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+    EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+    struct BlobMeta meta;
+    EXPECT_CALL(*m1ptr, stat(sess, &meta)).WillOnce(Return(false));
+
+    EXPECT_FALSE(mgr.stat(sess, &meta));
+}
+
+TEST(ManagerSessionStatTest, StatSessionFoundAndHandlerReturnsSuccess)
+{
+    // The handler was found and returned success.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t flags = OpenFlags::read, sess;
+    std::string path = "/asdf/asdf";
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+    EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+    struct BlobMeta meta;
+    EXPECT_CALL(*m1ptr, stat(sess, &meta)).WillOnce(Return(true));
+
+    EXPECT_TRUE(mgr.stat(sess, &meta));
+}
+} // namespace blobs
diff --git a/test/manager_stat_unittest.cpp b/test/manager_stat_unittest.cpp
new file mode 100644
index 0000000..a13a66d
--- /dev/null
+++ b/test/manager_stat_unittest.cpp
@@ -0,0 +1,60 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::Return;
+
+TEST(ManagerStatTest, StatNoHandler)
+{
+    // There is no handler for this path.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    struct BlobMeta meta;
+    std::string path = "/asdf/asdf";
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(false));
+
+    EXPECT_FALSE(mgr.stat(path, &meta));
+}
+
+TEST(ManagerStatTest, StatHandlerFoundButFails)
+{
+    // There is a handler for this path but Stat fails.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    struct BlobMeta meta;
+    std::string path = "/asdf/asdf";
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, stat(path, &meta)).WillOnce(Return(false));
+
+    EXPECT_FALSE(mgr.stat(path, &meta));
+}
+
+TEST(ManagerStatTest, StatHandlerFoundAndSucceeds)
+{
+    // There is a handler and Stat succeeds.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    struct BlobMeta meta;
+    std::string path = "/asdf/asdf";
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, stat(path, &meta)).WillOnce(Return(true));
+
+    EXPECT_TRUE(mgr.stat(path, &meta));
+}
+} // namespace blobs
diff --git a/test/manager_unittest.cpp b/test/manager_unittest.cpp
new file mode 100644
index 0000000..7d4d49e
--- /dev/null
+++ b/test/manager_unittest.cpp
@@ -0,0 +1,172 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::Return;
+
+TEST(BlobsTest, RegisterNullPointerFails)
+{
+    // The only invalid pointer really is a null one.
+
+    BlobManager mgr;
+    EXPECT_FALSE(mgr.registerHandler(nullptr));
+}
+
+TEST(BlobsTest, RegisterNonNullPointerPasses)
+{
+    // Test that the valid pointer is boringly registered.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+}
+
+TEST(BlobsTest, GetCountNoBlobsRegistered)
+{
+    // Request the Blob Count when there are no blobs.
+
+    BlobManager mgr;
+    EXPECT_EQ(0, mgr.buildBlobList());
+}
+
+TEST(BlobsTest, GetCountBlobRegisteredReturnsOne)
+{
+    // Request the blob count and verify the list is of length one.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    std::vector<std::string> v = {"item"};
+
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    // We expect it to ask for the list.
+    EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v));
+
+    EXPECT_EQ(1, mgr.buildBlobList());
+}
+
+TEST(BlobsTest, GetCountBlobsRegisteredEachReturnsOne)
+{
+    // Request the blob count and verify the list is of length two.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    std::unique_ptr<BlobMock> m2 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    auto m2ptr = m2.get();
+    std::vector<std::string> v1, v2;
+
+    v1.push_back("asdf");
+    v2.push_back("ghjk");
+
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+    EXPECT_TRUE(mgr.registerHandler(std::move(m2)));
+
+    // We expect it to ask for the list.
+    EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v1));
+    EXPECT_CALL(*m2ptr, getBlobIds()).WillOnce(Return(v2));
+
+    EXPECT_EQ(2, mgr.buildBlobList());
+}
+
+TEST(BlobsTest, EnumerateBlobZerothEntry)
+{
+    // Validate that you can read back the 0th blobId.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    std::unique_ptr<BlobMock> m2 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    auto m2ptr = m2.get();
+    std::vector<std::string> v1, v2;
+
+    v1.push_back("asdf");
+    v2.push_back("ghjk");
+
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+    EXPECT_TRUE(mgr.registerHandler(std::move(m2)));
+
+    // We expect it to ask for the list.
+    EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v1));
+    EXPECT_CALL(*m2ptr, getBlobIds()).WillOnce(Return(v2));
+
+    EXPECT_EQ(2, mgr.buildBlobList());
+
+    std::string result = mgr.getBlobId(0);
+    // The exact order the blobIds is returned is not guaranteed to never
+    // change.
+    EXPECT_TRUE("asdf" == result || "ghjk" == result);
+}
+
+TEST(BlobsTest, EnumerateBlobFirstEntry)
+{
+    // Validate you can read back the two real entries.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    std::unique_ptr<BlobMock> m2 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    auto m2ptr = m2.get();
+    std::vector<std::string> v1, v2;
+
+    v1.push_back("asdf");
+    v2.push_back("ghjk");
+
+    // Presently the list of blobs is read and appended in a specific order,
+    // but I don't want to rely on that.
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+    EXPECT_TRUE(mgr.registerHandler(std::move(m2)));
+
+    // We expect it to ask for the list.
+    EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v1));
+    EXPECT_CALL(*m2ptr, getBlobIds()).WillOnce(Return(v2));
+
+    EXPECT_EQ(2, mgr.buildBlobList());
+
+    // Try to grab the two blobIds and verify they're in the list.
+    std::vector<std::string> results;
+    results.push_back(mgr.getBlobId(0));
+    results.push_back(mgr.getBlobId(1));
+    EXPECT_EQ(2, results.size());
+    EXPECT_TRUE(std::find(results.begin(), results.end(), "asdf") !=
+                results.end());
+    EXPECT_TRUE(std::find(results.begin(), results.end(), "ghjk") !=
+                results.end());
+}
+
+TEST(BlobTest, EnumerateBlobInvalidEntry)
+{
+    // Validate trying to read an invalid entry fails expectedly.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    std::unique_ptr<BlobMock> m2 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    auto m2ptr = m2.get();
+    std::vector<std::string> v1, v2;
+
+    v1.push_back("asdf");
+    v2.push_back("ghjk");
+
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+    EXPECT_TRUE(mgr.registerHandler(std::move(m2)));
+
+    // We expect it to ask for the list.
+    EXPECT_CALL(*m1ptr, getBlobIds()).WillOnce(Return(v1));
+    EXPECT_CALL(*m2ptr, getBlobIds()).WillOnce(Return(v2));
+
+    EXPECT_EQ(2, mgr.buildBlobList());
+
+    // Grabs the third entry which isn't valid.
+    EXPECT_STREQ("", mgr.getBlobId(2).c_str());
+}
+} // namespace blobs
diff --git a/test/manager_write_unittest.cpp b/test/manager_write_unittest.cpp
new file mode 100644
index 0000000..33c6d5a
--- /dev/null
+++ b/test/manager_write_unittest.cpp
@@ -0,0 +1,90 @@
+#include "blob_mock.hpp"
+#include "manager.hpp"
+
+#include <gtest/gtest.h>
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace blobs
+{
+
+TEST(ManagerWriteTest, WriteNoSessionReturnsFalse)
+{
+    // Calling Write on a session that doesn't exist should return false.
+
+    BlobManager mgr;
+    uint16_t sess = 1;
+    uint32_t ofs = 0x54;
+    std::vector<uint8_t> data = {0x11, 0x22};
+
+    EXPECT_FALSE(mgr.write(sess, ofs, data));
+}
+
+TEST(ManagerWriteTest, WriteSessionFoundButHandlerReturnsFalse)
+{
+    // The handler was found but it returned failure.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t flags = OpenFlags::write, sess;
+    std::string path = "/asdf/asdf";
+    uint32_t ofs = 0x54;
+    std::vector<uint8_t> data = {0x11, 0x22};
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+    EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+    EXPECT_CALL(*m1ptr, write(sess, ofs, data)).WillOnce(Return(false));
+
+    EXPECT_FALSE(mgr.write(sess, ofs, data));
+}
+
+TEST(ManagerWriteTest, WriteFailsBecauseFileOpenedReadOnly)
+{
+    // The manager will not route a write call to a file opened read-only.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t flags = OpenFlags::read, sess;
+    std::string path = "/asdf/asdf";
+    uint32_t ofs = 0x54;
+    std::vector<uint8_t> data = {0x11, 0x22};
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+    EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+    EXPECT_FALSE(mgr.write(sess, ofs, data));
+}
+
+TEST(ManagerWriteTest, WriteSessionFoundAndHandlerReturnsSuccess)
+{
+    // The handler was found and returned success.
+
+    BlobManager mgr;
+    std::unique_ptr<BlobMock> m1 = std::make_unique<BlobMock>();
+    auto m1ptr = m1.get();
+    EXPECT_TRUE(mgr.registerHandler(std::move(m1)));
+
+    uint16_t flags = OpenFlags::write, sess;
+    std::string path = "/asdf/asdf";
+    uint32_t ofs = 0x54;
+    std::vector<uint8_t> data = {0x11, 0x22};
+
+    EXPECT_CALL(*m1ptr, canHandleBlob(path)).WillOnce(Return(true));
+    EXPECT_CALL(*m1ptr, open(_, flags, path)).WillOnce(Return(true));
+    EXPECT_TRUE(mgr.open(flags, path, &sess));
+
+    EXPECT_CALL(*m1ptr, write(sess, ofs, data)).WillOnce(Return(true));
+
+    EXPECT_TRUE(mgr.write(sess, ofs, data));
+}
+} // namespace blobs
diff --git a/test/process_unittest.cpp b/test/process_unittest.cpp
new file mode 100644
index 0000000..2ffb023
--- /dev/null
+++ b/test/process_unittest.cpp
@@ -0,0 +1,290 @@
+#include "crc.hpp"
+#include "crc_mock.hpp"
+#include "ipmi.hpp"
+#include "manager_mock.hpp"
+#include "process.hpp"
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrictMock;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+
+namespace
+{
+
+void EqualFunctions(IpmiBlobHandler lhs, IpmiBlobHandler rhs)
+{
+    EXPECT_FALSE(lhs == nullptr);
+    EXPECT_FALSE(rhs == nullptr);
+
+    ipmi_ret_t (*const* lPtr)(ManagerInterface*, const uint8_t*, uint8_t*,
+                              size_t*) =
+        lhs.target<ipmi_ret_t (*)(ManagerInterface*, const uint8_t*, uint8_t*,
+                                  size_t*)>();
+
+    ipmi_ret_t (*const* rPtr)(ManagerInterface*, const uint8_t*, uint8_t*,
+                              size_t*) =
+        rhs.target<ipmi_ret_t (*)(ManagerInterface*, const uint8_t*, uint8_t*,
+                                  size_t*)>();
+
+    EXPECT_TRUE(lPtr);
+    EXPECT_TRUE(rPtr);
+    EXPECT_EQ(*lPtr, *rPtr);
+    return;
+}
+
+} // namespace
+
+TEST(ValidateBlobCommandTest, InvalidCommandReturnsFailure)
+{
+    // Verify we handle an invalid command.
+
+    StrictMock<CrcMock> crc;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+    request[0] = 0xff;         // There is no command 0xff.
+    dataLen = sizeof(uint8_t); // There is no payload for CRC.
+
+    EXPECT_EQ(nullptr, validateBlobCommand(&crc, request, reply, &dataLen));
+}
+
+TEST(ValidateBlobCommandTest, ValidCommandWithoutPayload)
+{
+    // Verify we handle a valid command that doesn't have a payload.
+
+    StrictMock<CrcMock> crc;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+    request[0] = BlobOEMCommands::bmcBlobGetCount;
+    dataLen = sizeof(uint8_t); // There is no payload for CRC.
+
+    IpmiBlobHandler res = validateBlobCommand(&crc, request, reply, &dataLen);
+    EXPECT_FALSE(res == nullptr);
+    EqualFunctions(getBlobCount, res);
+}
+
+TEST(ValidateBlobCommandTest, WithPayloadMinimumLengthIs3VerifyChecks)
+{
+    // Verify that if there's a payload, it's at least one command byte and
+    // two bytes for the crc16 and then one data byte.
+
+    StrictMock<CrcMock> crc;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+    request[0] = BlobOEMCommands::bmcBlobGetCount;
+    dataLen = sizeof(uint8_t) + sizeof(uint16_t);
+    // There is a payload, but there are insufficient bytes.
+
+    EXPECT_EQ(nullptr, validateBlobCommand(&crc, request, reply, &dataLen));
+}
+
+TEST(ValidateBlobCommandTest, WithPayloadAndInvalidCrc)
+{
+    // Verify that the CRC is checked, and failure is reported.
+
+    StrictMock<CrcMock> crc;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+    auto req = reinterpret_cast<struct BmcBlobWriteTx*>(request);
+    req->cmd = BlobOEMCommands::bmcBlobWrite;
+    req->crc = 0x34;
+    req->sessionId = 0x54;
+    req->offset = 0x100;
+
+    uint8_t expectedBytes[2] = {0x66, 0x67};
+    std::memcpy(req->data, &expectedBytes[0], sizeof(expectedBytes));
+
+    dataLen = sizeof(struct BmcBlobWriteTx) + sizeof(expectedBytes);
+
+    // skip over cmd and crc.
+    size_t expectedLen = dataLen - 3;
+
+    EXPECT_CALL(crc, clear());
+    EXPECT_CALL(crc, compute(_, expectedLen))
+        .WillOnce(Invoke([&](const uint8_t* bytes, uint32_t length) {
+            EXPECT_EQ(0, std::memcmp(&request[3], bytes, length));
+        }));
+    EXPECT_CALL(crc, get()).WillOnce(Return(0x1234));
+
+    EXPECT_EQ(nullptr, validateBlobCommand(&crc, request, reply, &dataLen));
+}
+
+TEST(ValidateBlobCommandTest, WithPayloadAndValidCrc)
+{
+    // Verify the CRC is checked and if it matches, return the handler.
+
+    StrictMock<CrcMock> crc;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+    auto req = reinterpret_cast<struct BmcBlobWriteTx*>(request);
+    req->cmd = BlobOEMCommands::bmcBlobWrite;
+    req->crc = 0x3412;
+    req->sessionId = 0x54;
+    req->offset = 0x100;
+
+    uint8_t expectedBytes[2] = {0x66, 0x67};
+    std::memcpy(req->data, &expectedBytes[0], sizeof(expectedBytes));
+
+    dataLen = sizeof(struct BmcBlobWriteTx) + sizeof(expectedBytes);
+
+    // skip over cmd and crc.
+    size_t expectedLen = dataLen - 3;
+
+    EXPECT_CALL(crc, clear());
+    EXPECT_CALL(crc, compute(_, expectedLen))
+        .WillOnce(Invoke([&](const uint8_t* bytes, uint32_t length) {
+            EXPECT_EQ(0, std::memcmp(&request[3], bytes, length));
+        }));
+    EXPECT_CALL(crc, get()).WillOnce(Return(0x3412));
+
+    IpmiBlobHandler res = validateBlobCommand(&crc, request, reply, &dataLen);
+    EXPECT_FALSE(res == nullptr);
+    EqualFunctions(writeBlob, res);
+}
+
+TEST(ValidateBlobCommandTest, InputIntegrationTest)
+{
+    // Given a request buffer generated by the host-side utility, verify it is
+    // properly routed.
+
+    Crc16 crc;
+    size_t dataLen;
+    uint8_t request[] = {0x02, 0x88, 0x21, 0x03, 0x00, 0x2f, 0x64, 0x65, 0x76,
+                         0x2f, 0x68, 0x61, 0x76, 0x65, 0x6e, 0x2f, 0x63, 0x6f,
+                         0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x73,
+                         0x73, 0x74, 0x68, 0x72, 0x75, 0x00};
+
+    // The above request to open a file for reading & writing named:
+    // "/dev/haven/command_passthru"
+
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+    dataLen = sizeof(request);
+
+    IpmiBlobHandler res = validateBlobCommand(&crc, request, reply, &dataLen);
+    EXPECT_FALSE(res == nullptr);
+    EqualFunctions(openBlob, res);
+}
+
+TEST(ProcessBlobCommandTest, CommandReturnsNotOk)
+{
+    // Verify that if the IPMI command handler returns not OK that this is
+    // noticed and returned.
+
+    StrictMock<CrcMock> crc;
+    StrictMock<ManagerMock> manager;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+    IpmiBlobHandler h = [](ManagerInterface* mgr, const uint8_t* reqBuf,
+                           uint8_t* replyCmdBuf,
+                           size_t* dataLen) { return IPMI_CC_INVALID; };
+
+    dataLen = sizeof(request);
+
+    EXPECT_EQ(IPMI_CC_INVALID,
+              processBlobCommand(h, &manager, &crc, request, reply, &dataLen));
+}
+
+TEST(ProcessBlobCommandTest, CommandReturnsOkWithNoPayload)
+{
+    // Verify that if the IPMI command handler returns OK but without a payload
+    // it doesn't try to compute a CRC.
+
+    StrictMock<CrcMock> crc;
+    StrictMock<ManagerMock> manager;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+    IpmiBlobHandler h = [](ManagerInterface* mgr, const uint8_t* reqBuf,
+                           uint8_t* replyCmdBuf, size_t* dataLen) {
+        (*dataLen) = 0;
+        return IPMI_CC_OK;
+    };
+
+    dataLen = sizeof(request);
+
+    EXPECT_EQ(IPMI_CC_OK,
+              processBlobCommand(h, &manager, &crc, request, reply, &dataLen));
+}
+
+TEST(ProcessBlobCommandTest, CommandReturnsOkWithInvalidPayloadLength)
+{
+    // There is a minimum payload length of 3 bytes, this command returns a
+    // payload of 2 bytes.
+
+    StrictMock<CrcMock> crc;
+    StrictMock<ManagerMock> manager;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+    IpmiBlobHandler h = [](ManagerInterface* mgr, const uint8_t* reqBuf,
+                           uint8_t* replyCmdBuf, size_t* dataLen) {
+        (*dataLen) = sizeof(uint16_t);
+        return IPMI_CC_OK;
+    };
+
+    dataLen = sizeof(request);
+
+    EXPECT_EQ(IPMI_CC_INVALID,
+              processBlobCommand(h, &manager, &crc, request, reply, &dataLen));
+}
+
+TEST(ProcessBlobCommandTest, CommandReturnsOkWithValidPayloadLength)
+{
+    // There is a minimum payload length of 3 bytes, this command returns a
+    // payload of 3 bytes and the crc code is called to process the payload.
+
+    StrictMock<CrcMock> crc;
+    StrictMock<ManagerMock> manager;
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+    uint32_t payloadLen = sizeof(uint16_t) + sizeof(uint8_t);
+
+    IpmiBlobHandler h = [payloadLen](ManagerInterface* mgr,
+                                     const uint8_t* reqBuf,
+                                     uint8_t* replyCmdBuf, size_t* dataLen) {
+        (*dataLen) = payloadLen;
+        replyCmdBuf[2] = 0x56;
+        return IPMI_CC_OK;
+    };
+
+    dataLen = sizeof(request);
+
+    EXPECT_CALL(crc, clear());
+    EXPECT_CALL(crc, compute(_, payloadLen));
+    EXPECT_CALL(crc, get()).WillOnce(Return(0x3412));
+
+    EXPECT_EQ(IPMI_CC_OK,
+              processBlobCommand(h, &manager, &crc, request, reply, &dataLen));
+    EXPECT_EQ(dataLen, payloadLen);
+
+    uint8_t expectedBytes[3] = {0x12, 0x34, 0x56};
+    EXPECT_EQ(0, std::memcmp(expectedBytes, reply, sizeof(expectedBytes)));
+}
+} // namespace blobs