mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-01 03:53:33 +00:00
398f0c16fb
This change makes SSL virtual hosting possible. You can now load
multiple certificates for multiple domains and redbean will just
figure out which one to use, even if you only have 1 ip address.
You can also use a jumbo certificate that lists all your domains
in the the subject alternative names.
This change also makes performance improvements to MbedTLS. Here
are some benchmarks vs. cc1920749e
BEFORE AFTER (microsecs)
suite_ssl.com 2512881 191738 13.11x faster
suite_pkparse.com 36291 3295 11.01x faster
suite_x509parse.com 854669 120293 7.10x faster
suite_pkwrite.com 6549 1265 5.18x faster
suite_ecdsa.com 53347 18778 2.84x faster
suite_pk.com 49051 18717 2.62x faster
suite_ecdh.com 19535 9502 2.06x faster
suite_shax.com 15848 7965 1.99x faster
suite_rsa.com 353257 184828 1.91x faster
suite_x509write.com 162646 85733 1.90x faster
suite_ecp.com 20503 11050 1.86x faster
suite_hmac_drbg.no_reseed.com 19528 11417 1.71x faster
suite_hmac_drbg.nopr.com 12460 8010 1.56x faster
suite_mpi.com 687124 442661 1.55x faster
suite_hmac_drbg.pr.com 11890 7752 1.53x faster
There aren't any special tricks to the performance imporvements.
It's mostly due to code cleanup, assembly and intel instructions
like mulx, adox, and adcx.
1113 lines
35 KiB
C
1113 lines
35 KiB
C
/*
|
|
* 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. "<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("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;
|
|
}
|