binarystore: Implement read/write

Implement binarystore read/write and add corresponding unit tests.

Signed-off-by: Kun Yi <kunyi@google.com>
Change-Id: I0d12d7e84abf4c6bc9ac40a780ba5233f3a83a20
diff --git a/binarystore.cpp b/binarystore.cpp
index 092a069..764b9fb 100644
--- a/binarystore.cpp
+++ b/binarystore.cpp
@@ -75,13 +75,56 @@
 
 std::vector<uint8_t> BinaryStore::read(uint32_t offset, uint32_t requestedSize)
 {
-    std::vector<std::uint8_t> result;
+    std::vector<uint8_t> result;
+
+    if (!currentBlob_)
+    {
+        return result;
+    }
+
+    auto dataPtr = currentBlob_->mutable_data();
+
+    /* If it is out of bound, return empty vector */
+    if (offset >= dataPtr->size())
+    {
+        return result;
+    }
+
+    uint32_t requestedEndPos = offset + requestedSize;
+
+    result = std::vector<uint8_t>(
+        dataPtr->begin() + offset,
+        std::min(dataPtr->begin() + requestedEndPos, dataPtr->end()));
     return result;
 }
 
 bool BinaryStore::write(uint32_t offset, const std::vector<uint8_t>& data)
 {
-    return false;
+    if (!currentBlob_)
+    {
+        return false;
+    }
+
+    if (!writable_)
+    {
+        return false;
+    }
+
+    auto dataPtr = currentBlob_->mutable_data();
+
+    if (offset > dataPtr->size())
+    {
+        /* Will leave a gap with undefined data */
+        return false;
+    }
+
+    /* Copy (overwrite) the data */
+    if (offset + data.size() > dataPtr->size())
+    {
+        dataPtr->resize(offset + data.size()); // not enough space, extend
+    }
+    std::copy(data.begin(), data.end(), dataPtr->begin() + offset);
+    return true;
 }
 
 bool BinaryStore::commit()
diff --git a/binarystore_mock.hpp b/binarystore_mock.hpp
index 34bec6a..bdbb052 100644
--- a/binarystore_mock.hpp
+++ b/binarystore_mock.hpp
@@ -26,6 +26,10 @@
                 Invoke(&real_store_, &BinaryStore::openOrCreateBlob));
         ON_CALL(*this, close)
             .WillByDefault(Invoke(&real_store_, &BinaryStore::close));
+        ON_CALL(*this, read)
+            .WillByDefault(Invoke(&real_store_, &BinaryStore::read));
+        ON_CALL(*this, write)
+            .WillByDefault(Invoke(&real_store_, &BinaryStore::write));
     }
     MOCK_CONST_METHOD0(getBaseBlobId, std::string());
     MOCK_CONST_METHOD0(getBlobIds, std::vector<std::string>());
diff --git a/test/handler_readwrite_unittest.cpp b/test/handler_readwrite_unittest.cpp
index 17e3cef..5579499 100644
--- a/test/handler_readwrite_unittest.cpp
+++ b/test/handler_readwrite_unittest.cpp
@@ -6,6 +6,8 @@
 #include <vector>
 
 using ::testing::_;
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
 using ::testing::Return;
 using ::testing::StartsWith;
 
@@ -18,21 +20,34 @@
 class BinaryStoreBlobHandlerReadWriteTest : public BinaryStoreBlobHandlerTest
 {
   protected:
+    BinaryStoreBlobHandlerReadWriteTest()
+    {
+        addDefaultStore(rwTestBaseId);
+    }
+
     static inline std::string rwTestBaseId = "/test/"s;
     static inline std::string rwTestBlobId = "/test/blob0"s;
     static inline std::vector<uint8_t> rwTestData = {0, 1, 2, 3, 4,
                                                      5, 6, 7, 8, 9};
+    static inline uint16_t rwTestROFlags = OpenFlags::read;
+    static inline uint16_t rwTestRWFlags = OpenFlags::read | OpenFlags::write;
     static inline uint16_t rwTestSessionId = 0;
     static inline uint32_t rwTestOffset = 0;
+
+    void openAndWriteTestData()
+    {
+        EXPECT_TRUE(handler.open(rwTestSessionId, rwTestRWFlags, rwTestBlobId));
+        EXPECT_TRUE(handler.write(rwTestSessionId, rwTestOffset, rwTestData));
+    }
 };
 
 TEST_F(BinaryStoreBlobHandlerReadWriteTest, ReadWriteReturnsWhatStoreReturns)
 {
-    uint16_t flags = OpenFlags::read;
     const std::vector<uint8_t> emptyData;
     auto store = defaultMockStore(rwTestBaseId);
 
-    EXPECT_CALL(*store, openOrCreateBlob(_, flags)).WillOnce(Return(true));
+    EXPECT_CALL(*store, openOrCreateBlob(_, rwTestRWFlags))
+        .WillOnce(Return(true));
     EXPECT_CALL(*store, read(rwTestOffset, _))
         .WillOnce(Return(emptyData))
         .WillOnce(Return(rwTestData));
@@ -42,11 +57,92 @@
 
     handler.addNewBinaryStore(std::move(store));
 
-    EXPECT_TRUE(handler.open(rwTestSessionId, flags, rwTestBlobId));
+    EXPECT_TRUE(handler.open(rwTestSessionId, rwTestRWFlags, rwTestBlobId));
     EXPECT_EQ(emptyData, handler.read(rwTestSessionId, rwTestOffset, 1));
     EXPECT_EQ(rwTestData, handler.read(rwTestSessionId, rwTestOffset, 1));
     EXPECT_FALSE(handler.write(rwTestSessionId, rwTestOffset, emptyData));
     EXPECT_TRUE(handler.write(rwTestSessionId, rwTestOffset, rwTestData));
 }
 
+TEST_F(BinaryStoreBlobHandlerReadWriteTest, WriteFailForWrongSession)
+{
+    uint16_t wrongSessionId = 1;
+    EXPECT_TRUE(handler.open(rwTestSessionId, rwTestRWFlags, rwTestBlobId));
+    EXPECT_FALSE(handler.write(wrongSessionId, rwTestOffset, rwTestData));
+}
+
+TEST_F(BinaryStoreBlobHandlerReadWriteTest, WriteFailForWrongFlag)
+{
+    EXPECT_TRUE(handler.open(rwTestSessionId, rwTestROFlags, rwTestBlobId));
+    EXPECT_FALSE(handler.write(rwTestSessionId, rwTestOffset, rwTestData));
+}
+
+TEST_F(BinaryStoreBlobHandlerReadWriteTest, ReadReturnEmptyForWrongSession)
+{
+    uint16_t wrongSessionId = 1;
+    EXPECT_TRUE(handler.open(rwTestSessionId, rwTestROFlags, rwTestBlobId));
+    EXPECT_THAT(handler.read(wrongSessionId, rwTestOffset, rwTestData.size()),
+                IsEmpty());
+}
+
+TEST_F(BinaryStoreBlobHandlerReadWriteTest, AbleToReadDataWritten)
+{
+    openAndWriteTestData();
+    EXPECT_EQ(rwTestData,
+              handler.read(rwTestSessionId, rwTestOffset, rwTestData.size()));
+}
+
+TEST_F(BinaryStoreBlobHandlerReadWriteTest, ReadBeyondEndReturnsEmpty)
+{
+    openAndWriteTestData();
+    const size_t offsetBeyondEnd = rwTestData.size();
+    EXPECT_THAT(
+        handler.read(rwTestSessionId, offsetBeyondEnd, rwTestData.size()),
+        IsEmpty());
+}
+
+TEST_F(BinaryStoreBlobHandlerReadWriteTest, ReadAtOffset)
+{
+    openAndWriteTestData();
+    EXPECT_EQ(rwTestData,
+              handler.read(rwTestSessionId, rwTestOffset, rwTestData.size()));
+
+    // Now read over the entire offset range for 1 byte
+    for (size_t i = 0; i < rwTestData.size(); ++i)
+    {
+        EXPECT_THAT(handler.read(rwTestSessionId, i, 1),
+                    ElementsAre(rwTestData[i]));
+    }
+}
+
+TEST_F(BinaryStoreBlobHandlerReadWriteTest, PartialOverwriteDataWorks)
+{
+    // Construct a partially overwritten vector
+    const size_t overwritePos = 8;
+    std::vector<uint8_t> overwriteData = {8, 9, 10, 11, 12};
+    std::vector<uint8_t> expectedOverWrittenData(
+        rwTestData.begin(), rwTestData.begin() + overwritePos);
+    expectedOverWrittenData.insert(expectedOverWrittenData.end(),
+                                   overwriteData.begin(), overwriteData.end());
+
+    openAndWriteTestData();
+    EXPECT_EQ(rwTestData,
+              handler.read(rwTestSessionId, rwTestOffset, rwTestData.size()));
+    EXPECT_TRUE(handler.write(rwTestSessionId, overwritePos, overwriteData));
+
+    EXPECT_EQ(expectedOverWrittenData,
+              handler.read(rwTestSessionId, 0, expectedOverWrittenData.size()));
+
+    EXPECT_THAT(
+        handler.read(rwTestSessionId, expectedOverWrittenData.size(), 1),
+        IsEmpty());
+}
+
+TEST_F(BinaryStoreBlobHandlerReadWriteTest, WriteWithGapShouldFail)
+{
+    const auto gapOffset = 10;
+    EXPECT_TRUE(handler.open(rwTestSessionId, rwTestRWFlags, rwTestBlobId));
+    EXPECT_FALSE(handler.write(rwTestSessionId, gapOffset, rwTestData));
+}
+
 } // namespace blobs