Add class interface BinaryStore for storage abstraction

Represent each storage location as a separate class instance. Add an
interface BinaryStore for this purpose, and add basic unit tests
using a mock.

Signed-off-by: Kun Yi <kunyi@google.com>
Change-Id: I67a140280985db567a4f31d0fe5439105b0a47f9
diff --git a/binarystore.hpp b/binarystore.hpp
new file mode 100644
index 0000000..068db5a
--- /dev/null
+++ b/binarystore.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+using std::size_t;
+using std::uint16_t;
+using std::uint32_t;
+using std::uint64_t;
+using std::uint8_t;
+
+namespace binstore
+{
+
+/**
+ * @class BinaryStoreInterface is an abstraction for a storage location.
+ *        Each instance would be uniquely identified by a baseId string.
+ */
+class BinaryStoreInterface
+{
+  public:
+    virtual ~BinaryStoreInterface() = default;
+
+    virtual std::string getBaseBlobId() const = 0;
+    virtual std::vector<std::string> getBlobIds() const = 0;
+    virtual bool canHandleBlob(const std::string& blobId) const = 0;
+    virtual bool openOrCreateBlob(const std::string& blobId) = 0;
+    virtual std::vector<uint8_t> read(uint32_t offset,
+                                      uint32_t requestedSize) = 0;
+    virtual bool write(uint32_t offset, const std::vector<uint8_t>& data) = 0;
+    virtual bool commit() = 0;
+    virtual bool close() = 0;
+    virtual bool stat() = 0;
+};
+
+} // namespace binstore
diff --git a/binarystore_mock.hpp b/binarystore_mock.hpp
new file mode 100644
index 0000000..3ea1201
--- /dev/null
+++ b/binarystore_mock.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "binarystore.hpp"
+
+#include <gmock/gmock.h>
+
+namespace binstore
+{
+
+class MockBinaryStore : public BinaryStoreInterface
+{
+  public:
+    MOCK_CONST_METHOD0(getBaseBlobId, std::string());
+    MOCK_CONST_METHOD1(canHandleBlob, bool(const std::string&));
+    MOCK_CONST_METHOD0(getBlobIds, std::vector<std::string>());
+    MOCK_METHOD1(openOrCreateBlob, bool(const std::string&));
+    MOCK_METHOD2(read, std::vector<uint8_t>(uint32_t, uint32_t));
+    MOCK_METHOD2(write, bool(uint32_t, const std::vector<uint8_t>&));
+    MOCK_METHOD0(commit, bool());
+    MOCK_METHOD0(close, bool());
+    MOCK_METHOD0(stat, bool());
+};
+
+} // namespace binstore
diff --git a/handler.cpp b/handler.cpp
index 967a8c2..4e6e6ea 100644
--- a/handler.cpp
+++ b/handler.cpp
@@ -1,18 +1,35 @@
 #include "handler.hpp"
 
+#include <algorithm>
+
 namespace blobs
 {
 
+void BinaryStoreBlobHandler::addNewBinaryStore(
+    std::unique_ptr<binstore::BinaryStoreInterface> store)
+{
+    // TODO: this is a very rough measure to test the mock interface for now.
+    stores_[store->getBaseBlobId()] = std::move(store);
+}
+
 bool BinaryStoreBlobHandler::canHandleBlob(const std::string& path)
 {
-    // TODO: implement
-    return false;
+    return std::any_of(stores_.begin(), stores_.end(),
+                       [&](const auto& baseStorePair) {
+                           return baseStorePair.second->canHandleBlob(path);
+                       });
 }
 
 std::vector<std::string> BinaryStoreBlobHandler::getBlobIds()
 {
-    // TODO: implement
     std::vector<std::string> result;
+
+    for (const auto& baseStorePair : stores_)
+    {
+        const auto& ids = baseStorePair.second->getBlobIds();
+        result.insert(result.end(), ids.begin(), ids.end());
+    }
+
     return result;
 }
 
@@ -32,6 +49,18 @@
 bool BinaryStoreBlobHandler::open(uint16_t session, uint16_t flags,
                                   const std::string& path)
 {
+    if (!canHandleBlob(path))
+    {
+        return false;
+    }
+
+    auto found = sessions_.find(session);
+    if (found != sessions_.end())
+    {
+        /* This session is already active */
+        return false;
+    }
+
     // TODO: implement
     return false;
 }
diff --git a/handler.hpp b/handler.hpp
index dcc17b4..61ded36 100644
--- a/handler.hpp
+++ b/handler.hpp
@@ -1,8 +1,13 @@
 #pragma once
 
+#include "binarystore.hpp"
+
 #include <blobs-ipmid/blobs.hpp>
 #include <cstdint>
+#include <map>
+#include <memory>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 using std::size_t;
@@ -40,6 +45,24 @@
     bool close(uint16_t session) override;
     bool stat(uint16_t session, struct BlobMeta* meta) override;
     bool expire(uint16_t session) override;
+
+    /**
+     * Registers a binarystore in the main handler. Once called, handler will
+     * take over the ownership of of enclosed binary store.
+     *
+     * @param store: pointer to a valid BinaryStore.
+     * TODO: a minimal amount of error checking would be better
+     */
+    void addNewBinaryStore(
+        std::unique_ptr<binstore::BinaryStoreInterface> store);
+
+  private:
+    /* map of baseId: binaryStore, which has a 1:1 relationship. */
+    std::map<std::string, std::unique_ptr<binstore::BinaryStoreInterface>>
+        stores_;
+
+    /* map of sessionId: open binaryStore base, which has a 1:1 relationship. */
+    std::unordered_map<uint16_t, std::string> sessions_;
 };
 
 } // namespace blobs
diff --git a/test/handler_unittest.cpp b/test/handler_unittest.cpp
index 124a3ff..d96fc41 100644
--- a/test/handler_unittest.cpp
+++ b/test/handler_unittest.cpp
@@ -1,12 +1,106 @@
 #include "handler_unittest.hpp"
 
+using ::testing::_;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::StrNe;
+using namespace std::string_literals;
+using namespace binstore;
+
 namespace blobs
 {
 
+class BinaryStoreBlobHandlerBasicTest : public BinaryStoreBlobHandlerTest
+{
+  protected:
+    static inline std::string basicTestBaseId = "/test/"s;
+    static inline std::string basicTestBlobId = "/test/blob0"s;
+    static inline std::string basicTestInvalidBlobId = "/invalid/blob0"s;
+};
+
+TEST_F(BinaryStoreBlobHandlerBasicTest, CanHandleBlobZeroStoreFail)
+{
+    // Cannot handle since there is no store. Shouldn't crash.
+    EXPECT_FALSE(handler.canHandleBlob(basicTestInvalidBlobId));
+}
+
 TEST_F(BinaryStoreBlobHandlerBasicTest, CanHandleBlobChecksNameInvalid)
 {
+    auto bstore = std::make_unique<MockBinaryStore>();
+
+    handler.addNewBinaryStore(std::move(bstore));
+
     // Verify canHandleBlob checks and returns false on an invalid name.
-    EXPECT_FALSE(bstore.canHandleBlob("asdf"));
+    EXPECT_FALSE(handler.canHandleBlob(basicTestInvalidBlobId));
+}
+
+TEST_F(BinaryStoreBlobHandlerBasicTest, CanHandleBlobCanOpenValidBlob)
+{
+    auto bstore = std::make_unique<MockBinaryStore>();
+
+    EXPECT_CALL(*bstore, getBaseBlobId())
+        .WillRepeatedly(Return(basicTestBaseId));
+    EXPECT_CALL(*bstore, canHandleBlob(StrNe(basicTestBlobId)))
+        .WillRepeatedly(Return(false));
+    EXPECT_CALL(*bstore, canHandleBlob(StrEq(basicTestBlobId)))
+        .WillRepeatedly(Return(true));
+    handler.addNewBinaryStore(std::move(bstore));
+
+    // Verify canHandleBlob return true for a blob id that it can handle
+    EXPECT_FALSE(handler.canHandleBlob(basicTestInvalidBlobId));
+    EXPECT_TRUE(handler.canHandleBlob(basicTestBlobId));
+}
+
+TEST_F(BinaryStoreBlobHandlerBasicTest, CanHandleBlobCanOpenValidBlobMultiple)
+{
+    auto bstore = std::make_unique<MockBinaryStore>();
+    auto bstore1 = std::make_unique<MockBinaryStore>();
+    const std::string anotherBaseId = "/another/"s;
+    const std::string anotherBlobId = "/another/blob/id"s;
+
+    EXPECT_CALL(*bstore, getBaseBlobId())
+        .WillRepeatedly(Return(basicTestBaseId));
+    EXPECT_CALL(*bstore, canHandleBlob(StrNe(basicTestBlobId)))
+        .WillRepeatedly(Return(false));
+    EXPECT_CALL(*bstore, canHandleBlob(StrEq(basicTestBlobId)))
+        .WillRepeatedly(Return(true));
+    handler.addNewBinaryStore(std::move(bstore));
+
+    EXPECT_CALL(*bstore1, getBaseBlobId())
+        .WillRepeatedly(Return(anotherBaseId));
+    EXPECT_CALL(*bstore1, canHandleBlob(StrNe(anotherBlobId)))
+        .WillRepeatedly(Return(false));
+    EXPECT_CALL(*bstore1, canHandleBlob(StrEq(anotherBlobId)))
+        .WillRepeatedly(Return(true));
+    handler.addNewBinaryStore(std::move(bstore1));
+
+    // Verify canHandleBlob return true for a blob id that it can handle
+    EXPECT_FALSE(handler.canHandleBlob(basicTestInvalidBlobId));
+    EXPECT_TRUE(handler.canHandleBlob(basicTestBlobId));
+    EXPECT_TRUE(handler.canHandleBlob(anotherBlobId));
+}
+
+TEST_F(BinaryStoreBlobHandlerBasicTest, GetBlobIdEqualsConcatenationsOfIds)
+{
+    std::string baseId0 = "/test/"s;
+    std::string baseId1 = "/test1/"s;
+    std::vector<std::string> idList0 = {"/test/"s, "/test/0"s};
+    std::vector<std::string> idList1 = {"/test1/"s, "/test1/2"s};
+    auto expectedIdList = idList0;
+    expectedIdList.insert(expectedIdList.end(), idList1.begin(), idList1.end());
+
+    auto bstore0 = std::make_unique<MockBinaryStore>();
+    EXPECT_CALL(*bstore0, getBaseBlobId()).WillOnce(Return(baseId0));
+    EXPECT_CALL(*bstore0, getBlobIds()).WillOnce(Return(idList0));
+    handler.addNewBinaryStore(std::move(bstore0));
+
+    auto bstore1 = std::make_unique<MockBinaryStore>();
+    EXPECT_CALL(*bstore1, getBaseBlobId()).WillOnce(Return(baseId1));
+    EXPECT_CALL(*bstore1, getBlobIds()).WillOnce(Return(idList1));
+    handler.addNewBinaryStore(std::move(bstore1));
+
+    // Verify canHandleBlob return true for a blob id that it can handle
+    EXPECT_EQ(expectedIdList, handler.getBlobIds());
 }
 
 } // namespace blobs
diff --git a/test/handler_unittest.hpp b/test/handler_unittest.hpp
index fd93de4..f6390fc 100644
--- a/test/handler_unittest.hpp
+++ b/test/handler_unittest.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "binarystore_mock.hpp"
 #include "handler.hpp"
 
 #include <gtest/gtest.h>
@@ -11,12 +12,7 @@
 {
   protected:
     BinaryStoreBlobHandlerTest() = default;
-
-    BinaryStoreBlobHandler bstore;
-};
-
-class BinaryStoreBlobHandlerBasicTest : public BinaryStoreBlobHandlerTest
-{
+    BinaryStoreBlobHandler handler;
 };
 
 } // namespace blobs