tools: implement bt sendcontents

Implement the block transfer (really not blocktransfer only)
sendcontents handler.  This handler sends the file contents within
the IPMI packets themselves.

Note: This is really used for kcs, etc, but it's called bt to avoid
confusion with general ipmi code.

Change-Id: I310034a6afdf0eb25894e658ccee42e6394aa4d2
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/test/Makefile.am b/test/Makefile.am
index 3d51fa4..0b01997 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -28,6 +28,7 @@
 	firmware_commit_unittest \
 	file_handler_unittest \
 	tools_blob_unittest \
+	tools_bt_unittest \
 	tools_updater_unittest \
 	tools_ipmi_unittest
 
@@ -72,6 +73,9 @@
 tools_blob_unittest_SOURCES = tools_blob_unittest.cpp
 tools_blob_unittest_LDADD = $(top_builddir)/tools/blob_handler.o
 
+tools_bt_unittest_SOURCES = tools_bt_unittest.cpp
+tools_bt_unittest_LDADD = $(top_builddir)/tools/bt.o
+
 tools_updater_unittest_SOURCES = tools_updater_unittest.cpp
 tools_updater_unittest_LDADD = $(top_builddir)/tools/updater.o
 
diff --git a/test/tools_bt_unittest.cpp b/test/tools_bt_unittest.cpp
new file mode 100644
index 0000000..24511c3
--- /dev/null
+++ b/test/tools_bt_unittest.cpp
@@ -0,0 +1,48 @@
+#include "blob_interface_mock.hpp"
+#include "bt.hpp"
+#include "internal_sys_mock.hpp"
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+namespace host_tool
+{
+
+using ::testing::_;
+using ::testing::ContainerEq;
+using ::testing::Eq;
+using ::testing::Invoke;
+using ::testing::NotNull;
+using ::testing::Return;
+
+TEST(BtHandlerTest, verifySendsFileContents)
+{
+    /* In this very basic test, we'll feed the bt handler data from the internal
+     * syscall mock and catch the writes via the blob mock.
+     */
+    internal::InternalSysMock sysMock;
+    BlobInterfaceMock blobMock;
+
+    BtDataHandler handler(&blobMock, &sysMock);
+    std::string filePath = "/asdf";
+    int fd = 1;
+    std::uint16_t session = 0xbeef;
+    std::vector<std::uint8_t> bytes = {'1', '2', '3', '4'};
+
+    EXPECT_CALL(sysMock, open(Eq(filePath), _)).WillOnce(Return(fd));
+    EXPECT_CALL(sysMock, read(fd, NotNull(), _))
+        .WillOnce(Invoke([&](int fd, void* buf, std::size_t count) {
+            EXPECT_TRUE(count > bytes.size());
+            std::memcpy(buf, bytes.data(), bytes.size());
+            return bytes.size();
+        }))
+        .WillOnce(Return(0));
+    EXPECT_CALL(sysMock, close(fd)).WillOnce(Return(0));
+
+    EXPECT_CALL(blobMock, writeBytes(session, 0, ContainerEq(bytes)));
+
+    EXPECT_TRUE(handler.sendContents(filePath, session));
+}
+
+} // namespace host_tool
diff --git a/tools/bt.cpp b/tools/bt.cpp
index da582d4..297bc30 100644
--- a/tools/bt.cpp
+++ b/tools/bt.cpp
@@ -1,12 +1,50 @@
 #include "bt.hpp"
 
+#include "blob_errors.hpp"
+
+#include <cstdint>
+#include <vector>
+
 namespace host_tool
 {
 
 bool BtDataHandler::sendContents(const std::string& input,
                                  std::uint16_t session)
 {
-    return false;
+    int inputFd = sys->open(input.c_str(), 0);
+    if (inputFd < 0)
+    {
+        return false;
+    }
+
+    static constexpr int btBufferLen = 50;
+    std::uint8_t readBuffer[btBufferLen];
+    int bytesRead;
+    std::uint32_t offset = 0;
+
+    try
+    {
+        do
+        {
+            bytesRead = sys->read(inputFd, readBuffer, sizeof(readBuffer));
+            if (bytesRead > 0)
+            {
+                /* minorly awkward repackaging. */
+                std::vector<std::uint8_t> buffer(&readBuffer[0],
+                                                 &readBuffer[bytesRead]);
+                blob->writeBytes(session, offset, buffer);
+                offset += bytesRead;
+            }
+        } while (bytesRead > 0);
+    }
+    catch (const BlobException& b)
+    {
+        sys->close(inputFd);
+        return false;
+    }
+
+    sys->close(inputFd);
+    return true;
 }
 
 } // namespace host_tool