/****************************************************************************
 * apps/testing/setest/setest.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you 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.
 *
 ****************************************************************************/

/* Copyright 2023 NXP */

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <fcntl.h>
#include <libgen.h>
#include <nuttx/crypto/se05x.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define DEFAULT_SETTINGS                                                     \
  {                                                                          \
    .se05x_dev = default_se05x_device, .skip_process = FALSE,                \
    .base_id = 0x230000                                                      \
  }

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct settings_t
{
  FAR char *se05x_dev;
  bool skip_process;
  uint32_t base_id;
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

static char default_se05x_device[] = "/dev/se05x";

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static void print_usage(FAR FILE *f, char *prg)
{
  fprintf(f, "%s - Secure Element Test\n", prg);
  fprintf(f, "\nUsage: %s [options] <secure element device>\n", prg);
  fprintf(f, "Options:\n");
  fprintf(f, "         -b <id>     (use base address <id>\n");
  fprintf(f, "                      default = 0x230000)\n");
}

static int parse_arguments(int argc, FAR char *argv[],
                           FAR struct settings_t *settings)
{
  int result = 0;
  int opt;
  FAR char *prg = basename(argv[0]);
  while (((opt = getopt(argc, argv, "b:h")) != -1) && (result == 0))
    {
      switch (opt)
        {
        case 'b':
          settings->base_id = (uint32_t)strtoul(optarg, NULL, 0);
          break;
        case 'h':
          print_usage(stdout, prg);
          settings->skip_process = TRUE;
          break;
        default:
          print_usage(stderr, prg);
          result = -EINVAL;
          break;
        }
    }

  if ((result == 0) && (!settings->skip_process))
    {
      settings->se05x_dev = default_se05x_device;

      /* if device is specified as positional argument */

      if (optind != argc)
        {
          settings->se05x_dev = argv[optind];
        }
    }

  return result;
}

static uint32_t get_se05x_id(FAR struct settings_t *settings, uint32_t id)
{
  return settings->base_id + id;
}

static void print_result(bool success)
{
  if (success)
    {
      puts(" SUCCESS");
    }
  else
    {
      puts(" FAIL");
    }
}

static int invert_result(int result)
{
  return result != 0 ? 0 : -1;
}

static int run_tests(int fd, FAR struct settings_t *settings)
{
  int result = 0;
  puts("Setup");
  for (uint32_t id = get_se05x_id(settings, 1);
       id < get_se05x_id(settings, 5); id++)
    {
      /* result is neglected because already empty entries result with
       * errors
       */

      (void)ioctl(fd, SEIOC_DELETE_KEY, id);
    }

  puts("Generate 3 keypairs");
  for (uint32_t id = get_se05x_id(settings, 1);
       (id < get_se05x_id(settings, 4)) && (result == 0); id++)
    {
      struct se05x_generate_keypair_s args = {
          .id = id, .cipher = SE05X_ASYM_CIPHER_EC_NIST_P_256
      };

      result = ioctl(fd, SEIOC_GENERATE_KEYPAIR, &args);
    }

  print_result(result == 0);

  uint8_t public_key[300];
  size_t public_key_size;
  if (result == 0)
    {
      puts("Read from pub #1");

      struct se05x_key_transmission_s args = {
          .entry = {.id = get_se05x_id(settings, 1),
                    .cipher = SE05X_ASYM_CIPHER_EC_NIST_P_256},
          .content = {.buffer = public_key,
                      .buffer_size = sizeof(public_key)}
      };

      result = ioctl(fd, SEIOC_GET_KEY, &args);

      public_key_size = args.content.buffer_content_size;

      print_result(result == 0);
    }

  if (result == 0)
    {
      puts("Write to pub #4");

      struct se05x_key_transmission_s args = {
          .entry = {.id = get_se05x_id(settings, 4),
                    .cipher = SE05X_ASYM_CIPHER_EC_NIST_P_256},
          .content = {.buffer = public_key,
                      .buffer_size = public_key_size,
                      .buffer_content_size = public_key_size}
      };

      result = ioctl(fd, SEIOC_SET_KEY, &args);

      print_result(result == 0);
    }

  uint8_t hash[32] = {
          0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10,
          11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
          22, 23, 24, 25, 26, 27, 28, 29, 30, 31
  };

  uint8_t signature[300];
  size_t signature_content_size;
  if (result == 0)
    {
      puts("Create signature with priv #1");

      struct se05x_signature_s args = {
          .key_id = get_se05x_id(settings, 1),
          .algorithm = SE05X_ALGORITHM_SHA256,
          .tbs = {.buffer = hash,
                  .buffer_size = sizeof(hash),
                  .buffer_content_size = sizeof(hash)},
          .signature = {.buffer = signature,
                        .buffer_size = sizeof(signature)},
      };

      result = ioctl(fd, SEIOC_CREATE_SIGNATURE, &args);
      signature_content_size = args.signature.buffer_content_size;
      print_result(result == 0);
    }

  if (result == 0)
    {
      puts("Verify signature with pub #4");

      struct se05x_signature_s args = {
          .key_id = get_se05x_id(settings, 4),
          .algorithm = SE05X_ALGORITHM_SHA256,
          .tbs = {.buffer = hash,
                  .buffer_size = sizeof(hash),
                  .buffer_content_size = sizeof(hash)},
          .signature = {.buffer = signature,
                        .buffer_content_size = signature_content_size},
      };

      result = ioctl(fd, SEIOC_VERIFY_SIGNATURE, &args);

      print_result(result == 0);
    }

  if (result == 0)
    {
      puts("Verification of signature with pub #2 should be dissapproved");

      struct se05x_signature_s args = {
          .key_id = get_se05x_id(settings, 2),
          .algorithm = SE05X_ALGORITHM_SHA256,
          .tbs = {.buffer = hash, .buffer_size = sizeof(hash)},
          .signature = {.buffer = signature,
                        .buffer_content_size = signature_content_size},
      };

      result = invert_result(ioctl(fd, SEIOC_VERIFY_SIGNATURE, &args));

      print_result(result == 0);
    }

  uint8_t symm_key_a[32];
  if (result == 0)
    {
      puts("Derive symm key #a from priv #1 and pub #2");

      struct se05x_derive_key_s args = {
          .private_key_id = get_se05x_id(settings, 1),
          .public_key_id = get_se05x_id(settings, 2),
          .content = {.buffer = symm_key_a,
                      .buffer_size = sizeof(symm_key_a)},
      };

      result = ioctl(fd, SEIOC_DERIVE_SYMM_KEY, &args);

      print_result(result == 0);
    }

  uint8_t symm_key_b[32];
  if (result == 0)
    {
      puts("Derive symm key #b from priv #2 and pub #1");

      struct se05x_derive_key_s args = {
          .private_key_id = get_se05x_id(settings, 2),
          .public_key_id = get_se05x_id(settings, 1),
          .content = {.buffer = symm_key_b,
                      .buffer_size = sizeof(symm_key_b)},
      };

      result = ioctl(fd, SEIOC_DERIVE_SYMM_KEY, &args);

      print_result(result == 0);
    }

  uint8_t symm_key_c[32];
  if (result == 0)
    {
      puts("Derive symm key #c from priv #1 and pub #3");

      struct se05x_derive_key_s args = {
          .private_key_id = get_se05x_id(settings, 1),
          .public_key_id = get_se05x_id(settings, 3),
          .content = {.buffer = symm_key_c,
                      .buffer_size = sizeof(symm_key_c)},
      };

      result = ioctl(fd, SEIOC_DERIVE_SYMM_KEY, &args);

      print_result(result == 0);
    }

  if (result == 0)
    {
      puts("Symm key #a and #b should be equal");

      result = memcmp(symm_key_a, symm_key_b, sizeof(symm_key_a));

      print_result(result == 0);
    }

  if (result == 0)
    {
      puts("Symm key #a and #c should be different");

      result =
          invert_result(memcmp(symm_key_a, symm_key_c, sizeof(symm_key_a)));

      print_result(result == 0);
    }

  if (result == 0)
    {
      puts("All tests succeeded!\n");
    }

  return result;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

int main(int argc, FAR char *argv[])
{
  struct settings_t settings = DEFAULT_SETTINGS;
  int result = parse_arguments(argc, argv, &settings);

  int fd;
  if ((result == 0) && (!settings.skip_process))
    {
      fd = open(settings.se05x_dev, O_RDONLY);
      if (fd == -1)
        {
          result = -ENODEV;
        }
      else
        {
          run_tests(fd, &settings);
          close(fd);
        }
    }

  if (result != 0)
    {
      fprintf(stderr, "err %i: %s\n", -result, strerror(-result));
    }

  return result == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
