binarystore: Enable maxBinarySize Feature

Fail on write() and commit() if the result will exceed the max size.

Enabled by adding the max_binary_size to the proto buffer. The new one
takes priority and will replace existing one even if it already exists.

The `max_binary_size` is the size of the total data including the size
header. It is calculated with
```
blob_.SerializeAsString().size() +
  sizeof(boost::endian::little_uint64_t)
```

Change-Id: I28a4c7a25fa066c11510b51cdfce27df4e09d3b5
Signed-off-by: Willy Tu <wltu@google.com>
diff --git a/binarystore.cpp b/binarystore.cpp
index e1cd03d..0760f33 100644
--- a/binarystore.cpp
+++ b/binarystore.cpp
@@ -11,6 +11,7 @@
 #include <cstdint>
 #include <ipmid/handler.hpp>
 #include <memory>
+#include <optional>
 #include <phosphor-logging/elog.hpp>
 #include <string>
 #include <vector>
@@ -30,7 +31,8 @@
 
 std::unique_ptr<BinaryStoreInterface>
     BinaryStore::createFromConfig(const std::string& baseBlobId,
-                                  std::unique_ptr<SysFile> file)
+                                  std::unique_ptr<SysFile> file,
+                                  std::optional<uint32_t> maxSize)
 {
     if (baseBlobId.empty() || !file)
     {
@@ -39,7 +41,8 @@
         return nullptr;
     }
 
-    auto store = std::make_unique<BinaryStore>(baseBlobId, std::move(file));
+    auto store =
+        std::make_unique<BinaryStore>(baseBlobId, std::move(file), maxSize);
 
     if (!store->loadSerializedData())
     {
@@ -50,7 +53,8 @@
 }
 
 std::unique_ptr<BinaryStoreInterface>
-    BinaryStore::createFromFile(std::unique_ptr<SysFile> file, bool readOnly)
+    BinaryStore::createFromFile(std::unique_ptr<SysFile> file, bool readOnly,
+                                std::optional<uint32_t> maxSize)
 {
     if (!file)
     {
@@ -58,7 +62,8 @@
         return nullptr;
     }
 
-    auto store = std::make_unique<BinaryStore>(std::move(file), readOnly);
+    auto store =
+        std::make_unique<BinaryStore>(std::move(file), readOnly, maxSize);
 
     if (!store->loadSerializedData())
     {
@@ -92,6 +97,16 @@
              * and is a valid case to handle. Simply init an empty binstore. */
             commitState_ = CommitState::Uninitialized;
         }
+
+        // The new max size takes priority
+        if (maxSize)
+        {
+            blob_.set_max_size_bytes(*maxSize);
+        }
+        else
+        {
+            blob_.clear_max_size_bytes();
+        }
     }
     catch (const std::system_error& e)
     {
@@ -288,9 +303,25 @@
         return false;
     }
 
+    bool needResize = offset + data.size() > dataPtr->size();
+
+    // current size is the binary blob proto size + uint64 tracking the total
+    // size of the binary blob.
+    // currentSize = blob_size + x (uint64_t), where x = blob_size.
+    size_t currentSize = blob_.SerializeAsString().size() +
+                         sizeof(boost::endian::little_uint64_t);
+    size_t sizeDelta = needResize ? offset + data.size() - dataPtr->size() : 0;
+
+    if (maxSize && currentSize + sizeDelta > *maxSize)
+    {
+        log<level::ERR>("Write data would make the total size exceed the max "
+                        "size allowed. Return.");
+        return false;
+    }
+
     commitState_ = CommitState::Dirty;
     /* Copy (overwrite) the data */
-    if (offset + data.size() > dataPtr->size())
+    if (needResize)
     {
         dataPtr->resize(offset + data.size()); // not enough space, extend
     }
@@ -313,6 +344,13 @@
                            sizeof(sizeLE));
     commitData += blobData;
 
+    // This should never be true if it is blocked by the write command
+    if (maxSize && sizeof(commitData) > *maxSize)
+    {
+        log<level::ERR>("Commit Data excedded maximum allowed size");
+        return false;
+    }
+
     try
     {
         file_->writeStr(commitData, 0);