hermes: card_qrtr: Unit test framework added

This change introduces a unit test framework for the CardQrtr
implementation. The framework allows tests to fake sending raw data
from the modem to the host with:
   ModemReceiveData(raw_data);

The framework also allows tests to set up expectations for data that the
CardQrtr implementation will send to the modem, with the use of:
   EXPECT_SEND(data, len);

BUG=chromium:847619
TEST=`cros_workon_make --board cheza hermes --test` passes.

Change-Id: Ibbc0d79525eb22d8067073d00802bee0f07222e6
Reviewed-on: https://chromium-review.googlesource.com/1368987
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Tested-by: Alex Khouderchah <akhouderchah@chromium.org>
Reviewed-by: Eric Caruso <ejcaruso@chromium.org>
diff --git a/hermes/BUILD.gn b/hermes/BUILD.gn
index cdc5bd5..04f65a7 100644
--- a/hermes/BUILD.gn
+++ b/hermes/BUILD.gn
@@ -65,6 +65,7 @@
     ]
     sources = [
       "apdu_test.cc",
+      "card_qrtr_test.cc",
     ]
     deps = [
       ":libhermes",
diff --git a/hermes/card_qrtr.h b/hermes/card_qrtr.h
index f13a8f2..0279b4c 100644
--- a/hermes/card_qrtr.h
+++ b/hermes/card_qrtr.h
@@ -96,6 +96,8 @@
   lpa::util::Executor* executor() override { return executor_; }
 
  private:
+  friend class CardQrtrTest;
+
   ///////////////////
   // State Diagram //
   ///////////////////
diff --git a/hermes/card_qrtr_test.cc b/hermes/card_qrtr_test.cc
new file mode 100644
index 0000000..c4461e5
--- /dev/null
+++ b/hermes/card_qrtr_test.cc
@@ -0,0 +1,330 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "hermes/card_qrtr.h"
+
+#include <memory>
+#include <utility>
+
+#include <base/bind.h>
+#include <base/files/scoped_file.h>
+#include <base/strings/string_number_conversions.h>
+#include <fcntl.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "hermes/apdu.h"
+#include "hermes/sgp_22.h"
+#include "hermes/socket_qrtr.h"
+#include "hermes/type_traits.h"
+#include "hermes/utils.h"
+
+//
+// General testing structure
+// -------------------------
+// The CardQrtr implementation sends and receives data from a qrtr socket, whose
+// other end is a modem. In order to fake communication with the modem, the
+// qrtr socket is replaced with a regular file descriptor, with the modem itself
+// being faked by the CardQrtrTest testing framework.
+//
+// For each TEST_F(CardQrtrTest, ...) test, sending data from modem -> CardQrtr
+// can be faked with CardQrtrTest::CardReceiveData(...). The CardQrtr -> modem
+// messages are obviously not faked, as it is what we are testing, but
+// CardQrtr::SendApdus is now wrapped by CardQrtrTest::SendApdus. The
+// EXPECT_SEND macro is used to verify that the sent data is as we expected.  In
+// both cases, the transaction IDs of provided data is ignored, and the proper
+// transaction ID values from the calls made to CardQrtr::AllocateIds are used
+// instead. This means that tests will not break if the implementation of
+// AllocateIds is changed.
+//
+
+using ::testing::_;
+using ::testing::ElementsAreArray;
+using ::testing::Invoke;
+using ::testing::WithArgs;
+
+namespace {
+
+constexpr uint32_t kTestNode = 0;
+constexpr uint32_t kTestPort = 59;
+const char* kQrtrFilename = "/tmp/hermes_qrtr_test";
+
+constexpr auto kQrtrNewServerResp = hermes::make_array<uint8_t>(
+  0x04, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00
+);
+
+constexpr auto kQrtrResetReq = hermes::make_array<uint8_t>(
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+);
+
+constexpr auto kQrtrResetResp = hermes::make_array<uint8_t>(
+  0x02, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00,
+  0x00
+);
+
+constexpr auto kQrtrOpenLogicalChannelReq = hermes::make_array<uint8_t>(
+  0x00, 0x00, 0x00, 0x42, 0x00, 0x18, 0x00, 0x01, 0x01, 0x00, 0x01, 0x10, 0x11,
+  0x00, 0x10, 0xA0, 0x00, 0x00, 0x05, 0x59, 0x10, 0x10, 0xFF, 0xFF, 0xFF, 0xFF,
+  0x89, 0x00, 0x00, 0x01, 0x00
+);
+
+constexpr auto kQrtrOpenLogicalChannelResp = hermes::make_array<uint8_t>(
+  0x02, 0x00, 0x00, 0x42, 0x00, 0x35, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x12, 0x22, 0x00, 0x21, 0x6F, 0x1F, 0x84, 0x10, 0xA0, 0x00, 0x00, 0x05,
+  0x59, 0x10, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0x89, 0x00, 0x00, 0x01, 0x00, 0xA5,
+  0x04, 0x9F, 0x65, 0x01, 0xFF, 0xE0, 0x05, 0x82, 0x03, 0x02, 0x00, 0x00, 0x11,
+  0x02, 0x00, 0x90, 0x00, 0x10, 0x01, 0x00, 0x01
+);
+
+constexpr auto kApduPrefix = hermes::make_array<uint8_t>(
+  0x00, 0x00, 0x00, 0x3B, 0x00, 0x13, 0x00, 0x01, 0x01, 0x00, 0x01, 0x02, 0x08,
+  0x00, 0x06, 0x00, 0x80, 0xE2, 0x91, 0x00, 0x00
+);
+
+constexpr auto kApduSuffix = hermes::make_array<uint8_t>(
+  0x10, 0x01, 0x00, 0x01
+);
+
+constexpr auto kGetChallengeApdu = hermes::make_array<uint8_t>(
+  0xBF, 0x2E, 0x00
+);
+
+constexpr auto kGetChallengeResp = hermes::make_array<uint8_t>(
+  0x02, 0x00, 0x00, 0x3B, 0x00, 0x23, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x10, 0x19, 0x00, 0x17, 0x00, 0xBF, 0x2E, 0x12, 0x80, 0x10, 0x5A, 0x6C,
+  0x23, 0x71, 0x94, 0xBE, 0xAB, 0x24, 0xF4, 0xEF, 0xAB, 0x54, 0xB7, 0x3A, 0x59,
+  0xCF, 0x90, 0x00
+);
+
+void NullResponseCallback(std::vector<std::vector<uint8_t>>& responses, int err) {}
+
+// Create a full QRTR packet given the data of an APDU message. The current
+// implementation only works for non-fragmented APDUs.
+template <typename Iterator>
+hermes::EnableIfIterator_t<Iterator, std::vector<uint8_t>> CreateQrtrFromApdu(
+    Iterator first, Iterator last) {
+  std::vector<uint8_t> result;
+  result.insert(result.end(), kApduPrefix.begin(), kApduPrefix.end());
+  result.insert(result.end(), first, last);
+  result.insert(result.end(), kApduSuffix.begin(), kApduSuffix.end());
+  result[5] = result.size() - 7;
+  result[12] = result.size() - 18;
+  result[14] = std::distance(first, last) + 5;
+  result[20] = std::distance(first, last);
+  return result;
+}
+
+}  // namespace
+
+// Expect CardQrtr instance to send the provided vector of data to the modem.
+// This macro should only be called within a CardQrtrTest.
+//
+// Note that the transaction ID in the provided vector will be ignored and the
+// earliest unused ID from CardQrtr::AllocateId will be used instead. This
+// allows for changes in message ordering to not invalidate the data passed
+// to this macro.
+#define EXPECT_SEND(socket_obj, data)                                         \
+  EXPECT_CALL(socket_obj, Send(_, data.size(), _))                            \
+    .Times(1)                                                                 \
+    .WillOnce(WithArgs<0, 1>(Invoke(                                          \
+      [this, d = data](const void* arr, size_t l) {                           \
+        auto expected = d;                                                    \
+        expected[1] = ((uint8_t*)arr)[1];                                     \
+        this->receive_ids_.push_back(expected[1]);                            \
+        EXPECT_THAT(expected, ElementsAreArray((uint8_t*)arr, l));            \
+        return 0;                                                             \
+      })))
+
+namespace hermes {
+
+// Socket class which mocks the outgoing (host -> modem) socket calls and
+// provides implementations for incoming (modem -> host) socket calls that reads
+// data from kQrtrFilename rather than from an actual QRTR socket.
+class MockSocketQrtr : public SocketInterface {
+ public:
+  void SetDataAvailableCallback(DataAvailableCallback cb) override {
+    cb_ = cb;
+  }
+
+  bool Open() override {
+    socket_ = base::ScopedFD(open(kQrtrFilename, O_RDWR));
+    if (!socket_.is_valid()) {
+      return false;
+    }
+    int off = lseek(socket_.get(), 0, SEEK_SET);
+    EXPECT_EQ(off, 0);
+    // Return without setting up a MessageLoop::WatchFileDescriptor. The epoll
+    // syscall does not always support regular file descriptors. Libevent could
+    // be configured not to use epoll, but this would require modifying or
+    // substituting base::MessagePumpLibevent. Instead, CardQrtrTest will
+    // manually call the DataAvailableCallback as needed.
+    return true;
+  }
+
+  bool IsValid() const override { return socket_.is_valid(); }
+  Type GetType() const override { return Type::kQrtr; }
+
+  int Recv(void* buf, size_t size, void* metadata) override {
+    int bytes_read = read(socket_.get(), buf, size);
+    EXPECT_EQ(bytes_read, size);
+    LOG(INFO) << "Mock CardQrtr receiving data (" << size
+              << " bytes): " << base::HexEncode(buf, size);
+
+    if (metadata) {
+      auto data = reinterpret_cast<SocketQrtr::PacketMetadata*>(metadata);
+      data->node = kTestNode;
+      if (size && *static_cast<uint8_t*>(buf) == QRTR_TYPE_NEW_SERVER) {
+        data->port = QRTR_PORT_CTRL;
+      } else {
+        data->port = kTestPort;
+      }
+    }
+    return bytes_read;
+  }
+
+  MOCK_METHOD0(Close, void());
+  MOCK_METHOD3(StartService, bool(uint32_t, uint16_t, uint16_t));
+  MOCK_METHOD3(StopService, bool(uint32_t, uint16_t, uint16_t));
+  MOCK_METHOD3(Send, int(const void*, size_t, const void*));
+
+ private:
+  friend class CardQrtrTest;
+
+  base::ScopedFD socket_;
+  DataAvailableCallback cb_;
+};
+
+// Test framework for CardQrtr tests. Allows for the faking of modem -> cpu
+// responses with the use of CardReceiveData.
+class CardQrtrTest : public testing::Test {
+ public:
+  CardQrtrTest() {
+    auto socket = std::make_unique<MockSocketQrtr>();
+    socket_ = socket.get();
+    card_ = CardQrtr::Create(std::move(socket), nullptr, nullptr);
+  }
+
+ protected:
+  // Fake modem initialization such that tests may jump right to sending QMI
+  // commands
+  void SetUp() override {
+    fd_.reset(open(kQrtrFilename, O_RDWR | O_CREAT | O_TRUNC, 0777));
+    ASSERT_TRUE(fd_.is_valid());
+
+    receive_ids_.clear();
+  }
+
+  void TearDown() override {
+    EXPECT_CALL(*socket_, Close());
+    card_.reset(nullptr);
+    fd_.reset();
+  }
+
+  // Wrapper for CardQrtr::SendApdus. Tests should use this rather than
+  // CardQrtr::SendApdus.
+  void SendApdus(std::vector<lpa::card::Apdu> commands,
+                 CardQrtr::ResponseCallback cb) {
+    EXPECT_CALL(*socket_, StartService(_, _, _))
+      // Add a receive transaction id when new_lookup is called.
+      .WillOnce(WithoutArgs(Invoke([this](){
+        this->receive_ids_.push_back(0);
+        return true; })));
+
+    {
+      ::testing::InSequence dummy;
+
+      // Expect RESET and OPEN_LOGICAL_CHANNEL request after receiving
+      // NEW_SERVER.
+      EXPECT_SEND(*socket_, kQrtrResetReq);
+      EXPECT_SEND(*socket_, kQrtrOpenLogicalChannelReq);
+    }
+
+    card_->SendApdus(std::move(commands), std::move(cb));
+    SimulateInitialization();
+
+    EXPECT_CALL(*socket_, StopService(_, _, _));
+  }
+
+  // Cause |card_| to receive the provided data.
+  template <typename Iterator>
+  EnableIfIterator_t<Iterator, void> CardReceiveData(
+      Iterator first, Iterator last) {
+    std::vector<uint8_t> receive_data(first, last);
+    receive_data[1] = receive_ids_[0];
+    receive_ids_.pop_front();
+
+    int ret = write(fd_.get(), receive_data.data(), receive_data.size());
+    EXPECT_EQ(ret, receive_data.size());
+    // Set card buffer size so that the proper amount of data is read from fd.
+    card_->buffer_.resize(receive_data.size());
+    socket_->cb_.Run(card_->socket_.get());
+  }
+
+  void SimulateInitialization() {
+    // Receive NEW_SERVER response from sock_new_lookup
+    CardReceiveData(kQrtrNewServerResp.begin(), kQrtrNewServerResp.end());
+    // Receive RESET response from RESET request
+    CardReceiveData(kQrtrResetResp.begin(), kQrtrResetResp.end());
+    // Receive repsonse to OPEN_LOGICAL_CHANNEL request
+    CardReceiveData(kQrtrOpenLogicalChannelResp.begin(),
+                    kQrtrOpenLogicalChannelResp.end());
+  }
+
+  base::ScopedFD fd_;
+  // Queue of transaction ids created by the CardQrtr instance in question. This
+  // is used such that AllocateId implementations may change without breaking
+  // the unit tests (which should not be affected by changes in id allocation
+  // strategy).  Send ids are ids to use when sending commands. Likewise for
+  // receive ids.
+  std::deque<uint16_t> receive_ids_;
+  MockSocketQrtr* socket_;
+  std::unique_ptr<CardQrtr> card_;
+};
+
+///////////
+// TESTS //
+///////////
+
+TEST_F(CardQrtrTest, EmptyApdu) {
+  auto v = std::vector<uint8_t>();
+  EXPECT_SEND(*socket_, CreateQrtrFromApdu(v.begin(), v.end()));
+  std::vector<lpa::card::Apdu> commands = {lpa::card::Apdu::NewStoreData({})};
+  SendApdus(std::move(commands), NullResponseCallback);
+}
+
+TEST_F(CardQrtrTest, RequestGetEid) {
+  EXPECT_SEND(*socket_, CreateQrtrFromApdu(kGetChallengeApdu.begin(),
+                                           kGetChallengeApdu.end()));
+  std::vector<lpa::card::Apdu> commands = {lpa::card::Apdu::NewStoreData(
+      std::vector<uint8_t>(kGetChallengeApdu.begin(),
+                           kGetChallengeApdu.end()))};
+  SendApdus(std::move(commands), NullResponseCallback);
+}
+
+TEST_F(CardQrtrTest, SendTwoApdus) {
+  auto v = std::vector<uint8_t>();
+  {
+    ::testing::InSequence dummy;
+
+    EXPECT_SEND(*socket_, CreateQrtrFromApdu(kGetChallengeApdu.begin(),
+                                             kGetChallengeApdu.end()));
+    // Do not expect to reinitialize the modem in between APDUs.
+    EXPECT_SEND(*socket_, CreateQrtrFromApdu(v.begin(), v.end()));
+  }
+
+  std::vector<lpa::card::Apdu> commands = {
+    lpa::card::Apdu::NewStoreData(
+      std::vector<uint8_t>(kGetChallengeApdu.begin(),
+                           kGetChallengeApdu.end())),
+      lpa::card::Apdu::NewStoreData({})};
+  SendApdus(std::move(commands), NullResponseCallback);
+  CardReceiveData(kGetChallengeResp.begin(),
+                  kGetChallengeResp.end());
+}
+
+}  // namespace hermes
diff --git a/hermes/type_traits.h b/hermes/type_traits.h
new file mode 100644
index 0000000..4dc7c4f
--- /dev/null
+++ b/hermes/type_traits.h
@@ -0,0 +1,52 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HERMES_TYPE_TRAITS_H_
+#define HERMES_TYPE_TRAITS_H_
+
+#include <type_traits>
+#include <vector>
+
+namespace hermes_internal {
+
+template<typename... Ts> struct make_void { typedef void type; };
+template<typename... Ts> using void_t = typename make_void<Ts...>::type;
+
+template <typename T>
+struct is_array : std::false_type {};
+
+template <typename T, std::size_t N>
+struct is_array<std::array<T, N>> : std::true_type {};
+
+template <typename T>
+struct is_vector : std::false_type {};
+
+template <typename T, typename Allocator>
+struct is_vector<std::vector<T, Allocator>> : std::true_type {};
+
+template <typename, typename = void>
+struct is_iterator : std::false_type {};
+
+template <typename T>
+struct is_iterator<T, void_t<
+    typename std::iterator_traits<T>::iterator_category>> : std::true_type {};
+
+}  // namespace hermes_internal
+
+namespace hermes {
+
+template <typename T, typename ReturnType>
+using EnableIfArrayOrVector_t =
+    std::enable_if_t<hermes_internal::is_vector<T>::value ||
+                     hermes_internal::is_array<T>::value,
+                     ReturnType>;
+
+template <typename T, typename ReturnType>
+using EnableIfIterator_t =
+    std::enable_if_t<hermes_internal::is_iterator<T>::value,
+                     ReturnType>;
+
+}  // namespace hermes
+
+#endif  // HERMES_TYPE_TRAITS_H_
diff --git a/hermes/utils.h b/hermes/utils.h
new file mode 100644
index 0000000..9b3347d
--- /dev/null
+++ b/hermes/utils.h
@@ -0,0 +1,25 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HERMES_UTILS_H_
+#define HERMES_UTILS_H_
+
+#include <utility>
+
+namespace hermes {
+
+// Create a std::array from a set of values without manually specifying the
+// size of the array. Note that unlike the make_array likely to make its way
+// into C++20, this function always requires the user to specify ElementType.
+// This is done so that users are not surprised by the element type of resulting
+// arrays when std::common_type is used.
+template <typename ElementType, typename... T>
+constexpr auto make_array(T&&... values) {
+  return std::array<ElementType, sizeof...(T)>{
+    static_cast<ElementType>(std::forward<T>(values))...};
+}
+
+}  // namespace hermes
+
+#endif  // HERMES_UTILS_H_