Redfish:Dump offload handler implementation using nbd-proxy

This handler transfers data between nbd-client and nbd-server.
basically it invokes nbd-proxy and reads data from socket
and writes on to nbd-client and vice-versa

Change-Id: I429393a5e056647333bf4e148c0df2a5695b2a47
Signed-off-by: Ravi Teja <raviteja28031990@gmail.com>
diff --git a/include/dump_offload.hpp b/include/dump_offload.hpp
new file mode 100644
index 0000000..e628ec6
--- /dev/null
+++ b/include/dump_offload.hpp
@@ -0,0 +1,305 @@
+#pragma once
+
+#include <signal.h>
+#include <sys/select.h>
+
+#include <boost/beast/core/flat_static_buffer.hpp>
+#include <boost/beast/http.hpp>
+#include <boost/process.hpp>
+#include <cstdio>
+#include <cstdlib>
+
+namespace crow
+{
+namespace obmc_dump
+{
+
+inline void handleDumpOffloadUrl(const crow::Request& req, crow::Response& res,
+                                 const std::string& entryId);
+inline void resetHandler();
+
+// The max network block device buffer size is 128kb plus 16bytes
+// for the message header
+static constexpr auto nbdBufferSize = 131088;
+
+/** class Handler
+ *  handles data transfer between nbd-client and nbd-server.
+ *  This handler invokes nbd-proxy and reads data from socket
+ *  and writes on to nbd-client and vice-versa
+ */
+class Handler : public std::enable_shared_from_this<Handler>
+{
+  public:
+    Handler(const std::string& media, boost::asio::io_context& ios,
+            const std::string& entryID) :
+        pipeOut(ios),
+        pipeIn(ios), media(media), entryID(entryID), doingWrite(false),
+        negotiationDone(false), writeonnbd(false),
+        outputBuffer(std::make_unique<
+                     boost::beast::flat_static_buffer<nbdBufferSize>>()),
+        inputBuffer(
+            std::make_unique<boost::beast::flat_static_buffer<nbdBufferSize>>())
+    {
+    }
+
+    ~Handler()
+    {
+    }
+
+    /**
+     * @brief  Invokes InitiateOffload method of dump manager which
+     *         directs pldm to start writing on the nbd device.
+     *
+     * @return void
+     */
+    void initiateOffloadOnNbdDevice()
+    {
+        crow::connections::systemBus->async_method_call(
+            [this,
+             self(shared_from_this())](const boost::system::error_code ec) {
+                if (ec)
+                {
+                    BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
+                    resetBuffers();
+                    resetHandler();
+                    return;
+                }
+            },
+            "xyz.openbmc_project.Dump.Manager",
+            "/xyz/openbmc_project/dump/entry/" + entryID,
+            "xyz.openbmc_project.Dump.Entry", "InitiateOffload");
+    }
+
+    /**
+     * @brief  Kills nbd-proxy
+     *
+     * @return void
+     */
+    void doClose()
+    {
+        int rc = kill(proxy.id(), SIGTERM);
+        if (rc)
+        {
+            return;
+        }
+        proxy.wait();
+    }
+
+    /**
+     * @brief  Starts nbd-proxy
+     *
+     * @return void
+     */
+    void connect()
+    {
+        std::error_code ec;
+        proxy = boost::process::child("/usr/sbin/nbd-proxy", media,
+                                      boost::process::std_out > pipeOut,
+                                      boost::process::std_in < pipeIn, ec);
+        if (ec)
+        {
+            BMCWEB_LOG_ERROR << "Couldn't connect to nbd-proxy: "
+                             << ec.message();
+            resetHandler();
+            return;
+        }
+        doRead();
+    }
+
+    /**
+     * @brief  Wait for data on tcp socket from nbd-server.
+     *
+     * @return void
+     */
+    void waitForMessageOnSocket()
+    {
+
+        std::size_t bytes = inputBuffer->capacity() - inputBuffer->size();
+
+        (*stream).async_read_some(
+            inputBuffer->prepare(bytes),
+            [this,
+             self(shared_from_this())](const boost::system::error_code& ec,
+                                       std::size_t bytes_transferred) {
+                if (ec)
+                {
+                    BMCWEB_LOG_DEBUG << "Error while reading on socket";
+                    doClose();
+                    resetBuffers();
+                    resetHandler();
+                    return;
+                }
+
+                inputBuffer->commit(bytes_transferred);
+                doWrite();
+            });
+    }
+
+    /**
+     * @brief  Writes data on input pipe of nbd-client.
+     *
+     * @return void
+     */
+    void doWrite()
+    {
+
+        if (doingWrite)
+        {
+            BMCWEB_LOG_DEBUG << "Already writing.  Bailing out";
+            return;
+        }
+
+        if (inputBuffer->size() == 0)
+        {
+            BMCWEB_LOG_DEBUG << "inputBuffer empty.  Bailing out";
+            return;
+        }
+
+        doingWrite = true;
+        boost::asio::async_write(
+            pipeIn, inputBuffer->data(),
+            [this, self(shared_from_this())](const boost::beast::error_code& ec,
+                                             std::size_t bytesWritten) {
+                if (ec)
+                {
+                    BMCWEB_LOG_DEBUG << "VM socket port closed";
+                    doClose();
+                    resetBuffers();
+                    resetHandler();
+                    return;
+                }
+
+                doingWrite = false;
+
+                if (negotiationDone == false)
+                {
+                    // "gDf" is NBD reply magic
+                    std::string reply_magic("gDf");
+                    std::string reply_string(
+                        static_cast<char*>(inputBuffer->data().data()),
+                        bytesWritten);
+                    std::size_t found = reply_string.find(reply_magic);
+                    if (found != std::string::npos)
+                    {
+                        negotiationDone = true;
+                        writeonnbd = true;
+                    }
+                }
+
+                inputBuffer->consume(bytesWritten);
+                waitForMessageOnSocket();
+                if (writeonnbd)
+                {
+                    // NBD Negotiation Complete!!!!. Notify Dump manager to
+                    // start dumping the actual data over NBD device
+                    initiateOffloadOnNbdDevice();
+                    writeonnbd = false;
+                }
+            });
+    }
+
+    /**
+     * @brief  Reads data on output pipe of nbd-client and write on
+     *         tcp socket.
+     *
+     * @return void
+     */
+    void doRead()
+    {
+        std::size_t bytes = outputBuffer->capacity() - outputBuffer->size();
+
+        pipeOut.async_read_some(
+            outputBuffer->prepare(bytes),
+            [this, self(shared_from_this())](
+                const boost::system::error_code& ec, std::size_t bytesRead) {
+                if (ec)
+                {
+                    BMCWEB_LOG_ERROR << "Couldn't read from VM port: " << ec;
+                    doClose();
+                    resetBuffers();
+                    resetHandler();
+                    return;
+                }
+
+                outputBuffer->commit(bytesRead);
+
+                boost::asio::async_write(
+                    *stream, outputBuffer->data(),
+                    [this](const boost::system::error_code& ec,
+                           std::size_t bytes_transferred) {
+                        if (ec)
+                        {
+                            BMCWEB_LOG_DEBUG << "Error while writing on socket";
+                            doClose();
+                            resetBuffers();
+                            resetHandler();
+                            return;
+                        }
+
+                        outputBuffer->consume(bytes_transferred);
+                        doRead();
+                    });
+            });
+    }
+
+    /**
+     * @brief  Resets input and output buffers.
+     * @return void
+     */
+    void resetBuffers()
+    {
+#if BOOST_VERSION >= 107000
+        this->inputBuffer->clear();
+        this->outputBuffer->clear();
+#else
+        this->inputBuffer->reset();
+        this->outputBuffer->reset();
+#endif
+    }
+
+    boost::process::async_pipe pipeOut;
+    boost::process::async_pipe pipeIn;
+    boost::process::child proxy;
+    std::string media;
+    std::string entryID;
+    bool doingWrite;
+    bool negotiationDone;
+    bool writeonnbd;
+    std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
+        outputBuffer;
+    std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
+        inputBuffer;
+    std::shared_ptr<crow::Request::Adaptor> stream;
+};
+
+static std::shared_ptr<Handler> handler;
+inline void resetHandler()
+{
+
+    handler.reset();
+}
+inline void handleDumpOffloadUrl(const crow::Request& req, crow::Response& res,
+                                 const std::string& entryId)
+{
+
+    // Run only one instance of Handler, one dump offload can happen at a time
+    if (handler != nullptr)
+    {
+        BMCWEB_LOG_ERROR << "Handler already running";
+        res.result(boost::beast::http::status::service_unavailable);
+        res.jsonValue["Description"] = "Service is already being used";
+        res.end();
+        return;
+    }
+
+    const char* media = "1";
+    boost::asio::io_context* io_con = req.ioService;
+
+    handler = std::make_shared<Handler>(media, *io_con, entryId);
+    handler->stream =
+        std::make_shared<crow::Request::Adaptor>(std::move(req.socket()));
+    handler->connect();
+    handler->waitForMessageOnSocket();
+}
+} // namespace obmc_dump
+} // namespace crow