/* * 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/fmt/conv.h" #include "libc/fmt/fmt.h" #include "libc/log/log.h" #include "libc/mem/mem.h" #include "libc/rand/rand.h" #include "libc/runtime/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 "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\""); #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; 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) { int ret = 0; showcrashreports(); setvbuf(stdout, malloc(BUFSIZ), _IOLBF, BUFSIZ); setvbuf(stderr, malloc(BUFSIZ), _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) fwrite(output, 1, appendz(output).i, stderr); __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, ...) { char *p; int i, n; 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 ¶m_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. "" * 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("Failed to open test file: %s\n", 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; }