flash-ipmi: implement flashDataBlock

Change-Id: Ic932e66ceeb58533f4c4902fbaddb0cb760f1590
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/flash-ipmi.cpp b/flash-ipmi.cpp
index 9dbabac..07b9b9a 100644
--- a/flash-ipmi.cpp
+++ b/flash-ipmi.cpp
@@ -68,7 +68,7 @@
 
 bool FlashUpdate::openEverything()
 {
-    flashFd = std::fopen(tmpPath.c_str(), "w");
+    flashFd = std::fopen(tmpPath.c_str(), "wb");
     if (flashFd == nullptr)
     {
         log<level::INFO>("Unable to open staging path",
@@ -92,9 +92,38 @@
     return openEverything();
 }
 
+bool FlashUpdate::writeBlock(std::FILE* fd, uint32_t offset,
+                             const std::vector<uint8_t>& bytes)
+{
+    /* Seek into position, let's assume fseek won't call if offset matches
+     * position.
+     */
+    if (std::fseek(fd, offset, SEEK_SET))
+    {
+        log<level::ERR>("Unable to seek into file to write bytes.");
+        return false;
+    }
+
+    /* Write the bytes. */
+    auto written = std::fwrite(bytes.data(), 1, bytes.size(), fd);
+
+    if (written != bytes.size())
+    {
+        log<level::ERR>("Unable to write all the bytes requested.");
+        return false;
+    }
+
+    (void)std::fflush(fd);
+    return true;
+}
+
 bool FlashUpdate::flashData(uint32_t offset, const std::vector<uint8_t>& bytes)
 {
-    /* TODO: implement. */
+    if (flashFd)
+    {
+        return writeBlock(flashFd, offset, bytes);
+    }
+
     return false;
 }
 
diff --git a/flash-ipmi.hpp b/flash-ipmi.hpp
index 7b60b05..2e6f6f0 100644
--- a/flash-ipmi.hpp
+++ b/flash-ipmi.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <cstdio>
 #include <string>
 #include <vector>
 
@@ -171,6 +172,17 @@
 
   private:
     /**
+     * Attempt to write the bytes at the offset.
+     *
+     * @param[in] fd - the file stream pointer.
+     * @param[in] offset - the 0-based byte offset into the flash image.
+     * @param[in] bytes - the bytes to write.
+     * @return true on success, false otherwise.
+     */
+    bool writeBlock(std::FILE* fd, uint32_t offset,
+                    const std::vector<uint8_t>& bytes);
+
+    /**
      * Tries to close out everything.
      */
     void closeEverything();
diff --git a/test/Makefile.am b/test/Makefile.am
index 245b251..43040a1 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -20,7 +20,8 @@
 	ipmi_abort_unittest \
 	ipmi_validate_unittest \
 	ipmi_command_unittest \
-	flash_start_unittest
+	flash_start_unittest \
+	flash_flashdata_unittest
 
 TESTS = $(check_PROGRAMS)
 
@@ -56,3 +57,6 @@
 
 flash_start_unittest_SOURCES = flash_start_unittest.cpp
 flash_start_unittest_LDADD = $(top_builddir)/flash-ipmi.o $(SDBUSPLUS_LIBS)
+
+flash_flashdata_unittest_SOURCES = flash_flashdata_unittest.cpp
+flash_flashdata_unittest_LDADD = $(top_builddir)/flash-ipmi.o $(SDBUSPLUS_LIBS)
diff --git a/test/flash_flashdata_unittest.cpp b/test/flash_flashdata_unittest.cpp
new file mode 100644
index 0000000..df561ec
--- /dev/null
+++ b/test/flash_flashdata_unittest.cpp
@@ -0,0 +1,80 @@
+#include "flash-ipmi.hpp"
+
+#include <cstdio>
+#include <cstring>
+#include <gtest/gtest.h>
+#include <string>
+#include <vector>
+
+#define THIRTYTWO_MIB 33554432
+
+class FlashIpmiFlashDataTest : public ::testing::Test
+{
+  protected:
+    FlashIpmiFlashDataTest() = default;
+
+    void SetUp() override
+    {
+        name = std::tmpnam(nullptr);
+    }
+    void TearDown() override
+    {
+        (void)std::remove(name.c_str());
+    }
+
+    std::string name;
+};
+
+TEST_F(FlashIpmiFlashDataTest, CalledOutOfSequenceFails)
+{
+    // Verify that there is sanity checking.
+    std::vector<uint8_t> bytes = {0xaa, 0x55};
+
+    FlashUpdate updater(name);
+    EXPECT_FALSE(updater.flashData(0, bytes));
+
+    // Verify the file doesn't exist.
+    auto file = std::fopen(name.c_str(), "r");
+    EXPECT_FALSE(file);
+}
+
+TEST_F(FlashIpmiFlashDataTest, CalledWithDataSucceeds)
+{
+    // Verify that under normal usage it writes the bytes.
+    std::vector<uint8_t> bytes = {0xaa, 0x55};
+
+    FlashUpdate updater(name);
+    updater.start(THIRTYTWO_MIB);
+    EXPECT_TRUE(updater.flashData(0, bytes));
+
+    auto file = std::fopen(name.c_str(), "r");
+    EXPECT_TRUE(file);
+
+    uint8_t buffer[2];
+    auto read = std::fread(buffer, 1, bytes.size(), file);
+    EXPECT_EQ(read, bytes.size());
+    EXPECT_EQ(0, std::memcmp(buffer, bytes.data(), bytes.size()));
+    std::fclose(file);
+}
+
+TEST_F(FlashIpmiFlashDataTest, CalledNonZeroOffsetSucceeds)
+{
+    // Skipping bytes in POSIX can create a sparse file.  In this case,
+    // let's allow the behavior.  If we'd rather, we can have writeBlock
+    // check that the offset is where we expect.
+
+    std::vector<uint8_t> bytes = {0xaa, 0x55};
+
+    FlashUpdate updater(name);
+    updater.start(THIRTYTWO_MIB);
+    EXPECT_TRUE(updater.flashData(2, bytes));
+
+    auto file = std::fopen(name.c_str(), "r");
+    EXPECT_TRUE(file);
+
+    uint8_t buffer[4];
+    auto read = std::fread(buffer, 1, sizeof(buffer), file);
+    EXPECT_EQ(read, sizeof(buffer));
+    EXPECT_EQ(0, std::memcmp(&buffer[2], bytes.data(), bytes.size()));
+    std::fclose(file);
+}