/*
 *  Copyright The Mbed TLS Contributors
 *  SPDX-License-Identifier: Apache-2.0
 *
 *  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 "libc/assert.h"
#include "libc/bits/bits.h"
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/fmt/conv.h"
#include "libc/fmt/fmt.h"
#include "libc/log/backtrace.internal.h"
#include "libc/log/check.h"
#include "libc/log/libfatal.internal.h"
#include "libc/log/log.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/vendor.internal.h"
#include "libc/nt/runtime.h"
#include "libc/rand/rand.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/symbols.internal.h"
#include "libc/stdio/append.internal.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/nr.h"
#include "libc/x/x.h"
#include "third_party/mbedtls/config.h"
#include "third_party/mbedtls/endian.h"
#include "third_party/mbedtls/error.h"
#include "third_party/mbedtls/platform.h"
#include "third_party/mbedtls/test/lib.h"

asm(".ident\t\"\\n\\n\
Mbed TLS (Apache 2.0)\\n\
Copyright ARM Limited\\n\
Copyright Mbed TLS Contributors\"");
asm(".include \"libc/disclaimer.inc\"");

STATIC_YOINK("zip_uri_support");

#if defined(MBEDTLS_PLATFORM_C)
static mbedtls_platform_context platform_ctx;
#endif

#if defined(MBEDTLS_CHECK_PARAMS)
typedef struct {
  uint8_t expected_call;
  uint8_t expected_call_happened;
  jmp_buf state;
  mbedtls_test_param_failed_location_record_t location_record;
} param_failed_ctx_t;
static param_failed_ctx_t param_failed_ctx;
#endif

struct Buffer {
  size_t i, n;
  char *p;
};

char *output;
jmp_buf jmp_tmp;
int option_verbose = 1;
mbedtls_test_info_t mbedtls_test_info;

static uint64_t Rando(void) {
  static uint64_t x = 0x18abac12f3191aed;
  uint64_t z = (x += 0x9e3779b97f4a7c15);
  z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
  z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
  return z ^ (z >> 31);
}

int mbedtls_test_platform_setup(void) {
  char *p;
  int ret = 0;
  static char mybuf[2][BUFSIZ];
  showcrashreports();
  setvbuf(stdout, mybuf[0], _IOLBF, BUFSIZ);
  setvbuf(stderr, mybuf[1], _IOLBF, BUFSIZ);
#if defined(MBEDTLS_PLATFORM_C)
  ret = mbedtls_platform_setup(&platform_ctx);
#endif /* MBEDTLS_PLATFORM_C */
  return ret;
}

void mbedtls_test_platform_teardown(void) {
#if defined(MBEDTLS_PLATFORM_C)
  mbedtls_platform_teardown(&platform_ctx);
#endif /* MBEDTLS_PLATFORM_C */
}

wontreturn void exit(int rc) {
  if (rc) xwrite(1, output, appendz(output).i);
  free(output);
  output = 0;
  __cxa_finalize(0);
  _Exit(rc);
}

char *GetTlsError(long r) {
  char s[128];
  if (-0x10000 < r && r < 0) {
    mbedtls_strerror(r, s, sizeof(s));
    return xasprintf("-0x%04lx %s", -r, s);
  } else {
    return xasprintf("%#lx", r);
  }
}

int mbedtls_hardware_poll(void *wut, unsigned char *p, size_t n, size_t *olen) {
  uint64_t x;
  size_t i, j;
  unsigned char b[8];
  for (i = 0; i < n; ++i) {
    x = Rando();
    WRITE64LE(b, x);
    for (j = 0; j < 8 && i + j < n; ++j) {
      p[i + j] = b[j];
    }
  }
  *olen = n;
  return 0;
}

int mbedtls_test_write(const char *fmt, ...) {
  int i, n;
  char *p;
  va_list va;
  va_start(va, fmt);
  if (option_verbose) {
    n = vfprintf(stderr, fmt, va);
  } else {
    n = vappendf(&output, fmt, va);
  }
  va_end(va);
  return n;
}

static int ascii2uc(const char c, unsigned char *uc) {
  if ((c >= '0') && (c <= '9')) {
    *uc = c - '0';
  } else if ((c >= 'a') && (c <= 'f')) {
    *uc = c - 'a' + 10;
  } else if ((c >= 'A') && (c <= 'F')) {
    *uc = c - 'A' + 10;
  } else {
    return -1;
  }
  return 0;
}

int mbedtls_test_hexcmp(uint8_t *a, uint8_t *b, uint32_t a_len,
                        uint32_t b_len) {
  int ret = 0;
  uint32_t i = 0;
  if (a_len != b_len) return -1;
  for (i = 0; i < a_len; i++) {
    if (a[i] != b[i]) {
      ret = -1;
      break;
    }
  }
  return ret;
}

/**
 * \brief           Record the current test case as a failure.
 *
 *                  This function can be called directly however it is usually
 *                  called via macros such as TEST_ASSERT, TEST_EQUAL,
 *                  PSA_ASSERT, etc...
 *
 * \note            If the test case was already marked as failed, calling
 *                  `mbedtls_test_fail( )` again will not overwrite any
 *                  previous information about the failure.
 *
 * \param test      Description of the failure or assertion that failed. This
 *                  MUST be a string literal.
 * \param line_no   Line number where the failure originated.
 * \param filename  Filename where the failure originated.
 */
void mbedtls_test_fail(const char *test, int line_no, const char *filename) {
  if (mbedtls_test_info.result == MBEDTLS_TEST_RESULT_FAILED) {
    /* We've already recorded the test as having failed. Don't
     * overwrite any previous information about the failure. */
    return;
  }
  mbedtls_test_info.result = MBEDTLS_TEST_RESULT_FAILED;
  mbedtls_test_info.test = test;
  mbedtls_test_info.line_no = line_no;
  mbedtls_test_info.filename = filename;
}

#ifdef MBEDTLS_CHECK_PARAMS
void mbedtls_param_failed(const char *msg, const char *file, int line) {
  /* Record the location of the failure */
  param_failed_ctx.location_record.failure_condition = msg;
  param_failed_ctx.location_record.file = file;
  param_failed_ctx.location_record.line = line;
  /* If we are testing the callback function...  */
  if (param_failed_ctx.expected_call != 0) {
    param_failed_ctx.expected_call = 0;
    param_failed_ctx.expected_call_happened = 1;
  } else {
    /* ...else try a long jump. If the execution state has not been set-up
     * or reset then the long jump buffer is all zero's and the call will
     * with high probability fault, emphasizing there is something to look
     * at.
     */
    longjmp(param_failed_ctx.state, 1);
  }
}
#endif

/**
 * \brief           Record the current test case as skipped.
 *
 *                  This function can be called directly however it is usually
 *                  called via the TEST_ASSUME macro.
 *
 * \param test      Description of the assumption that caused the test case to
 *                  be skipped. This MUST be a string literal.
 * \param line_no   Line number where the test case was skipped.
 * \param filename  Filename where the test case was skipped.
 */
void mbedtls_test_skip(const char *test, int line_no, const char *filename) {
  mbedtls_test_info.result = MBEDTLS_TEST_RESULT_SKIPPED;
  mbedtls_test_info.test = test;
  mbedtls_test_info.line_no = line_no;
  mbedtls_test_info.filename = filename;
}

/**
 * \brief       Set the test step number for failure reports.
 *
 *              Call this function to display "step NNN" in addition to the
 *              line number and file name if a test fails. Typically the "step
 *              number" is the index of a for loop but it can be whatever you
 *              want.
 *
 * \param step  The step number to report.
 */
void mbedtls_test_set_step(unsigned long step) {
  mbedtls_test_info.step = step;
}

/**
 * \brief       Reset mbedtls_test_info to a ready/starting state.
 */
void mbedtls_test_info_reset(void) {
  mbedtls_test_info.result = MBEDTLS_TEST_RESULT_SUCCESS;
  mbedtls_test_info.step = -1;
  mbedtls_test_info.test = 0;
  mbedtls_test_info.line_no = 0;
  mbedtls_test_info.filename = 0;
}

/**
 * \brief          This function decodes the hexadecimal representation of
 *                 data.
 *
 * \note           The output buffer can be the same as the input buffer. For
 *                 any other overlapping of the input and output buffers, the
 *                 behavior is undefined.
 *
 * \param obuf     Output buffer.
 * \param obufmax  Size in number of bytes of \p obuf.
 * \param ibuf     Input buffer.
 * \param len      The number of unsigned char written in \p obuf. This must
 *                 not be \c NULL.
 *
 * \return         \c 0 on success.
 * \return         \c -1 if the output buffer is too small or the input string
 *                 is not a valid hexadecimal representation.
 */
int mbedtls_test_unhexify(unsigned char *obuf, size_t obufmax, const char *ibuf,
                          size_t *len) {
  unsigned char uc, uc2;
  *len = strlen(ibuf);
  /* Must be even number of bytes. */
  if ((*len) & 1) return -1;
  *len /= 2;
  if ((*len) > obufmax) return -1;
  while (*ibuf != 0) {
    if (ascii2uc(*(ibuf++), &uc) != 0) return -1;
    if (ascii2uc(*(ibuf++), &uc2) != 0) return -1;
    *(obuf++) = (uc << 4) | uc2;
  }
  return 0;
}

void mbedtls_test_hexify(unsigned char *obuf, const unsigned char *ibuf,
                         int len) {
  unsigned char l, h;
  while (len != 0) {
    h = *ibuf / 16;
    l = *ibuf % 16;
    if (h < 10) {
      *obuf++ = '0' + h;
    } else {
      *obuf++ = 'a' + h - 10;
    }
    if (l < 10) {
      *obuf++ = '0' + l;
    } else {
      *obuf++ = 'a' + l - 10;
    }
    ++ibuf;
    len--;
  }
}

/**
 * Allocate and zeroize a buffer.
 *
 * If the size if zero, a pointer to a zeroized 1-byte buffer is returned.
 *
 * For convenience, dies if allocation fails.
 */
unsigned char *mbedtls_test_zero_alloc(size_t len) {
  void *p;
  size_t actual_len = (len != 0) ? len : 1;
  p = mbedtls_calloc(1, actual_len);
  TEST_HELPER_ASSERT(p != NULL);
  memset(p, 0x00, actual_len);
  return (p);
}

/**
 * Allocate and fill a buffer from hex data.
 *
 * The buffer is sized exactly as needed. This allows to detect buffer
 * overruns (including overreads) when running the test suite under valgrind.
 *
 * If the size if zero, a pointer to a zeroized 1-byte buffer is returned.
 *
 * For convenience, dies if allocation fails.
 */
unsigned char *mbedtls_test_unhexify_alloc(const char *ibuf, size_t *olen) {
  unsigned char *obuf;
  size_t len;
  *olen = strlen(ibuf) / 2;
  if (*olen == 0) return (mbedtls_test_zero_alloc(*olen));
  obuf = mbedtls_calloc(1, *olen);
  TEST_HELPER_ASSERT(obuf != NULL);
  TEST_HELPER_ASSERT(mbedtls_test_unhexify(obuf, *olen, ibuf, &len) == 0);
  return (obuf);
}

#ifdef MBEDTLS_CHECK_PARAMS
/**
 * \brief   Get the location record of the last call to
 *          mbedtls_test_param_failed().
 *
 * \note    The call expectation is set up and active until the next call to
 *          mbedtls_test_param_failed_check_expected_call() or
 *          mbedtls_param_failed() that cancels it.
 */
void mbedtls_test_param_failed_get_location_record(
    mbedtls_test_param_failed_location_record_t *location_record) {
  *location_record = param_failed_ctx.location_record;
}
#endif

#ifdef MBEDTLS_CHECK_PARAMS
/**
 * \brief   State that a call to mbedtls_param_failed() is expected.
 *
 * \note    The call expectation is set up and active until the next call to
 *          mbedtls_test_param_failed_check_expected_call() or
 *          mbedtls_param_failed that cancel it.
 */
void mbedtls_test_param_failed_expect_call(void) {
  param_failed_ctx.expected_call_happened = 0;
  param_failed_ctx.expected_call = 1;
}
#endif

#ifdef MBEDTLS_CHECK_PARAMS
/**
 * \brief   Check whether mbedtls_param_failed() has been called as expected.
 *
 * \note    Check whether mbedtls_param_failed() has been called between the
 *          last call to mbedtls_test_param_failed_expect_call() and the call
 *          to this function.
 *
 * \return  \c 0 Since the last call to mbedtls_param_failed_expect_call(),
 *               mbedtls_param_failed() has been called.
 *          \c -1 Otherwise.
 */
int mbedtls_test_param_failed_check_expected_call(void) {
  param_failed_ctx.expected_call = 0;
  if (param_failed_ctx.expected_call_happened != 0) return 0;
  return -1;
}
#endif

#ifdef MBEDTLS_CHECK_PARAMS
/**
 * \brief   Get the address of the object of type jmp_buf holding the execution
 *          state information used by mbedtls_param_failed() to do a long jump.
 *
 * \note    If a call to mbedtls_param_failed() is not expected in the sense
 *          that there is no call to mbedtls_test_param_failed_expect_call()
 *          preceding it, then mbedtls_param_failed() will try to restore the
 *          execution to the state stored in the jmp_buf object whose address
 *          is returned by the present function.
 *
 * \note    This function is intended to provide the parameter of the
 *          setjmp() function to set-up where mbedtls_param_failed() should
 *          long-jump if it has to. It is foreseen to be used as:
 *
 *          setjmp( mbedtls_test_param_failed_get_state_buf() ).
 *
 * \note    The type of the returned value is not jmp_buf as jmp_buf is an
 *          an array type (C specification) and a function cannot return an
 *          array type.
 *
 * \note    The type of the returned value is not jmp_buf* as then the return
 *          value couldn't be used by setjmp(), as its parameter's type is
 *          jmp_buf.
 *
 * \return  Address of the object of type jmp_buf holding the execution state
 *          information used by mbedtls_param_failed() to do a long jump.
 */
void *mbedtls_test_param_failed_get_state_buf(void) {
  return &param_failed_ctx.state;
}
#endif

#ifdef MBEDTLS_CHECK_PARAMS
/**
 * \brief   Reset the execution state used by mbedtls_param_failed() to do a
 *          long jump.
 *
 * \note    If a call to mbedtls_param_failed() is not expected in the sense
 *          that there is no call to mbedtls_test_param_failed_expect_call()
 *          preceding it, then mbedtls_param_failed() will try to restore the
 *          execution state that this function reset.
 *
 * \note    It is recommended to reset the execution state when the state
 *          is not relevant anymore. That way an unexpected call to
 *          mbedtls_param_failed() will not trigger a long jump with
 *          undefined behavior but rather a long jump that will rather fault.
 */
void mbedtls_test_param_failed_reset_state(void) {
  memset(param_failed_ctx.state, 0, sizeof(param_failed_ctx.state));
}
#endif

/**
 * This function just returns data from rand().
 * Although predictable and often similar on multiple
 * runs, this does not result in identical random on
 * each run. So do not use this if the results of a
 * test depend on the random data that is generated.
 *
 * rng_state shall be NULL.
 */
int mbedtls_test_rnd_std_rand(void *rng_state, unsigned char *output,
                              size_t len) {
  size_t i;
  if (rng_state != NULL) rng_state = NULL;
  for (i = 0; i < len; ++i) output[i] = rand();
  return 0;
}

/**
 * This function only returns zeros
 *
 * rng_state shall be NULL.
 */
int mbedtls_test_rnd_zero_rand(void *rng_state, unsigned char *output,
                               size_t len) {
  if (rng_state != NULL) rng_state = NULL;
  memset(output, 0, len);
  return 0;
}

/**
 * This function returns random based on a buffer it receives.
 *
 * rng_state shall be a pointer to a rnd_buf_info structure.
 *
 * The number of bytes released from the buffer on each call to
 * the random function is specified by per_call. (Can be between
 * 1 and 4)
 *
 * After the buffer is empty it will return rand();
 */
int mbedtls_test_rnd_buffer_rand(void *rng_state, unsigned char *output,
                                 size_t len) {
  mbedtls_test_rnd_buf_info *info = (mbedtls_test_rnd_buf_info *)rng_state;
  size_t use_len;
  if (rng_state == NULL) return (mbedtls_test_rnd_std_rand(NULL, output, len));
  use_len = len;
  if (len > info->length) use_len = info->length;
  if (use_len) {
    memcpy(output, info->buf, use_len);
    info->buf += use_len;
    info->length -= use_len;
  }
  if (len - use_len > 0)
    return (mbedtls_test_rnd_std_rand(NULL, output + use_len, len - use_len));
  return 0;
}

/**
 * This function returns random based on a pseudo random function.
 * This means the results should be identical on all systems.
 * Pseudo random is based on the XTEA encryption algorithm to
 * generate pseudorandom.
 *
 * rng_state shall be a pointer to a rnd_pseudo_info structure.
 */
int mbedtls_test_rnd_pseudo_rand(void *rng_state, unsigned char *output,
                                 size_t len) {
  mbedtls_test_rnd_pseudo_info *info =
      (mbedtls_test_rnd_pseudo_info *)rng_state;
  uint32_t i, *k, sum, delta = 0x9E3779B9;
  unsigned char result[4], *out = output;
  if (rng_state == NULL) return (mbedtls_test_rnd_std_rand(NULL, output, len));
  k = info->key;
  while (len > 0) {
    size_t use_len = (len > 4) ? 4 : len;
    sum = 0;
    for (i = 0; i < 32; i++) {
      info->v0 +=
          (((info->v1 << 4) ^ (info->v1 >> 5)) + info->v1) ^ (sum + k[sum & 3]);
      sum += delta;
      info->v1 += (((info->v0 << 4) ^ (info->v0 >> 5)) + info->v0) ^
                  (sum + k[(sum >> 11) & 3]);
    }
    PUT_UINT32_BE(info->v0, result, 0);
    memcpy(out, result, use_len);
    len -= use_len;
    out += 4;
  }
  return 0;
}

/**
 * \brief       Verifies that string is in string parameter format i.e. "<str>"
 *              It also strips enclosing '"' from the input string.
 *
 * \param str   String parameter.
 *
 * \return      0 if success else 1
 */
int verify_string(char **str) {
  if ((*str)[0] != '"' || (*str)[strlen(*str) - 1] != '"') {
    WRITE("Expected string (with \"\") for parameter and got: %`'s\n", *str);
    return -1;
  }
  (*str)++;
  (*str)[strlen(*str) - 1] = '\0';
  return 0;
}

/**
 * \brief       Verifies that string is an integer. Also gives the converted
 *              integer value.
 *
 * \param str   Input string.
 * \param value Pointer to int for output value.
 *
 * \return      0 if success else 1
 */
int verify_int(char *str, int *value) {
  size_t i;
  int minus = 0;
  int digits = 1;
  int hex = 0;
  for (i = 0; i < strlen(str); i++) {
    if (i == 0 && str[i] == '-') {
      minus = 1;
      continue;
    }
    if (((minus && i == 2) || (!minus && i == 1)) && str[i - 1] == '0' &&
        (str[i] == 'x' || str[i] == 'X')) {
      hex = 1;
      continue;
    }
    if (!((str[i] >= '0' && str[i] <= '9') ||
          (hex && ((str[i] >= 'a' && str[i] <= 'f') ||
                   (str[i] >= 'A' && str[i] <= 'F'))))) {
      digits = 0;
      break;
    }
  }
  if (digits) {
    if (hex)
      *value = strtol(str, NULL, 16);
    else
      *value = strtol(str, NULL, 10);
    return 0;
  }
  WRITE("Expected integer for parameter and got: %s\n", str);
  return KEY_VALUE_MAPPING_NOT_FOUND;
}

/**
 * \brief       Read a line from the passed file pointer.
 *
 * \param f     FILE pointer
 * \param buf   Pointer to memory to hold read line.
 * \param len   Length of the buf.
 *
 * \return      0 if success else -1
 */
int get_line(FILE *f, char *buf, size_t len) {
  char *ret;
  int i = 0, str_len = 0, has_string = 0;
  /* Read until we get a valid line */
  do {
    ret = fgets(buf, len, f);
    if (ret == NULL) return -1;
    str_len = strlen(buf);
    /* Skip empty line and comment */
    if (str_len == 0 || buf[0] == '#') continue;
    has_string = 0;
    for (i = 0; i < str_len; i++) {
      char c = buf[i];
      if (c != ' ' && c != '\t' && c != '\n' && c != '\v' && c != '\f' &&
          c != '\r') {
        has_string = 1;
        break;
      }
    }
  } while (!has_string);
  /* Strip new line and carriage return */
  ret = buf + strlen(buf);
  if (ret-- > buf && *ret == '\n') *ret = '\0';
  if (ret-- > buf && *ret == '\r') *ret = '\0';
  return 0;
}

/**
 * \brief       Splits string delimited by ':'. Ignores '\:'.
 *
 * \param buf           Input string
 * \param len           Input string length
 * \param params        Out params found
 * \param params_len    Out params array len
 *
 * \return      Count of strings found.
 */
static int parse_arguments(char *buf, size_t len, char **params,
                           size_t params_len) {
  int t = 0;
  size_t cnt = 0, i;
  char *cur = buf;
  char *p = buf, *q;
  params[cnt++] = cur;
  while (*p != '\0' && p < (buf + len)) {
    if (*p == '"') {
      if (t) {
        t = 0;
      } else {
        t = 1;
      }
    }
    if (*p == '\\') {
      p++;
      p++;
      continue;
    }
    if (*p == ':' && !t) {
      if (p + 1 < buf + len) {
        cur = p + 1;
        TEST_HELPER_ASSERT(cnt < params_len);
        params[cnt++] = cur;
      }
      *p = '\0';
    }
    p++;
  }
  /* Replace newlines, question marks and colons in strings */
  for (i = 0; i < cnt; i++) {
    p = params[i];
    q = params[i];
    while (*p != '\0') {
      if (*p == '\\' && *(p + 1) == 'n') {
        p += 2;
        *(q++) = '\n';
      } else if (*p == '\\' && *(p + 1) == ':') {
        p += 2;
        *(q++) = ':';
      } else if (*p == '\\' && *(p + 1) == '?') {
        p += 2;
        *(q++) = '?';
      } else
        *(q++) = *(p++);
    }
    *q = '\0';
  }
  return (cnt);
}

/**
 * \brief       Converts parameters into test function consumable parameters.
 *              Example: Input:  {"int", "0", "char*", "Hello",
 *                                "hex", "abef", "exp", "1"}
 *                      Output:  {
 *                                0,                // Verified int
 *                                "Hello",          // Verified string
 *                                2, { 0xab, 0xef },// Converted len,hex pair
 *                                9600              // Evaluated expression
 *                               }
 *
 *
 * \param cnt               Parameter array count.
 * \param params            Out array of found parameters.
 * \param int_params_store  Memory for storing processed integer parameters.
 *
 * \return      0 for success else 1
 */
static int convert_params(size_t cnt, char **params, int *int_params_store) {
  char **cur = params;
  char **out = params;
  int ret = DISPATCH_TEST_SUCCESS;
  while (cur < params + cnt) {
    char *type = *cur++;
    char *val = *cur++;
    if (strcmp(type, "char*") == 0) {
      if (verify_string(&val) == 0) {
        *out++ = val;
      } else {
        ret = (DISPATCH_INVALID_TEST_DATA);
        break;
      }
    } else if (strcmp(type, "int") == 0) {
      if (verify_int(val, int_params_store) == 0) {
        *out++ = (char *)int_params_store++;
      } else {
        ret = (DISPATCH_INVALID_TEST_DATA);
        break;
      }
    } else if (strcmp(type, "hex") == 0) {
      if (verify_string(&val) == 0) {
        size_t len;
        TEST_HELPER_ASSERT(mbedtls_test_unhexify((unsigned char *)val,
                                                 strlen(val), val, &len) == 0);
        *int_params_store = len;
        *out++ = val;
        *out++ = (char *)(int_params_store++);
      } else {
        ret = (DISPATCH_INVALID_TEST_DATA);
        break;
      }
    } else if (strcmp(type, "exp") == 0) {
      int exp_id = strtol(val, NULL, 10);
      if (get_expression(exp_id, int_params_store) == 0) {
        *out++ = (char *)int_params_store++;
      } else {
        ret = (DISPATCH_INVALID_TEST_DATA);
        break;
      }
    } else {
      ret = (DISPATCH_INVALID_TEST_DATA);
      break;
    }
  }
  return (ret);
}

/**
 * \brief       Tests snprintf implementation with test input.
 *
 * \note
 * At high optimization levels (e.g. gcc -O3), this function may be
 * inlined in run_test_snprintf. This can trigger a spurious warning about
 * potential misuse of snprintf from gcc -Wformat-truncation (observed with
 * gcc 7.2). This warning makes tests in run_test_snprintf redundant on gcc
 * only. They are still valid for other compilers. Avoid this warning by
 * forbidding inlining of this function by gcc.
 *
 * \param n         Buffer test length.
 * \param ref_buf   Expected buffer.
 * \param ref_ret   Expected snprintf return value.
 *
 * \return      0 for success else 1
 */
static noinline int test_snprintf(size_t n, const char *ref_buf, int ref_ret) {
  int ret;
  char buf[10] = "xxxxxxxxx";
  const char ref[10] = "xxxxxxxxx";
  if (n >= sizeof(buf)) return -1;
  ret = mbedtls_snprintf(buf, n, "%s", "123");
  if (ret < 0 || (size_t)ret >= n) ret = -1;
  if (strncmp(ref_buf, buf, sizeof(buf)) != 0 || ref_ret != ret ||
      memcmp(buf + n, ref + n, sizeof(buf) - n) != 0) {
    return 1;
  }
  return 0;
}

/**
 * \brief       Tests snprintf implementation.
 *
 * \return      0 for success else 1
 */
static int run_test_snprintf(void) {
  return (test_snprintf(0, "xxxxxxxxx", -1) != 0 ||
          test_snprintf(1, "", -1) != 0 || test_snprintf(2, "1", -1) != 0 ||
          test_snprintf(3, "12", -1) != 0 || test_snprintf(4, "123", 3) != 0 ||
          test_snprintf(5, "123", 3) != 0);
}

/**
 * \brief Write the description of the test case to the outcome CSV file.
 *
 * \param outcome_file  The file to write to.
 *                      If this is \c NULL, this function does nothing.
 * \param argv0         The test suite name.
 * \param test_case     The test case description.
 */
static void write_outcome_entry(FILE *outcome_file, const char *argv0,
                                const char *test_case) {
  /* The non-varying fields are initialized on first use. */
  static const char *platform = NULL;
  static const char *configuration = NULL;
  static const char *test_suite = NULL;
  if (outcome_file == NULL) return;
  if (platform == NULL) {
    platform = getenv("MBEDTLS_TEST_PLATFORM");
    if (platform == NULL) platform = "unknown";
  }
  if (configuration == NULL) {
    configuration = getenv("MBEDTLS_TEST_CONFIGURATION");
    if (configuration == NULL) configuration = "unknown";
  }
  if (test_suite == NULL) {
    test_suite = strrchr(argv0, '/');
    if (test_suite != NULL)
      test_suite += 1;  // skip the '/'
    else
      test_suite = argv0;
  }
  /* Write the beginning of the outcome line.
   * Ignore errors: writing the outcome file is on a best-effort basis. */
  WRITE("%s;%s;%s;%s;", platform, configuration, test_suite, test_case);
}

/**
 * \brief Write the result of the test case to the outcome CSV file.
 *
 * \param outcome_file  The file to write to.
 *                      If this is \c NULL, this function does nothing.
 * \param unmet_dep_count            The number of unmet dependencies.
 * \param unmet_dependencies         The array of unmet dependencies.
 * \param missing_unmet_dependencies Non-zero if there was a problem tracking
 *                                   all unmet dependencies, 0 otherwise.
 * \param ret                        The test dispatch status (DISPATCH_xxx).
 * \param info                       A pointer to the test info structure.
 */
static void write_outcome_result(FILE *outcome_file, size_t unmet_dep_count,
                                 int unmet_dependencies[],
                                 int missing_unmet_dependencies, int ret,
                                 const mbedtls_test_info_t *info) {
  if (outcome_file == NULL) return;
  /* Write the end of the outcome line.
   * Ignore errors: writing the outcome file is on a best-effort basis. */
  switch (ret) {
    case DISPATCH_TEST_SUCCESS:
      if (unmet_dep_count > 0) {
        size_t i;
        WRITE("SKIP");
        for (i = 0; i < unmet_dep_count; i++) {
          WRITE("%c%d", i == 0 ? ';' : ':', unmet_dependencies[i]);
        }
        if (missing_unmet_dependencies) WRITE(":...");
        break;
      }
      switch (info->result) {
        case MBEDTLS_TEST_RESULT_SUCCESS:
          WRITE("PASS;");
          break;
        case MBEDTLS_TEST_RESULT_SKIPPED:
          WRITE("SKIP;Runtime skip");
          break;
        default:
          WRITE("FAIL;%s:%d:%s", info->filename, info->line_no, info->test);
          break;
      }
      break;
    case DISPATCH_TEST_FN_NOT_FOUND:
      WRITE("FAIL;Test function not found");
      break;
    case DISPATCH_INVALID_TEST_DATA:
      WRITE("FAIL;Invalid test data");
      break;
    case DISPATCH_UNSUPPORTED_SUITE:
      WRITE("SKIP;Unsupported suite");
      break;
    default:
      WRITE("FAIL;Unknown cause");
      break;
  }
  WRITE("\n");
  fflush(outcome_file);
}

/**
 * \brief       Desktop implementation of execute_tests().
 *              Parses command line and executes tests from
 *              supplied or default data file.
 *
 * \param argc  Command line argument count.
 * \param argv  Argument array.
 *
 * \return      Program exit status.
 */
int execute_tests(int argc, const char **argv, const char *default_filename) {
  /* Local Configurations and options */
  long double t1, t2;
  const char *test_filename = NULL;
  const char **test_files = NULL;
  size_t testfile_count = 0;
  size_t function_id = 0;
  /* Other Local variables */
  int arg_index = 1;
  const char *next_arg;
  size_t testfile_index, i, cnt;
  int ret;
  unsigned total_errors = 0, total_tests = 0, total_skipped = 0;
  FILE *file;
  char buf[5000];
  char *params[50];
  /* Store for proccessed integer params. */
  int int_params[50];
  void *pointer;
  const char *outcome_file_name = getenv("MBEDTLS_TEST_OUTCOME_FILE");
  FILE *outcome_file = NULL;
  /*
   * The C standard doesn't guarantee that all-bits-0 is the representation
   * of a NULL pointer. We do however use that in our code for initializing
   * structures, which should work on every modern platform. Let's be sure.
   */
  memset(&pointer, 0, sizeof(void *));
  if (pointer != NULL) {
    WRITE("all-bits-zero is not a NULL pointer\n");
    return 1;
  }
  /*
   * Make sure we have a snprintf that correctly zero-terminates
   */
  if (run_test_snprintf() != 0) {
    WRITE("the snprintf implementation is broken\n");
    return 1;
  }
  if (outcome_file_name != NULL && *outcome_file_name != '\0') {
    outcome_file = fopen(outcome_file_name, "a");
    if (outcome_file == NULL) {
      WRITE("Unable to open outcome file. Continuing anyway.\n");
    }
  }
  while (arg_index < argc) {
    next_arg = argv[arg_index];
    if (strcmp(next_arg, "--verbose") == 0 || strcmp(next_arg, "-v") == 0) {
      option_verbose = 1;
    } else if (strcmp(next_arg, "--help") == 0 || strcmp(next_arg, "-h") == 0) {
      WRITE(
          "Usage: %s [OPTIONS] files...\n\n"
          "   Command line arguments:\n"
          "     files...          One or more test data files. If no file is\n"
          "                       specified the following default test case\n"
          "                       file is used:\n"
          "                           %s\n\n"
          "   Options:\n"
          "     -v | --verbose    Display full information about each test\n"
          "     -h | --help       Display this information\n\n",
          argv[0], "TESTCASE_FILENAME");
      exit(EXIT_SUCCESS);
    } else {
      /* Not an option, therefore treat all further arguments as the file
       * list.
       */
      test_files = &argv[arg_index];
      testfile_count = argc - arg_index;
    }
    arg_index++;
  }
  /* If no files were specified, assume a default */
  if (test_files == NULL || testfile_count == 0) {
    test_files = &default_filename;
    testfile_count = 1;
  }
  /* Initialize the struct that holds information about the last test */
  mbedtls_test_info_reset();
  /* Now begin to execute the tests in the testfiles */
  for (testfile_index = 0; testfile_index < testfile_count; testfile_index++) {
    size_t unmet_dep_count = 0;
    int unmet_dependencies[20];
    int missing_unmet_dependencies = 0;
    test_filename = test_files[testfile_index];
    file = fopen(test_filename, "r");
    if (file == NULL) {
      WRITE("%s (%s) failed to open test file: %s %m\n",
            program_invocation_short_name, program_executable_name,
            test_filename);
      if (outcome_file != NULL) fclose(outcome_file);
      return 1;
    }
    while (!feof(file)) {
      if (unmet_dep_count > 0) {
        WRITE("FATAL: Dep count larger than zero at start of loop\n");
        exit(EXIT_FAILURE);
      }
      unmet_dep_count = 0;
      missing_unmet_dependencies = 0;
      if ((ret = get_line(file, buf, sizeof(buf))) != 0) break;
      WRITE("%s%.66s",
            mbedtls_test_info.result == MBEDTLS_TEST_RESULT_FAILED ? "\n" : "",
            buf);
      WRITE(" ");
      for (i = strlen(buf) + 1; i < 67; i++) WRITE(".");
      WRITE(" ");
      fflush(stdout);
      write_outcome_entry(outcome_file, argv[0], buf);
      total_tests++;
      if ((ret = get_line(file, buf, sizeof(buf))) != 0) break;
      cnt = parse_arguments(buf, strlen(buf), params,
                            sizeof(params) / sizeof(params[0]));
      if (strcmp(params[0], "depends_on") == 0) {
        for (i = 1; i < cnt; i++) {
          int dep_id = strtol(params[i], NULL, 10);
          if (dep_check(dep_id) != DEPENDENCY_SUPPORTED) {
            if (unmet_dep_count < ARRAY_LENGTH(unmet_dependencies)) {
              unmet_dependencies[unmet_dep_count] = dep_id;
              unmet_dep_count++;
            } else {
              missing_unmet_dependencies = 1;
            }
          }
        }
        if ((ret = get_line(file, buf, sizeof(buf))) != 0) break;
        cnt = parse_arguments(buf, strlen(buf), params,
                              sizeof(params) / sizeof(params[0]));
      }
      // If there are no unmet dependencies execute the test
      t1 = nowl();
      if (unmet_dep_count == 0) {
        mbedtls_test_info_reset();
        function_id = strtoul(params[0], NULL, 10);
        if ((ret = check_test(function_id)) == DISPATCH_TEST_SUCCESS) {
          ret = convert_params(cnt - 1, params + 1, int_params);
          if (DISPATCH_TEST_SUCCESS == ret) {
            ret = dispatch_test(function_id, (void **)(params + 1));
          }
        }
      }
      t2 = nowl();
      write_outcome_result(outcome_file, unmet_dep_count, unmet_dependencies,
                           missing_unmet_dependencies, ret, &mbedtls_test_info);
      if (unmet_dep_count > 0 || ret == DISPATCH_UNSUPPORTED_SUITE) {
        total_skipped++;
        WRITE("----");
        if (1 == option_verbose && unmet_dep_count > 0) {
          WRITE(" (unmet dependencies: ");
          for (i = 0; i < unmet_dep_count; i++) {
            if (i) WRITE(",");
            WRITE("%d", unmet_dependencies[i]);
          }
          if (missing_unmet_dependencies) WRITE("...");
        }
        WRITE(")\n");
        fflush(stdout);
        unmet_dep_count = 0;
        missing_unmet_dependencies = 0;
      } else if (ret == DISPATCH_TEST_SUCCESS) {
        if (mbedtls_test_info.result == MBEDTLS_TEST_RESULT_SUCCESS) {
          WRITE("PASS (%,ldus)\n", (int64_t)((t2 - t1) * 1e6));
        } else if (mbedtls_test_info.result == MBEDTLS_TEST_RESULT_SKIPPED) {
          WRITE("----");
          total_skipped++;
        } else {
          total_errors++;
          WRITE("FAILED\n");
          WRITE("  %s\n  at ", mbedtls_test_info.test);
          if (mbedtls_test_info.step != -1) {
            WRITE("step %lu, ", mbedtls_test_info.step);
          }
          WRITE("line %d, %s", mbedtls_test_info.line_no,
                mbedtls_test_info.filename);
        }
        fflush(stdout);
      } else if (ret == DISPATCH_INVALID_TEST_DATA) {
        WRITE("FAILED: FATAL PARSE ERROR\n");
        fclose(file);
        exit(2);
      } else if (ret == DISPATCH_TEST_FN_NOT_FOUND) {
        WRITE("FAILED: FATAL TEST FUNCTION NOT FOUND\n");
        fclose(file);
        exit(2);
      } else
        total_errors++;
    }
    fclose(file);
  }
  if (outcome_file != NULL) fclose(outcome_file);
  WRITE("\n--------------------------------------------------"
        "--------------------------\n\n");
  if (total_errors == 0)
    WRITE("PASSED");
  else
    WRITE("FAILED");
  WRITE(" (%u / %u tests (%u skipped))\n", total_tests - total_errors,
        total_tests, total_skipped);
#if defined(MBEDTLS_MEMORY_BUFFER_ALLOC_C) && \
    !defined(TEST_SUITE_MEMORY_BUFFER_ALLOC)
#if defined(MBEDTLS_MEMORY_DEBUG)
  mbedtls_memory_buffer_alloc_status();
#endif
  mbedtls_memory_buffer_alloc_free();
#endif
  return total_errors != 0;
}