add example handler

This adds an example handler to demonstrate how one can add a specific
type of BLOB handler.

Change-Id: Ib5421f1b945b45998b40d3939a4dab9cdf39aaa9
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/Makefile.am b/Makefile.am
index 624bbf2..51f7bdd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,12 +1,19 @@
 AM_DEFAULT_SOURCE_EXT = .cpp
 
+HANDLERS =
+
+if ENABLE_EXAMPLE
+HANDLERS += example/example.cpp
+endif
+
 libblobcmdsdir = ${libdir}/ipmid-providers
 libblobcmds_LTLIBRARIES = libblobcmds.la
 libblobcmds_la_SOURCES = main.cpp \
 			 ipmi.cpp \
 			 manager.cpp \
 			 process.cpp \
-			 crc.cpp
+			 crc.cpp \
+			 $(HANDLERS)
 
 libblobcmds_la_LDFLAGS = $(SYSTEMD_LIBS) \
                          -version-info 0:0:0 -shared
diff --git a/configure.ac b/configure.ac
index 186c711..7ef48a4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -53,6 +53,12 @@
     AC_SUBST([OESDK_TESTCASE_FLAGS], [$testcase_flags])
 )
 
+# Do you want to include the example blob handler?
+AC_ARG_ENABLE([example],
+    AS_HELP_STRING([--enable-example], [Enable example blob handler])
+)
+AM_CONDITIONAL(ENABLE_EXAMPLE, [test "x$enable_example" = "xyes"])
+
 # Create configured output
 AC_CONFIG_FILES([Makefile test/Makefile])
 AC_OUTPUT
diff --git a/example/example.cpp b/example/example.cpp
new file mode 100644
index 0000000..de05f00
--- /dev/null
+++ b/example/example.cpp
@@ -0,0 +1,167 @@
+#include "example/example.hpp"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+namespace blobs
+{
+
+constexpr char ExampleBlobHandler::supportedPath[];
+
+ExampleBlob* ExampleBlobHandler::getSession(uint16_t id)
+{
+    auto search = sessions.find(id);
+    if (search == sessions.end())
+    {
+        return nullptr;
+    }
+    /* Not thread-safe, however, the blob handler deliberately assumes serial
+     * execution. */
+    return &search->second;
+}
+
+bool ExampleBlobHandler::canHandleBlob(const std::string& path)
+{
+    return (path == supportedPath);
+}
+
+std::vector<std::string> ExampleBlobHandler::getBlobIds()
+{
+    return {supportedPath};
+}
+
+bool ExampleBlobHandler::deleteBlob(const std::string& path)
+{
+    return false;
+}
+
+bool ExampleBlobHandler::stat(const std::string& path, struct BlobMeta* meta)
+{
+    return false;
+}
+
+bool ExampleBlobHandler::open(uint16_t session, uint16_t flags,
+                              const std::string& path)
+{
+    if (!canHandleBlob(path))
+    {
+        return false;
+    }
+
+    auto findSess = sessions.find(session);
+    if (findSess != sessions.end())
+    {
+        /* This session is already active. */
+        return false;
+    }
+    sessions[session] = ExampleBlob(session, flags);
+    return true;
+}
+
+std::vector<uint8_t> ExampleBlobHandler::read(uint16_t session, uint32_t offset,
+                                              uint32_t requestedSize)
+{
+    ExampleBlob* sess = getSession(session);
+    if (!sess)
+    {
+        return std::vector<uint8_t>();
+    }
+
+    /* Is the offset beyond the array? */
+    if (offset >= sizeof(sess->buffer))
+    {
+        return std::vector<uint8_t>();
+    }
+
+    /* Determine how many bytes we can read from the offset.
+     * In this case, if they read beyond "size" we allow it.
+     */
+    uint32_t remain = sizeof(sess->buffer) - offset;
+    uint32_t numBytes = std::min(remain, requestedSize);
+    /* Copy the bytes! */
+    std::vector<uint8_t> result(numBytes);
+    std::memcpy(result.data(), &sess->buffer[offset], numBytes);
+    return result;
+}
+
+bool ExampleBlobHandler::write(uint16_t session, uint32_t offset,
+                               const std::vector<uint8_t>& data)
+{
+    ExampleBlob* sess = getSession(session);
+    if (!sess)
+    {
+        return false;
+    }
+    /* Is the offset beyond the array? */
+    if (offset >= sizeof(sess->buffer))
+    {
+        return false;
+    }
+    /* Determine whether all their bytes will fit. */
+    uint32_t remain = sizeof(sess->buffer) - offset;
+    if (data.size() > remain)
+    {
+        return false;
+    }
+    sess->length =
+        std::max(offset + data.size(),
+                 static_cast<std::vector<uint8_t>::size_type>(sess->length));
+    std::memcpy(&sess->buffer[offset], data.data(), data.size());
+    return true;
+}
+
+bool ExampleBlobHandler::commit(uint16_t session,
+                                const std::vector<uint8_t>& data)
+{
+    ExampleBlob* sess = getSession(session);
+    if (!sess)
+    {
+        return false;
+    }
+
+    /* Do something with the staged data!. */
+
+    return false;
+}
+
+bool ExampleBlobHandler::close(uint16_t session)
+{
+    ExampleBlob* sess = getSession(session);
+    if (!sess)
+    {
+        return false;
+    }
+
+    sessions.erase(session);
+    return true;
+}
+
+bool ExampleBlobHandler::stat(uint16_t session, struct BlobMeta* meta)
+{
+    ExampleBlob* sess = getSession(session);
+    if (!sess)
+    {
+        return false;
+    }
+    if (!meta)
+    {
+        return false;
+    }
+    meta->size = sess->length;
+    meta->blobState = sess->state;
+    return true;
+}
+
+bool ExampleBlobHandler::expire(uint16_t session)
+{
+    ExampleBlob* sess = getSession(session);
+    if (!sess)
+    {
+        return false;
+    }
+    /* TODO: implement session expiration behavior. */
+    return false;
+}
+
+} // namespace blobs
diff --git a/example/example.hpp b/example/example.hpp
new file mode 100644
index 0000000..0ea9572
--- /dev/null
+++ b/example/example.hpp
@@ -0,0 +1,71 @@
+#pragma once
+
+#include "blobs.hpp"
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace blobs
+{
+
+constexpr int kBufferSize = 1024;
+
+struct ExampleBlob
+{
+    ExampleBlob() = default;
+    ExampleBlob(uint16_t id, uint16_t flags) :
+        sessionId(id), flags(flags), length(0)
+    {
+    }
+
+    /* The blob handler session id. */
+    uint16_t sessionId;
+
+    /* The flags passed into open. */
+    uint16_t flags;
+
+    /* The buffer is a fixed size, but length represents the number of bytes
+     * expected to be used contiguously from offset 0.
+     */
+    uint32_t length;
+
+    /* The staging buffer. */
+    uint8_t buffer[kBufferSize];
+};
+
+class ExampleBlobHandler : public GenericBlobInterface
+{
+  public:
+    /* We want everything explicitly default. */
+    ExampleBlobHandler() = default;
+    ~ExampleBlobHandler() = default;
+    ExampleBlobHandler(const ExampleBlobHandler&) = default;
+    ExampleBlobHandler& operator=(const ExampleBlobHandler&) = default;
+    ExampleBlobHandler(ExampleBlobHandler&&) = default;
+    ExampleBlobHandler& operator=(ExampleBlobHandler&&) = default;
+
+    bool canHandleBlob(const std::string& path) override;
+    std::vector<std::string> getBlobIds() override;
+    bool deleteBlob(const std::string& path) override;
+    bool stat(const std::string& path, struct BlobMeta* meta) override;
+    bool open(uint16_t session, uint16_t flags,
+              const std::string& path) override;
+    std::vector<uint8_t> read(uint16_t session, uint32_t offset,
+                              uint32_t requestedSize) override;
+    bool write(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;
+    bool expire(uint16_t session) override;
+
+    constexpr static char supportedPath[] = "/dev/fake/command";
+
+  private:
+    ExampleBlob* getSession(uint16_t id);
+
+    std::unordered_map<uint16_t, ExampleBlob> sessions;
+};
+
+} // namespace blobs
diff --git a/main.cpp b/main.cpp
index e7c1247..a28a232 100644
--- a/main.cpp
+++ b/main.cpp
@@ -26,6 +26,10 @@
 #include <host-ipmid/oemrouter.hpp>
 #include <memory>
 
+#if ENABLE_EXAMPLE
+#include "example/example.hpp"
+#endif
+
 /* TODO: Swap out once https://gerrit.openbmc-project.xyz/12743 is merged */
 namespace oem
 {
@@ -73,5 +77,9 @@
                                handleBlobCommand);
 
     manager = std::make_unique<BlobManager>();
+
+#if ENABLE_EXAMPLE
+    manager->registerHandler(std::move(std::make_unique<ExampleBlobHandler>()));
+#endif
 }
 } // namespace blobs