initial drop of phosphor-ipmi-blobs

This implements a majority of the OEM IPMI BLOBS protocol.  The only
piece missing from this is the timed expiration of sessions.

Change-Id: I82c9d17b625c94fc3340edcfabbbf1ffeb5ad7ac
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/manager.cpp b/manager.cpp
new file mode 100644
index 0000000..b6311b4
--- /dev/null
+++ b/manager.cpp
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "manager.hpp"
+
+#include <string>
+#include <vector>
+
+namespace blobs
+{
+
+void BlobManager::incrementOpen(const std::string& path)
+{
+    if (path.empty())
+    {
+        return;
+    }
+
+    openFiles[path] += 1;
+}
+
+void BlobManager::decrementOpen(const std::string& path)
+{
+    if (path.empty())
+    {
+        return;
+    }
+
+    /* TODO(venture): Check into the iterator from find, does it makes sense
+     * to just update it directly? */
+    auto entry = openFiles.find(path);
+    if (entry != openFiles.end())
+    {
+        /* found it, decrement it and remove it if 0. */
+        openFiles[path] -= 1;
+        if (openFiles[path] == 0)
+        {
+            openFiles.erase(path);
+        }
+    }
+}
+
+int BlobManager::getOpen(const std::string& path) const
+{
+    /* No need to input check on the read-only call. */
+    auto entry = openFiles.find(path);
+    if (entry != openFiles.end())
+    {
+        return entry->second;
+    }
+
+    return 0;
+}
+
+bool BlobManager::registerHandler(std::unique_ptr<GenericBlobInterface> handler)
+{
+    if (!handler)
+    {
+        return false;
+    }
+
+    handlers.push_back(std::move(handler));
+    return true;
+}
+
+uint32_t BlobManager::buildBlobList()
+{
+    /* Clear out the current list (IPMI handler is presently single-threaded).
+     */
+    ids.clear();
+
+    /* Grab the list of blobs and extend the local list */
+    for (auto& h : handlers)
+    {
+        std::vector<std::string> blobs = h->getBlobIds();
+        ids.insert(ids.end(), blobs.begin(), blobs.end());
+    }
+
+    return ids.size();
+}
+
+std::string BlobManager::getBlobId(uint32_t index)
+{
+    /* Range check. */
+    if (index >= ids.size())
+    {
+        return "";
+    }
+
+    return ids[index];
+}
+
+bool BlobManager::open(uint16_t flags, const std::string& path,
+                       uint16_t* session)
+{
+    GenericBlobInterface* handler = getHandler(path);
+
+    /* No handler found. */
+    if (!handler)
+    {
+        return false;
+    }
+
+    /* No sessions availabe... */
+    if (!getSession(session))
+    {
+        return false;
+    }
+
+    /* Verify flags - must be at least read or write */
+    if (!(flags & (OpenFlags::read | OpenFlags::write)))
+    {
+        /* Neither read not write set, which means calls to Read/Write will
+         * reject. */
+        return false;
+    }
+
+    if (!handler->open(*session, flags, path))
+    {
+        return false;
+    }
+
+    /* Associate session with handler */
+    sessions[*session] = SessionInfo(path, handler, flags);
+    incrementOpen(path);
+    return true;
+}
+
+GenericBlobInterface* BlobManager::getHandler(const std::string& path)
+{
+    /* Find a handler. */
+    GenericBlobInterface* handler = nullptr;
+
+    for (auto& h : handlers)
+    {
+        if (h->canHandleBlob(path))
+        {
+            handler = h.get();
+            break;
+        }
+    }
+
+    return handler;
+}
+
+GenericBlobInterface* BlobManager::getHandler(uint16_t session)
+{
+    auto item = sessions.find(session);
+    if (item == sessions.end())
+    {
+        return nullptr;
+    }
+
+    return item->second.handler;
+}
+
+SessionInfo* BlobManager::getSessionInfo(uint16_t session)
+{
+    auto item = sessions.find(session);
+    if (item == sessions.end())
+    {
+        return nullptr;
+    }
+
+    /* If we go to multi-threaded, this pointer can be invalidated and this
+     * method will need to change.
+     */
+    return &item->second;
+}
+
+std::string BlobManager::getPath(uint16_t session) const
+{
+    auto item = sessions.find(session);
+    if (item == sessions.end())
+    {
+        return "";
+    }
+
+    return item->second.blobId;
+}
+
+bool BlobManager::stat(const std::string& path, struct BlobMeta* meta)
+{
+    /* meta should never be NULL. */
+    GenericBlobInterface* handler = getHandler(path);
+
+    /* No handler found. */
+    if (!handler)
+    {
+        return false;
+    }
+
+    return handler->stat(path, meta);
+}
+
+bool BlobManager::stat(uint16_t session, struct BlobMeta* meta)
+{
+    /* meta should never be NULL. */
+    GenericBlobInterface* handler = getHandler(session);
+
+    /* No handler found. */
+    if (!handler)
+    {
+        return false;
+    }
+
+    return handler->stat(session, meta);
+}
+
+bool BlobManager::commit(uint16_t session, const std::vector<uint8_t>& data)
+{
+    GenericBlobInterface* handler = getHandler(session);
+
+    /* No handler found. */
+    if (!handler)
+    {
+        return false;
+    }
+
+    return handler->commit(session, data);
+}
+
+bool BlobManager::close(uint16_t session)
+{
+    GenericBlobInterface* handler = getHandler(session);
+
+    /* No handler found. */
+    if (!handler)
+    {
+        return false;
+    }
+
+    /* Handler returns failure */
+    if (!handler->close(session))
+    {
+        return false;
+    }
+
+    sessions.erase(session);
+    decrementOpen(getPath(session));
+    return true;
+}
+
+std::vector<uint8_t> BlobManager::read(uint16_t session, uint32_t offset,
+                                       uint32_t requestedSize)
+{
+    SessionInfo* info = getSessionInfo(session);
+
+    /* No session found. */
+    if (!info)
+    {
+        return std::vector<uint8_t>();
+    }
+
+    /* Check flags. */
+    if (!(info->flags & OpenFlags::read))
+    {
+        return std::vector<uint8_t>();
+    }
+
+    /* Try reading from it. */
+    return info->handler->read(session, offset, requestedSize);
+}
+
+bool BlobManager::write(uint16_t session, uint32_t offset,
+                        const std::vector<uint8_t>& data)
+{
+    SessionInfo* info = getSessionInfo(session);
+
+    /* No session found. */
+    if (!info)
+    {
+        return false;
+    }
+
+    /* Check flags. */
+    if (!(info->flags & OpenFlags::write))
+    {
+        return false;
+    }
+
+    /* Try writing to it. */
+    return info->handler->write(session, offset, data);
+}
+
+bool BlobManager::deleteBlob(const std::string& path)
+{
+    GenericBlobInterface* handler = getHandler(path);
+
+    /* No handler found. */
+    if (!handler)
+    {
+        return false;
+    }
+
+    /* Check if the file has any open handles. */
+    if (getOpen(path) > 0)
+    {
+        return false;
+    }
+
+    /* Try deleting it. */
+    return handler->deleteBlob(path);
+}
+
+bool BlobManager::getSession(uint16_t* sess)
+{
+    uint16_t tries = 0;
+    uint16_t lsess;
+
+    if (!sess)
+    {
+        return false;
+    }
+
+    /* This is not meant to fail as you have 64KiB values available. */
+
+    /* TODO(venture): We could just count the keys in the session map to know
+     * if it's full.
+     */
+    do
+    {
+        lsess = next++;
+        if (!sessions.count(lsess))
+        {
+            /* value not in use, return it. */
+            (*sess) = lsess;
+            return true;
+        }
+    } while (++tries < 0xffff);
+
+    return false;
+}
+} // namespace blobs