static_handler: implement basic version

Implement basic version that simply caches the information written to
it.  Later, error conditions will need addressing.

Change-Id: Ife27b72a6a685e500a0a16c55975c9f4469b206a
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/static_handler.cpp b/static_handler.cpp
index c4fa602..616a882 100644
--- a/static_handler.cpp
+++ b/static_handler.cpp
@@ -11,18 +11,65 @@
 bool StaticLayoutHandler::open(const std::string& path)
 {
     this->path = path;
-    return false;
+
+    if (stagedOutput.is_open())
+    {
+        /* This wasn't properly closed somehow.
+         * TODO: Throw an error or just reset the state?
+         */
+        return false;
+    }
+
+    /* using ofstream no need to set out */
+    stagedOutput.open(stagedFilename, std::ios::binary);
+    if (stagedOutput.bad())
+    {
+        /* TODO: Oh no! Care about this. */
+        return false;
+    }
+
+    /* We were able to open the file for staging.
+     * TODO: We'll need to do other stuff to eventually.
+     */
+    return true;
 }
 
 void StaticLayoutHandler::close()
 {
+    if (stagedOutput.is_open())
+    {
+        stagedOutput.close();
+    }
     return;
 }
 
 bool StaticLayoutHandler::write(std::uint32_t offset,
                                 const std::vector<std::uint8_t>& data)
 {
-    return false;
+    if (!stagedOutput.is_open())
+    {
+        return false;
+    }
+
+    /* We could track this, but if they write in a scattered method, this is
+     * easier.
+     */
+    stagedOutput.seekp(offset, std::ios_base::beg);
+    if (!stagedOutput.good())
+    {
+        /* the documentation wasn't super clear on fail vs bad in these cases,
+         * so let's only be happy with goodness.
+         */
+        return false;
+    }
+
+    stagedOutput.write(reinterpret_cast<const char*>(data.data()), data.size());
+    if (!stagedOutput.good())
+    {
+        return false;
+    }
+
+    return true;
 }
 
 } // namespace blobs
diff --git a/static_handler.hpp b/static_handler.hpp
index 0126315..35d40fb 100644
--- a/static_handler.hpp
+++ b/static_handler.hpp
@@ -3,6 +3,7 @@
 #include "image_handler.hpp"
 
 #include <cstdint>
+#include <fstream>
 #include <memory>
 #include <string>
 #include <vector>
@@ -14,11 +15,19 @@
 {
   public:
     /**
-     * Create a StaticLayoutHandler.
+     * Create a StaticLayoutHandler.  This appears to be a basic file writer, if
+     * it doesn't end up doing more, then it will be converted into such.
+     *
+     * @param[in] temporaryName - where to write the firmware image while it's
+     * being uploaded.
      */
     explicit StaticLayoutHandler(const std::string& temporaryName) :
         stagedFilename(temporaryName){};
 
+    /* Destroying the ofstream closes it first. */
+    ~StaticLayoutHandler() = default;
+
+    /* Opens the stagedFilename. */
     bool open(const std::string& path) override;
     void close() override;
     bool write(std::uint32_t offset,
@@ -27,6 +36,9 @@
   private:
     std::string path;
 
+    /** The staged output file stream object. */
+    std::ofstream stagedOutput;
+
     /** The file to use for staging the bytes. */
     std::string stagedFilename;
 };
diff --git a/test/Makefile.am b/test/Makefile.am
index 4144e4b..f5bf4de 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -17,7 +17,8 @@
 	firmware_write_unittest \
 	firmware_writemeta_unittest \
 	firmware_close_unittest \
-	firmware_delete_unittest
+	firmware_delete_unittest \
+	static_handler_unittest
 
 TESTS = $(check_PROGRAMS)
 
@@ -44,3 +45,6 @@
 
 firmware_delete_unittest_SOURCES = firmware_delete_unittest.cpp
 firmware_delete_unittest_LDADD = $(top_builddir)/firmware_handler.o
+
+static_handler_unittest_SOURCES = static_handler_unittest.cpp
+static_handler_unittest_LDADD = $(top_builddir)/static_handler.o
diff --git a/test/static_handler_unittest.cpp b/test/static_handler_unittest.cpp
new file mode 100644
index 0000000..4fda33d
--- /dev/null
+++ b/test/static_handler_unittest.cpp
@@ -0,0 +1,58 @@
+#include "static_handler.hpp"
+
+#include <cstdint>
+#include <cstdio>
+#include <fstream>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+static constexpr auto TESTPATH = "test.output";
+
+class StaticHandlerOpenTest : public ::testing::Test
+{
+  protected:
+    void TearDown() override
+    {
+        (void)std::remove(TESTPATH);
+    }
+};
+
+TEST_F(StaticHandlerOpenTest, VerifyItIsHappy)
+{
+    /* Opening a fail may create it? */
+
+    StaticLayoutHandler handler(TESTPATH);
+    EXPECT_TRUE(handler.open(""));
+
+    /* Calling open twice fails the second time. */
+    EXPECT_FALSE(handler.open(""));
+}
+
+TEST_F(StaticHandlerOpenTest, VerifyWriteDataWrites)
+{
+    /* Verify writing bytes writes them... flushing data can be an issue here,
+     * so we close first.
+     */
+    StaticLayoutHandler handler(TESTPATH);
+    EXPECT_TRUE(handler.open(""));
+
+    std::vector<std::uint8_t> bytes = {0x01, 0x02};
+    std::uint32_t offset = 0;
+
+    EXPECT_TRUE(handler.write(offset, bytes));
+    handler.close();
+
+    std::ifstream data;
+    data.open(TESTPATH, std::ios::binary);
+    char expectedBytes[2];
+    data.read(&expectedBytes[0], sizeof(expectedBytes));
+    EXPECT_EQ(expectedBytes[0], bytes[0]);
+    EXPECT_EQ(expectedBytes[1], bytes[1]);
+    /* annoyingly the memcmp was failing... but it's the same data. */
+}
+
+} // namespace blobs