/*
 * 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.
 *
 * Usage:
 *   $0 [impl name] < line-by-line-input-and-expect
 */

#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define LOG_TAG "ese-replay"
#include <ese/ese.h>
#include <ese/log.h>

#include "buffer.h"
#include "hw.h"
#include "payload.h"

const struct SupportedHardware kSupportedHardware = {
    .len = 3,
    .hw =
        {
            {
                .name = "nq-nci",
                .sym = "ESE_HW_NXP_PN80T_NQ_NCI_ops",
                .lib = "libese-hw-nxp-pn80t-nq-nci.so",
                .options = NULL,
            },
            {
                .name = "fake",
                .sym = "ESE_HW_FAKE_ops",
                .lib = "libese-hw-fake.so",
                .options = NULL,
            },
            {
                .name = "echo",
                .sym = "ESE_HW_ECHO_ops",
                .lib = "libese-hw-echo.so",
                .options = NULL,
            },
        },
};

int main(int argc, char **argv) {
  if (argc != 2) {
    printf("Usage:\n%s [hw_impl] < file_with_apdus\n\n"
           "File format:\n"
           "  hex-apdu-to-send hex-trailing-response-bytes\\n\n"
           "\n"
           "For example,\n"
           "  echo -e '00A4040000 9000\\n80CA9F7F00 9000\\n' | %s nq-nci\n",
           argv[0], argv[0]);
    print_supported_hardware(&kSupportedHardware);
    return 1;
  }
  int hw_id = find_supported_hardware(&kSupportedHardware, argv[1]);
  if (hw_id < 0) {
    fprintf(stderr, "Unknown hardware name: %s\n", argv[1]);
    return 3;
  }
  const struct Hardware *hw = &kSupportedHardware.hw[hw_id];

  struct EseInterface ese;
  printf("[-] Initializing eSE\n");

  if (!initialize_hardware(&ese, hw)) {
    fprintf(stderr, "Could not initialize hardware\n");
    return 2;
  }
  printf("eSE implementation selected: %s\n", ese_name(&ese));
  if (ese_open(&ese, hw->options)) {
    ALOGE("Cannot open hw");
    if (ese_error(&ese)) {
      ALOGE("eSE error (%d): %s", ese_error_code(&ese),
            ese_error_message(&ese));
    }
    return 5;
  }
  printf("eSE is open\n");
  struct Payload payload;
  if (!payload_init(&payload, 10 * 1024 * 1024, 1024 * 4)) {
    ALOGE("Failed to initialize payload.");
    return -1;
  }

  struct Buffer reply;
  buffer_init(&reply, 2048);
  while (!feof(stdin) && payload_read(&payload, stdin)) {
    payload_dump(&payload, stdout);
    reply.len = (uint32_t)ese_transceive(
        &ese, payload.tx.buffer, payload.tx.len, reply.buffer, reply.size);
    if ((int)reply.len < 0 || ese_error(&ese)) {
      printf("Transceive error. See logcat -s ese-replay\n");
      ALOGE("transceived returned failure: %d\n", (int)reply.len);
      if (ese_error(&ese)) {
        ALOGE("An error (%d) occurred: %s", ese_error_code(&ese),
              ese_error_message(&ese));
      }
      break;
    }
    buffer_dump(&reply, "", "Response", 240, stdout);
    if (reply.len < payload.expected.len) {
      printf("Received less data than expected: %u < %u\n", reply.len,
             payload.expected.len);
      break;
    }

    /* Only compare the end. This allows a simple APDU success match. */
    if (memcmp(payload.expected.buffer,
               (reply.buffer + reply.len) - payload.expected.len,
               payload.expected.len)) {
      printf("Response did not match. Aborting!\n");
      break;
    }
  }
  buffer_free(&reply);
  printf("Transmissions complete.\n");
  ese_close(&ese);
  release_hardware(hw);
  return 0;
}