/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * 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 <vector>
#include <gtest/gtest.h>

#include <ese/ese.h>
#include <ese/app/boot.h>
#include "../boot_private.h"

#include "ese_operations_interface.h"
#include "ese_operations_wrapper.h"

using ::testing::Test;

class FakeTransceive : public EseOperationsInterface {
 public:
  FakeTransceive() { }
  virtual ~FakeTransceive() { }

  virtual int EseOpen(struct EseInterface *ese, void *data) {
    return 0;
  }
  virtual uint32_t EseHwReceive(struct EseInterface *ese, uint8_t *data,
                                uint32_t len, int complete) { return -1; }
  virtual uint32_t EseHwTransmit(struct EseInterface *ese, const uint8_t *data,
                                 uint32_t len, int complete) { return -1; }
  virtual int EseReset(struct EseInterface *ese) { return -1; }
  virtual int EsePoll(struct EseInterface *ese, uint8_t poll_for, float timeout, int complete) {
    return -1;
  }
  virtual void EseClose(struct EseInterface *ese) { }

  virtual uint32_t EseTransceive(struct EseInterface *ese, const struct EseSgBuffer *tx_sg, uint32_t tx_nsg,
                                 struct EseSgBuffer *rx_sg, uint32_t rx_nsg) {
    // Get this calls expected data.
    EXPECT_NE(0UL, invocations.size());
    if (!invocations.size())
      return 0;
    const struct Invocation &invocation = invocations.at(0);

    uint32_t tx_total = ese_sg_length(tx_sg, tx_nsg);
    EXPECT_EQ(invocation.expected_tx.size(), tx_total);
    std::vector<uint8_t> incoming(tx_total);
    ese_sg_to_buf(tx_sg, tx_nsg, 0, tx_total, incoming.data());
    EXPECT_EQ(0, memcmp(incoming.data(), invocation.expected_tx.data(), tx_total));

    // Supply the golden return data and pop off the invocation.
    ese_sg_from_buf(rx_sg, rx_nsg, 0, invocation.rx.size(), invocation.rx.data());
    uint32_t rx_total = invocation.rx.size();
    invocations.erase(invocations.begin());
    return rx_total;
  }

  struct Invocation {
    std::vector<uint8_t> rx;
    std::vector<uint8_t> expected_tx;
  };

  std::vector<Invocation> invocations;
};

class BootAppTest : public virtual Test {
 public:
  BootAppTest() { }
  virtual ~BootAppTest() { }

  void SetUp() {
    // Configure ese with our internal ops.
    EseOperationsWrapper::InitializeEse(&ese_, &trans_);
  }

  void TearDown() {
    trans_.invocations.resize(0);
  }

 protected:
  FakeTransceive trans_;
  EseInterface ese_;
};

TEST_F(BootAppTest, EseBootSessionOpenSuccess) {
  EXPECT_EQ(0, ese_open(&ese_, NULL));
  struct EseBootSession session;
  ese_boot_session_init(&session);

  trans_.invocations.resize(2);

  trans_.invocations[0].expected_tx.resize(kManageChannelOpenLength);
  memcpy(trans_.invocations[0].expected_tx.data(), kManageChannelOpen,
    kManageChannelOpenLength);
  trans_.invocations[0].rx.resize(3);
  trans_.invocations[0].rx[0] = 0x01;  // Channel
  trans_.invocations[0].rx[1] = 0x90;  // Return code
  trans_.invocations[0].rx[2] = 0x00;

  trans_.invocations[1].expected_tx.resize(kSelectAppletLength);
  memcpy(trans_.invocations[1].expected_tx.data(), kSelectApplet,
    kSelectAppletLength);
  trans_.invocations[1].expected_tx[0] |= 0x01;  // Channel
  trans_.invocations[1].rx.resize(2);
  trans_.invocations[1].rx[0] = 0x90;
  trans_.invocations[1].rx[1] = 0x00;
  EXPECT_EQ(ESE_APP_RESULT_OK, ese_boot_session_open(&ese_, &session));
};

TEST_F(BootAppTest, EseBootSessionOpenCooldown) {
  EXPECT_EQ(0, ese_open(&ese_, NULL));
  struct EseBootSession session;
  ese_boot_session_init(&session);

  trans_.invocations.resize(1);

  trans_.invocations[0].expected_tx.resize(kManageChannelOpenLength);
  memcpy(trans_.invocations[0].expected_tx.data(), kManageChannelOpen,
    kManageChannelOpenLength);
  trans_.invocations[0].rx.resize(2);
  trans_.invocations[0].rx[0] = 0x66;  // Return code
  trans_.invocations[0].rx[1] = 0xA5;
  // This return code should allow a subsequent call of
  // ese_boot_cooldown_values();
  EXPECT_EQ(ESE_APP_RESULT_ERROR_COOLDOWN, ese_boot_session_open(&ese_, &session));
};

TEST_F(BootAppTest, EseBootSessionOpenSelectFailure) {
  EXPECT_EQ(0, ese_open(&ese_, NULL));
  struct EseBootSession session;
  ese_boot_session_init(&session);

  trans_.invocations.resize(2);

  trans_.invocations[0].expected_tx.resize(kManageChannelOpenLength);
  memcpy(trans_.invocations[0].expected_tx.data(), kManageChannelOpen,
    kManageChannelOpenLength);
  trans_.invocations[0].rx.resize(3);
  trans_.invocations[0].rx[0] = 0x01;  // Channel
  trans_.invocations[0].rx[1] = 0x90;  // Return code
  trans_.invocations[0].rx[2] = 0x00;

  trans_.invocations[1].expected_tx.resize(kSelectAppletLength);
  memcpy(trans_.invocations[1].expected_tx.data(), kSelectApplet,
    kSelectAppletLength);
  trans_.invocations[1].expected_tx[0] |= 0x01;  // Channel
  trans_.invocations[1].rx.resize(2);
  trans_.invocations[1].rx[0] = 0x90;
  trans_.invocations[1].rx[1] = 0x01;
  EXPECT_EQ(ESE_APP_RESULT_ERROR_OS, ese_boot_session_open(&ese_, &session));
};