ipmi: start implementing flashStartTransfer

Change-Id: I1f8b1498d517c0661e98b1ba895e7152f7a9ed8e
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/.gitignore b/.gitignore
index 80f3f36..81cb1b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,4 @@
 *_unittest
 *_unittest.log
 *_unittest.trs
+test-suite.log
diff --git a/Makefile.am b/Makefile.am
index 05f493e..0b76d38 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,7 +2,9 @@
 
 libflashcmdsdir = ${libdir}/ipmid-providers
 libflashcmds_LTLIBRARIES = libflashcmds.la
-libflashcmds_la_SOURCES = main.cpp
+libflashcmds_la_SOURCES = main.cpp \
+			  flash-ipmi.cpp \
+			  ipmi.cpp
 
 libflashcmds_la_LDFLAGS = $(SYSTEMD_LIBS) \
                           $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
@@ -12,4 +14,4 @@
                            $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
                            $(PHOSPHOR_LOGGING_CFLAGS)
 
-SUBDIRS = .
+SUBDIRS = . test
diff --git a/configure.ac b/configure.ac
index 0add2b8..0fe7382 100644
--- a/configure.ac
+++ b/configure.ac
@@ -69,5 +69,5 @@
 )
 
 # Create configured output
-AC_CONFIG_FILES([Makefile])
+AC_CONFIG_FILES([Makefile test/Makefile])
 AC_OUTPUT
diff --git a/flash-ipmi.cpp b/flash-ipmi.cpp
new file mode 100644
index 0000000..4d05044
--- /dev/null
+++ b/flash-ipmi.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 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 "flash-ipmi.hpp"
+
+void FlashUpdate::abortEverything()
+{
+    return;
+}
+
+bool FlashUpdate::openEverything()
+{
+    return true;
+}
+
+/* Prepare to receive a BMC image and then a signature. */
+bool FlashUpdate::start(uint32_t)
+{
+    /* TODO: Validate request->length */
+
+    /* Close out and delete everything. */
+    abortEverything();
+
+    /* Start over! */
+    return openEverything();
+}
diff --git a/flash-ipmi.hpp b/flash-ipmi.hpp
index 43cafe1..deef582 100644
--- a/flash-ipmi.hpp
+++ b/flash-ipmi.hpp
@@ -1,5 +1,10 @@
 #pragma once
 
+#include "host-ipmid/ipmid-api.h"
+
+/* Clearer way to represent the subcommand size. */
+#define SUBCMD_SZ sizeof(uint8_t)
+
 /*
  * flashStartTransfer -- starts file upload.
  * flashDataBlock -- adds data to image file.
@@ -55,3 +60,52 @@
     flashHashExtData = 12,
     flashMapRegionLpc = 13,
 };
+
+/*
+ * StartTransfer expects a basic structure providing some information.
+ */
+struct StartTx
+{
+    uint8_t cmd;
+    uint32_t length; /* Maximum image length is 4GiB (little-endian) */
+} __attribute__((packed));
+
+class UpdateInterface
+{
+  public:
+    virtual ~UpdateInterface() = default;
+
+    virtual bool start(uint32_t length) = 0;
+};
+
+class FlashUpdate : public UpdateInterface
+{
+  public:
+    FlashUpdate() = default;
+    ~FlashUpdate() = default;
+    FlashUpdate(const FlashUpdate&) = default;
+    FlashUpdate& operator=(const FlashUpdate&) = default;
+    FlashUpdate(FlashUpdate&&) = default;
+    FlashUpdate& operator=(FlashUpdate&&) = default;
+
+    /**
+     * Prepare to receive a BMC image and then a signature.
+     *
+     * @param[in] length - the size of the flash image.
+     * @return true on success, false otherwise.
+     */
+    bool start(uint32_t length) override;
+
+  private:
+    /**
+     * Tries to close out and delete anything staged.
+     */
+    void abortEverything();
+
+    /**
+     * Open all staged file handles you expect to use.
+     *
+     * @return false on failure.
+     */
+    bool openEverything();
+};
diff --git a/ipmi.cpp b/ipmi.cpp
new file mode 100644
index 0000000..65a719a
--- /dev/null
+++ b/ipmi.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017 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 "flash-ipmi.hpp"
+#include "ipmi.hpp"
+
+ipmi_ret_t startTransfer(UpdateInterface* updater, const uint8_t* reqBuf,
+                         uint8_t* replyBuf, size_t* dataLen)
+{
+    /* Validate the request buffer. */
+    if (sizeof(struct StartTx) > (*dataLen))
+    {
+        return IPMI_CC_INVALID;
+    }
+
+    auto request = reinterpret_cast<const struct StartTx*>(reqBuf);
+
+    if (!updater->start(request->length))
+    {
+        return IPMI_CC_INVALID;
+    }
+
+    /* We were successful and set the response byte to 0. */
+    replyBuf[0] = 0x00;
+    (*dataLen) = 1;
+    return IPMI_CC_OK;
+}
diff --git a/ipmi.hpp b/ipmi.hpp
new file mode 100644
index 0000000..3b622b0
--- /dev/null
+++ b/ipmi.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "host-ipmid/ipmid-api.h"
+
+#include "flash-ipmi.hpp"
+
+/**
+ * Prepare to receive a BMC image and then a signature.
+ *
+ * @param[in] updater - Pointer to Updater object.
+ * @param[in] reqBuf - the IPMI packet.
+ * @param[in] replyBuf - Pointer to buffer for any response.
+ * @param[in,out] dataLen - Initially reqBuf length, set to replyBuf
+ * length when done.
+ * @return corresponding IPMI return code.
+ */
+ipmi_ret_t startTransfer(UpdateInterface* updater, const uint8_t* reqBuf,
+                         uint8_t* replyBuf, size_t* dataLen);
diff --git a/main.cpp b/main.cpp
index 8a6bac1..a3288ef 100644
--- a/main.cpp
+++ b/main.cpp
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
+#include <memory>
+
 #include "host-ipmid/ipmid-api.h"
 #include "host-ipmid/oemrouter.hpp"
 
 #include "flash-ipmi.hpp"
+#include "ipmi.hpp"
 
 /* TODO: Once OEM IPMI number placement is settled, point to that. */
 namespace oem
@@ -29,6 +32,9 @@
 } // namespace google
 } // namespace oem
 
+/* We have one instance of the FlashUpdate object tracking commands. */
+std::unique_ptr<FlashUpdate> flashUpdateSingleton;
+
 static ipmi_ret_t flashControl(ipmi_cmd_t cmd, const uint8_t* reqBuf,
                                uint8_t* replyCmdBuf, size_t* dataLen)
 {
@@ -38,6 +44,20 @@
         return IPMI_CC_INVALID;
     }
 
+    uint8_t subCmd = reqBuf[0];
+
+    /* TODO: This could be cleaner to just have a function pointer table, may
+     * transition in later patchset.
+     */
+    switch (subCmd)
+    {
+        case FlashSubCmds::flashStartTransfer:
+            return startTransfer(flashUpdateSingleton.get(), reqBuf,
+                                 replyCmdBuf, dataLen);
+        default:
+            return IPMI_CC_INVALID;
+    }
+
     return IPMI_CC_INVALID;
 }
 
@@ -58,6 +78,8 @@
 
 void setupGlobalOemFlashControl()
 {
+    flashUpdateSingleton = std::make_unique<FlashUpdate>();
+
 #ifdef ENABLE_GOOGLE
     oem::Router* router = oem::mutableRouter();
 
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..7da78d8
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,18 @@
+AM_CPPFLAGS = -I$(top_srcdir)/ \
+	$(GTEST_CFLAGS) \
+	$(GMOCK_CFLAGS)
+AM_CXXFLAGS = \
+	$(GTEST_MAIN_CFLAGS)
+AM_LDFLAGS = \
+	$(GMOCK_LIBS) \
+	$(GTEST_MAIN_LIBS) \
+	$(OESDK_TESTCASE_FLAGS)
+
+# Run all 'check' test programs
+check_PROGRAMS = \
+	ipmi_starttransfer_unittest
+
+TESTS = $(check_PROGRAMS)
+
+ipmi_starttransfer_unittest_SOURCES = ipmi_starttransfer_unittest.cpp
+ipmi_starttransfer_unittest_LDADD = $(top_builddir)/ipmi.o
diff --git a/test/ipmi_starttransfer_unittest.cpp b/test/ipmi_starttransfer_unittest.cpp
new file mode 100644
index 0000000..448287b
--- /dev/null
+++ b/test/ipmi_starttransfer_unittest.cpp
@@ -0,0 +1,61 @@
+#include "flash-ipmi.hpp"
+#include "ipmi.hpp"
+
+#include "updater_mock.hpp"
+
+#include <cstring>
+#include <gtest/gtest.h>
+
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::StrictMock;
+
+// ipmid.hpp isn't installed where we can grab it and this value is per BMC
+// SoC.
+#define MAX_IPMI_BUFFER 64
+#define THIRTYTWO_MIB 33554432
+
+TEST(IpmiStartTransferTest, InvalidRequestLengthReturnsFailure)
+{
+    // Verify that the request is sanity checked w.r.t length.
+
+    StrictMock<UpdaterMock> updater;
+
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+    struct StartTx tx;
+    tx.cmd = FlashSubCmds::flashStartTransfer;
+    tx.length = THIRTYTWO_MIB;
+    std::memcpy(request, &tx, sizeof(tx));
+
+    dataLen = sizeof(tx) - 1; // It's too small to be a valid packet.
+
+    EXPECT_EQ(IPMI_CC_INVALID,
+              startTransfer(&updater, request, reply, &dataLen));
+}
+
+TEST(IpmiStartTransferTest, ValidRequestBoringCase)
+{
+    // Verify that if the request is valid it calls into the flash updater.
+
+    StrictMock<UpdaterMock> updater;
+
+    size_t dataLen;
+    uint8_t request[MAX_IPMI_BUFFER] = {0};
+    uint8_t reply[MAX_IPMI_BUFFER] = {0};
+
+    struct StartTx tx;
+    tx.cmd = FlashSubCmds::flashStartTransfer;
+    tx.length = THIRTYTWO_MIB;
+    std::memcpy(request, &tx, sizeof(tx));
+
+    dataLen = sizeof(tx);
+
+    EXPECT_CALL(updater, start(THIRTYTWO_MIB)).WillOnce(Return(true));
+
+    EXPECT_EQ(IPMI_CC_OK, startTransfer(&updater, request, reply, &dataLen));
+    EXPECT_EQ(sizeof(uint8_t), dataLen);
+    EXPECT_EQ(0, reply[0]);
+}
diff --git a/test/updater_mock.hpp b/test/updater_mock.hpp
new file mode 100644
index 0000000..3c2b5ab
--- /dev/null
+++ b/test/updater_mock.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "flash-ipmi.hpp"
+
+class UpdaterMock : public UpdateInterface
+{
+  public:
+    virtual ~UpdaterMock() = default;
+
+    MOCK_METHOD1(start, bool(uint32_t));
+};