new command: BmcBlobWriteMeta

Implement new command BmcBlobWriteMeta.

Change-Id: I2e148f4bde4ef5d24db7e30bb02bdde024d9166a
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/blobs-ipmid/blobs.hpp b/blobs-ipmid/blobs.hpp
index b6672b7..0014fd4 100644
--- a/blobs-ipmid/blobs.hpp
+++ b/blobs-ipmid/blobs.hpp
@@ -106,6 +106,17 @@
                        const std::vector<uint8_t>& data) = 0;
 
     /**
+     * Attempt to write metadata to a blob.
+     *
+     * @param[in] session - the session id.
+     * @param[in] offset - offset into the blob.
+     * @param[in] data - the data to write.
+     * @return bool - was able to write.
+     */
+    virtual bool writeMeta(uint16_t session, uint32_t offset,
+                           const std::vector<uint8_t>& data) = 0;
+
+    /**
      * Attempt to commit to a blob.
      *
      * @param[in] session - the session id.
diff --git a/blobs-ipmid/manager.hpp b/blobs-ipmid/manager.hpp
index 884271c..d7282e6 100644
--- a/blobs-ipmid/manager.hpp
+++ b/blobs-ipmid/manager.hpp
@@ -56,6 +56,9 @@
                        const std::vector<uint8_t>& data) = 0;
 
     virtual bool deleteBlob(const std::string& path) = 0;
+
+    virtual bool writeMeta(uint16_t session, uint32_t offset,
+                           const std::vector<uint8_t>& data) = 0;
 };
 
 /**
@@ -192,6 +195,17 @@
     bool deleteBlob(const std::string& path) override;
 
     /**
+     * Attempt to write Metadata to a blob.
+     *
+     * @param[in] session - the session for this command.
+     * @param[in] offset - the offset into the blob to write.
+     * @param[in] data - the bytes to write to the blob.
+     * @return bool - true if the write succeeded.
+     */
+    bool writeMeta(uint16_t session, uint32_t offset,
+                   const std::vector<uint8_t>& data) override;
+
+    /**
      * Attempts to return a valid unique session id.
      *
      * @param[in,out] - pointer to the session.
diff --git a/blobs-ipmid/test/blob_mock.hpp b/blobs-ipmid/test/blob_mock.hpp
index 3396048..b70d3d1 100644
--- a/blobs-ipmid/test/blob_mock.hpp
+++ b/blobs-ipmid/test/blob_mock.hpp
@@ -19,6 +19,8 @@
     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_METHOD3(writeMeta,
+                 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*));
diff --git a/blobs-ipmid/test/manager_mock.hpp b/blobs-ipmid/test/manager_mock.hpp
index a4714f8..00eeb6b 100644
--- a/blobs-ipmid/test/manager_mock.hpp
+++ b/blobs-ipmid/test/manager_mock.hpp
@@ -26,6 +26,8 @@
     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&));
+    MOCK_METHOD3(writeMeta,
+                 bool(uint16_t, uint32_t, const std::vector<uint8_t>&));
 };
 
 /*
diff --git a/example/example.cpp b/example/example.cpp
index c17d71d..929e4d1 100644
--- a/example/example.cpp
+++ b/example/example.cpp
@@ -117,6 +117,13 @@
     return true;
 }
 
+bool ExampleBlobHandler::writeMeta(uint16_t session, uint32_t offset,
+                                   const std::vector<uint8_t>& data)
+{
+    /* Not supported. */
+    return false;
+}
+
 bool ExampleBlobHandler::commit(uint16_t session,
                                 const std::vector<uint8_t>& data)
 {
diff --git a/example/example.hpp b/example/example.hpp
index 85f5b17..a85c275 100644
--- a/example/example.hpp
+++ b/example/example.hpp
@@ -58,6 +58,8 @@
                               uint32_t requestedSize) override;
     bool write(uint16_t session, uint32_t offset,
                const std::vector<uint8_t>& data) override;
+    bool writeMeta(uint16_t session, uint32_t offset,
+                   const std::vector<uint8_t>& data) override;
     bool commit(uint16_t session, const std::vector<uint8_t>& data) override;
     bool close(uint16_t session) override;
     bool stat(uint16_t session, struct BlobMeta* meta) override;
diff --git a/ipmi.cpp b/ipmi.cpp
index 6942b11..fb5f120 100644
--- a/ipmi.cpp
+++ b/ipmi.cpp
@@ -43,6 +43,8 @@
         {BlobOEMCommands::bmcBlobRead, sizeof(struct BmcBlobReadTx)},
         {BlobOEMCommands::bmcBlobWrite,
          sizeof(struct BmcBlobWriteTx) + sizeof(uint8_t)},
+        {BlobOEMCommands::bmcBlobWriteMeta,
+         sizeof(struct BmcBlobWriteMetaTx) + sizeof(uint8_t)},
     };
 
     auto results = minimumLengths.find(command);
@@ -321,4 +323,29 @@
     return IPMI_CC_OK;
 }
 
+ipmi_ret_t writeMeta(ManagerInterface* mgr, const uint8_t* reqBuf,
+                     uint8_t* replyCmdBuf, size_t* dataLen)
+{
+    size_t requestLen = (*dataLen);
+    struct BmcBlobWriteMetaTx request;
+
+    /* Copy over the request. */
+    std::memcpy(&request, reqBuf, sizeof(request));
+
+    /* Determine number of bytes of metadata to write. */
+    uint32_t size = requestLen - sizeof(request);
+
+    /* Nothing really else to validate, we just copy those bytes. */
+    std::vector<uint8_t> data(size);
+    std::memcpy(data.data(), &reqBuf[sizeof(request)], size);
+
+    /* Attempt to write the bytes. */
+    if (!mgr->writeMeta(request.sessionId, request.offset, data))
+    {
+        return IPMI_CC_INVALID;
+    }
+
+    return IPMI_CC_OK;
+}
+
 } // namespace blobs
diff --git a/ipmi.hpp b/ipmi.hpp
index 3b99e6e..a09d347 100644
--- a/ipmi.hpp
+++ b/ipmi.hpp
@@ -20,6 +20,7 @@
     bmcBlobDelete = 7,
     bmcBlobStat = 8,
     bmcBlobSessionStat = 9,
+    bmcBlobWriteMeta = 10,
 };
 
 /* Used by bmcBlobGetCount */
@@ -140,6 +141,16 @@
     uint8_t data[];
 } __attribute__((packed));
 
+/* Used by bmcBlobWriteMeta */
+struct BmcBlobWriteMetaTx
+{
+    uint8_t cmd; /* bmcBlobWriteMeta */
+    uint16_t crc;
+    uint16_t sessionId; /* Returned from BmcBlobOpen. */
+    uint32_t offset;    /* The byte sequence start, 0-based. */
+    uint8_t data[];
+} __attribute__((packed));
+
 /**
  * Validate the minimum request length if there is one.
  *
@@ -223,4 +234,11 @@
  */
 ipmi_ret_t writeBlob(ManagerInterface* mgr, const uint8_t* reqBuf,
                      uint8_t* replyCmdBuf, size_t* dataLen);
+
+/**
+ * Attempt to write metadata to the blob.
+ */
+ipmi_ret_t writeMeta(ManagerInterface* mgr, const uint8_t* reqBuf,
+                     uint8_t* replyCmdBuf, size_t* dataLen);
+
 } // namespace blobs
diff --git a/manager.cpp b/manager.cpp
index 6a94c1a..32fdd74 100644
--- a/manager.cpp
+++ b/manager.cpp
@@ -316,6 +316,21 @@
     return handler->deleteBlob(path);
 }
 
+bool BlobManager::writeMeta(uint16_t session, uint32_t offset,
+                            const std::vector<uint8_t>& data)
+{
+    SessionInfo* info = getSessionInfo(session);
+
+    /* No session found. */
+    if (!info)
+    {
+        return false;
+    }
+
+    /* Try writing metadata to it. */
+    return info->handler->writeMeta(session, offset, data);
+}
+
 bool BlobManager::getSession(uint16_t* sess)
 {
     uint16_t tries = 0;
diff --git a/process.cpp b/process.cpp
index b3939eb..245da69 100644
--- a/process.cpp
+++ b/process.cpp
@@ -44,6 +44,7 @@
     {BlobOEMCommands::bmcBlobDelete, deleteBlob},
     {BlobOEMCommands::bmcBlobStat, statBlob},
     {BlobOEMCommands::bmcBlobSessionStat, sessionStatBlob},
+    {BlobOEMCommands::bmcBlobWriteMeta, writeMeta},
 };
 
 IpmiBlobHandler validateBlobCommand(CrcInterface* crc, const uint8_t* reqBuf,
diff --git a/test/Makefile.am b/test/Makefile.am
index 29586bf..5e0cdb0 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -21,6 +21,7 @@
 	ipmi_commit_unittest \
 	ipmi_read_unittest \
 	ipmi_write_unittest \
+	ipmi_writemeta_unittest \
 	ipmi_validate_unittest \
 	manager_unittest \
 	manager_getsession_unittest \
@@ -32,6 +33,7 @@
 	manager_delete_unittest \
 	manager_write_unittest \
 	manager_read_unittest \
+	manager_writemeta_unittest \
 	process_unittest \
 	crc_unittest
 TESTS = $(check_PROGRAMS)
@@ -69,6 +71,9 @@
 ipmi_write_unittest_SOURCES = ipmi_write_unittest.cpp
 ipmi_write_unittest_LDADD = $(top_builddir)/ipmi.o
 
+ipmi_writemeta_unittest_SOURCES = ipmi_writemeta_unittest.cpp
+ipmi_writemeta_unittest_LDADD = $(top_builddir)/ipmi.o
+
 ipmi_validate_unittest_SOURCES = ipmi_validate_unittest.cpp
 ipmi_validate_unittest_LDADD = $(top_builddir)/ipmi.o
 
@@ -102,6 +107,9 @@
 manager_read_unittest_SOURCES = manager_read_unittest.cpp
 manager_read_unittest_LDADD = $(top_builddir)/manager.o
 
+manager_writemeta_unittest_SOURCES = manager_writemeta_unittest.cpp
+manager_writemeta_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
diff --git a/test/ipmi_writemeta_unittest.cpp b/test/ipmi_writemeta_unittest.cpp
new file mode 100644
index 0000000..2dfbe59
--- /dev/null
+++ b/test/ipmi_writemeta_unittest.cpp
@@ -0,0 +1,72 @@
+#include "ipmi.hpp"
+
+#include <blobs-ipmid/test/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(BlobWriteMetaTest, 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 BmcBlobWriteMetaTx*>(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 BmcBlobWriteMetaTx) + sizeof(expectedBytes);
+
+    EXPECT_CALL(
+        mgr, writeMeta(req->sessionId, req->offset,
+                       ElementsAreArray(expectedBytes, sizeof(expectedBytes))))
+        .WillOnce(Return(false));
+
+    EXPECT_EQ(IPMI_CC_INVALID, writeMeta(&mgr, request, reply, &dataLen));
+}
+
+TEST(BlobWriteMetaTest, 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 BmcBlobWriteMetaTx*>(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 BmcBlobWriteMetaTx) + sizeof(expectedBytes);
+
+    EXPECT_CALL(
+        mgr, writeMeta(req->sessionId, req->offset,
+                       ElementsAreArray(expectedBytes, sizeof(expectedBytes))))
+        .WillOnce(Return(true));
+
+    EXPECT_EQ(IPMI_CC_OK, writeMeta(&mgr, request, reply, &dataLen));
+}
+} // namespace blobs
diff --git a/test/manager_writemeta_unittest.cpp b/test/manager_writemeta_unittest.cpp
new file mode 100644
index 0000000..e83c904
--- /dev/null
+++ b/test/manager_writemeta_unittest.cpp
@@ -0,0 +1,92 @@
+#include <blobs-ipmid/manager.hpp>
+#include <blobs-ipmid/test/blob_mock.hpp>
+
+#include <gtest/gtest.h>
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace blobs
+{
+
+TEST(ManagerWriteMetaTest, WriteMetaNoSessionReturnsFalse)
+{
+    // Calling WriteMeta 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.writeMeta(sess, ofs, data));
+}
+
+TEST(ManagerWriteMetaTest, WriteMetaSessionFoundButHandlerReturnsFalse)
+{
+    // 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, writeMeta(sess, ofs, data)).WillOnce(Return(false));
+
+    EXPECT_FALSE(mgr.writeMeta(sess, ofs, data));
+}
+
+TEST(ManagerWriteMetaTest, WriteMetaSucceedsEvenIfFileOpenedReadOnly)
+{
+    // The manager will not route a WriteMeta 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_CALL(*m1ptr, writeMeta(sess, ofs, data)).WillOnce(Return(true));
+
+    EXPECT_TRUE(mgr.writeMeta(sess, ofs, data));
+}
+
+TEST(ManagerWriteMetaTest, WriteMetaMetaSessionFoundAndHandlerReturnsSuccess)
+{
+    // 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, writeMeta(sess, ofs, data)).WillOnce(Return(true));
+
+    EXPECT_TRUE(mgr.writeMeta(sess, ofs, data));
+}
+} // namespace blobs