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