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/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