tools: add network bridge support

Add support to the host tool for sending the image data over a network
connection.

Signed-off-by: Benjamin Fair <benjaminfair@google.com>
Change-Id: I88630d79499254d6c80ceaa8c7721c241d394fc8
diff --git a/tools/net.cpp b/tools/net.cpp
new file mode 100644
index 0000000..4d2ce13
--- /dev/null
+++ b/tools/net.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2019 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 "net.hpp"
+
+#include "data.hpp"
+#include "flags.hpp"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <sys/sendfile.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <cstdint>
+#include <cstring>
+#include <ipmiblob/blob_errors.hpp>
+#include <memory>
+#include <stdplus/handle/managed.hpp>
+#include <string>
+#include <vector>
+
+namespace
+{
+
+void closefd(int&& fd, const internal::Sys*& sys)
+{
+    sys->close(fd);
+}
+using Fd = stdplus::Managed<int, const internal::Sys*>::Handle<closefd>;
+
+} // namespace
+
+namespace host_tool
+{
+
+bool NetDataHandler::sendContents(const std::string& input,
+                                  std::uint16_t session)
+{
+    constexpr size_t blockSize = 64 * 1024;
+    Fd inputFd(std::nullopt, sys);
+
+    {
+        inputFd.reset(sys->open(input.c_str(), O_RDONLY));
+        if (*inputFd < 0)
+        {
+            (void)inputFd.release();
+            std::fprintf(stderr, "Unable to open file: '%s'\n", input.c_str());
+            return false;
+        }
+
+        std::int64_t fileSize = sys->getSize(input.c_str());
+        if (fileSize == 0)
+        {
+            std::fprintf(stderr,
+                         "Zero-length file, or other file access error\n");
+            return false;
+        }
+
+        progress->start(fileSize);
+    }
+
+    Fd connFd(std::nullopt, sys);
+
+    {
+        struct addrinfo hints;
+        std::memset(&hints, 0, sizeof(hints));
+        hints.ai_flags = AI_NUMERICHOST;
+        hints.ai_family = AF_INET;
+        hints.ai_socktype = SOCK_STREAM;
+
+        struct addrinfo *addrs, *addr;
+        int ret = sys->getaddrinfo(host.c_str(), port.c_str(), &hints, &addrs);
+        if (ret < 0)
+        {
+            std::fprintf(stderr, "Couldn't parse address %s with port %s: %s\n",
+                         host.c_str(), port.c_str(), gai_strerror(ret));
+            return false;
+        }
+
+        for (addr = addrs; addr != nullptr; addr = addr->ai_next)
+        {
+            connFd.reset(sys->socket(addr->ai_family, addr->ai_socktype,
+                                     addr->ai_protocol));
+            if (*connFd == -1)
+                continue;
+
+            if (sys->connect(*connFd, addr->ai_addr, addr->ai_addrlen) != -1)
+                break;
+        }
+
+        // TODO: use stdplus Managed for the addrinfo structs
+        sys->freeaddrinfo(addrs);
+
+        if (addr == nullptr)
+        {
+            std::fprintf(stderr, "Failed to connect\n");
+            return false;
+        }
+    }
+
+    try
+    {
+        int bytesSent = 0;
+        off_t offset = 0;
+
+        do
+        {
+            bytesSent = sys->sendfile(*connFd, *inputFd, &offset, blockSize);
+            if (bytesSent < 0)
+            {
+                std::fprintf(stderr, "Failed to send data to BMC: %s\n",
+                             strerror(errno));
+                return false;
+            }
+            else if (bytesSent > 0)
+            {
+                /* Ok, so the data is staged, now send the blob write with
+                 * the details.
+                 */
+                struct ipmi_flash::ExtChunkHdr chunk;
+                chunk.length = bytesSent;
+                std::vector<std::uint8_t> chunkBytes(sizeof(chunk));
+                std::memcpy(chunkBytes.data(), &chunk, sizeof(chunk));
+
+                /* This doesn't return anything on success. */
+                blob->writeBytes(session, offset - bytesSent, chunkBytes);
+                progress->updateProgress(bytesSent);
+            }
+        } while (bytesSent > 0);
+    }
+    catch (const ipmiblob::BlobException& b)
+    {
+        return false;
+    }
+
+    return true;
+}
+
+} // namespace host_tool