forked from wolfSSL/wolfssl
Merge pull request #819 from dgarske/test_static_fixes
Fixes for wolfCrypt test/benchmark with static memory
This commit is contained in:
@@ -166,7 +166,7 @@ static int ClientBenchmarkConnections(WOLFSSL_CTX* ctx, char* host, word16 port,
|
||||
/* time passed in number of connects give average */
|
||||
int times = benchmark;
|
||||
int loops = resumeSession ? 2 : 1;
|
||||
int i = 0;
|
||||
int i = 0;
|
||||
#ifndef NO_SESSION_CACHE
|
||||
WOLFSSL_SESSION* benchSession = NULL;
|
||||
#endif
|
||||
|
@@ -210,7 +210,7 @@ int testsuite_test(int argc, char** argv)
|
||||
#endif /* HAVE_WNR */
|
||||
|
||||
printf("\nAll tests passed!\n");
|
||||
EXIT_TEST(EXIT_SUCCESS);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void simple_test(func_args* args)
|
||||
|
@@ -32,6 +32,10 @@
|
||||
/* Macro to disable benchmark */
|
||||
#ifndef NO_CRYPT_BENCHMARK
|
||||
|
||||
#ifdef XMALLOC_USER
|
||||
#include <stdlib.h> /* we're using malloc / free direct here */
|
||||
#endif
|
||||
|
||||
#ifdef WOLFSSL_STATIC_MEMORY
|
||||
#include <wolfssl/wolfcrypt/memory.h>
|
||||
static WOLFSSL_HEAP_HINT* HEAP_HINT;
|
||||
@@ -93,6 +97,12 @@
|
||||
#include <wolfssl/wolfcrypt/random.h>
|
||||
#include <wolfssl/wolfcrypt/error-crypt.h>
|
||||
|
||||
/* only for stack size check */
|
||||
#ifdef HAVE_STACK_SIZE
|
||||
#include <wolfssl/ssl.h>
|
||||
#include <wolfssl/test.h>
|
||||
#endif
|
||||
|
||||
#ifdef WOLFSSL_ASYNC_CRYPT
|
||||
#include <wolfssl/wolfcrypt/async.h>
|
||||
#endif
|
||||
@@ -158,7 +168,7 @@
|
||||
#if defined(USE_CERT_BUFFERS_1024) || defined(USE_CERT_BUFFERS_2048) \
|
||||
|| !defined(NO_DH)
|
||||
/* include test cert and key buffers for use with NO_FILESYSTEM */
|
||||
#include <wolfssl/certs_test.h>
|
||||
#include <wolfssl/certs_test.h>
|
||||
#endif
|
||||
|
||||
|
||||
@@ -178,6 +188,7 @@
|
||||
#include "wolfssl/wolfcrypt/mem_track.h"
|
||||
#endif
|
||||
|
||||
|
||||
void bench_des(void);
|
||||
void bench_idea(void);
|
||||
void bench_arc4(void);
|
||||
@@ -235,7 +246,7 @@ void bench_rng(void);
|
||||
|
||||
#ifdef WOLFSSL_CURRTIME_REMAP
|
||||
#define current_time WOLFSSL_CURRTIME_REMAP
|
||||
#else
|
||||
#elif !defined(HAVE_STACK_SIZE)
|
||||
double current_time(int);
|
||||
#endif
|
||||
|
||||
@@ -282,28 +293,24 @@ static const XGEN_ALIGN byte iv[] =
|
||||
};
|
||||
|
||||
|
||||
/* so embedded projects can pull in tests on their own */
|
||||
#if !defined(NO_MAIN_DRIVER)
|
||||
|
||||
int main(int argc, char** argv)
|
||||
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
#else
|
||||
int benchmark_test(void *args)
|
||||
{
|
||||
(void)args;
|
||||
#endif
|
||||
|
||||
#ifdef WOLFSSL_STATIC_MEMORY
|
||||
#ifdef BENCH_EMBEDDED
|
||||
byte memory[50000];
|
||||
static byte gBenchMemory[50000];
|
||||
#else
|
||||
byte memory[400000];
|
||||
static byte gBenchMemory[400000];
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (wc_LoadStaticMemory(&HEAP_HINT, memory, sizeof(memory),
|
||||
#ifdef HAVE_STACK_SIZE
|
||||
THREAD_RETURN WOLFSSL_THREAD benchmark_test(void* args)
|
||||
#else
|
||||
int benchmark_test(void *args)
|
||||
#endif
|
||||
{
|
||||
(void)args;
|
||||
|
||||
#ifdef WOLFSSL_STATIC_MEMORY
|
||||
if (wc_LoadStaticMemory(&HEAP_HINT, gBenchMemory, sizeof(gBenchMemory),
|
||||
WOLFMEM_GENERAL, 1) != 0) {
|
||||
printf("unable to load static memory");
|
||||
exit(EXIT_FAILURE);
|
||||
@@ -314,7 +321,6 @@ int benchmark_test(void *args)
|
||||
InitMemoryTracker();
|
||||
#endif
|
||||
|
||||
wolfCrypt_Init();
|
||||
INIT_CYCLE_COUNTER
|
||||
|
||||
#if defined(DEBUG_WOLFSSL) && !defined(HAVE_VALGRIND)
|
||||
@@ -333,13 +339,6 @@ int benchmark_test(void *args)
|
||||
}
|
||||
#endif /* WOLFSSL_ASYNC_CRYPT */
|
||||
|
||||
#ifdef HAVE_WNR
|
||||
if (wc_InitNetRandom(wnrConfigFile, NULL, 5000) != 0) {
|
||||
printf("Whitewood netRandom config init failed\n");
|
||||
exit(-1);
|
||||
}
|
||||
#endif /* HAVE_WNR */
|
||||
|
||||
#if defined(HAVE_LOCAL_RNG)
|
||||
{
|
||||
int rngRet;
|
||||
@@ -351,7 +350,7 @@ int benchmark_test(void *args)
|
||||
#endif
|
||||
if (rngRet < 0) {
|
||||
printf("InitRNG failed\n");
|
||||
return rngRet;
|
||||
EXIT_TEST(rngRet);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -495,17 +494,6 @@ int benchmark_test(void *args)
|
||||
wolfAsync_DevClose(&devId);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_WNR
|
||||
if (wc_FreeNetRandom() < 0) {
|
||||
printf("Failed to free netRandom context\n");
|
||||
exit(-1);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (wolfCrypt_Cleanup() != 0) {
|
||||
printf("error with wolfCrypt_Cleanup\n");
|
||||
}
|
||||
|
||||
#if defined(USE_WOLFSSL_MEMORY) && defined(WOLFSSL_TRACK_MEMORY)
|
||||
ShowMemoryTracker();
|
||||
#endif
|
||||
@@ -514,6 +502,46 @@ int benchmark_test(void *args)
|
||||
}
|
||||
|
||||
|
||||
#ifndef NO_MAIN_DRIVER
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
#ifdef HAVE_WNR
|
||||
if (wc_InitNetRandom(wnrConfigFile, NULL, 5000) != 0) {
|
||||
printf("Whitewood netRandom config init failed\n");
|
||||
exit(-1);
|
||||
}
|
||||
#endif /* HAVE_WNR */
|
||||
|
||||
wolfCrypt_Init();
|
||||
|
||||
#ifdef HAVE_STACK_SIZE
|
||||
ret = StackSizeCheck(NULL, benchmark_test);
|
||||
#else
|
||||
ret = benchmark_test(NULL);
|
||||
#endif
|
||||
|
||||
if (wolfCrypt_Cleanup() != 0) {
|
||||
printf("Error with wolfCrypt_Cleanup!\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
#ifdef HAVE_WNR
|
||||
if (wc_FreeNetRandom() < 0) {
|
||||
printf("Failed to free netRandom context\n");
|
||||
exit(-1);
|
||||
}
|
||||
#endif /* HAVE_WNR */
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* NO_MAIN_DRIVER */
|
||||
|
||||
|
||||
#ifdef BENCH_EMBEDDED
|
||||
enum BenchmarkBounds {
|
||||
numBlocks = 25, /* how many kB to test (en/de)cryption */
|
||||
@@ -2547,7 +2575,7 @@ void bench_ed25519KeySign(void)
|
||||
}
|
||||
#endif /* HAVE_ED25519 */
|
||||
|
||||
|
||||
#ifndef HAVE_STACK_SIZE
|
||||
#if defined(_WIN32) && !defined(INTIME_RTOS)
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@@ -2659,6 +2687,7 @@ void bench_ed25519KeySign(void)
|
||||
}
|
||||
|
||||
#endif /* _WIN32 */
|
||||
#endif /* !HAVE_STACK_SIZE */
|
||||
|
||||
#if defined(HAVE_GET_CYCLES)
|
||||
|
||||
|
@@ -28,7 +28,11 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int benchmark_test(void* args);
|
||||
#ifdef HAVE_STACK_SIZE
|
||||
THREAD_RETURN WOLFSSL_THREAD benchmark_test(void* args);
|
||||
#else
|
||||
int benchmark_test(void *args);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
@@ -61,6 +61,7 @@ int get_rand_digit(WC_RNG* rng, mp_digit* d)
|
||||
return wc_RNG_GenerateBlock(rng, (byte*)d, sizeof(mp_digit));
|
||||
}
|
||||
|
||||
#ifdef WC_RSA_BLINDING
|
||||
int mp_rand(mp_int* a, int digits, WC_RNG* rng)
|
||||
{
|
||||
int ret;
|
||||
@@ -103,5 +104,6 @@ int mp_rand(mp_int* a, int digits, WC_RNG* rng)
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* WC_RSA_BLINDING */
|
||||
|
||||
#endif
|
||||
#endif /* USE_FAST_MATH || !NO_BIG_INT */
|
||||
|
@@ -110,6 +110,14 @@
|
||||
#include <wolfssl/wolfcrypt/logging.h>
|
||||
#endif
|
||||
|
||||
/* only for stack size check */
|
||||
#ifdef HAVE_STACK_SIZE
|
||||
#include <wolfssl/ssl.h>
|
||||
#define err_sys err_sys_remap /* remap err_sys */
|
||||
#include <wolfssl/test.h>
|
||||
#undef err_sys
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
/* 4996 warning to use MS extensions e.g., strcpy_s instead of strncpy */
|
||||
#pragma warning(disable: 4996)
|
||||
@@ -295,22 +303,25 @@ int memcb_test(void);
|
||||
|
||||
#define ERROR_OUT(err, eLabel) { ret = (err); goto eLabel; }
|
||||
|
||||
|
||||
#ifdef HAVE_STACK_SIZE
|
||||
static THREAD_RETURN err_sys(const char* msg, int es)
|
||||
#else
|
||||
static int err_sys(const char* msg, int es)
|
||||
|
||||
#endif
|
||||
{
|
||||
printf("%s error = %d\n", msg, es);
|
||||
|
||||
EXIT_TEST(-1);
|
||||
}
|
||||
|
||||
/* func_args from test.h, so don't have to pull in other junk */
|
||||
#ifndef HAVE_STACK_SIZE
|
||||
/* func_args from test.h, so don't have to pull in other stuff */
|
||||
typedef struct func_args {
|
||||
int argc;
|
||||
char** argv;
|
||||
int return_code;
|
||||
} func_args;
|
||||
|
||||
#endif /* !HAVE_STACK_SIZE */
|
||||
|
||||
#ifdef HAVE_FIPS
|
||||
|
||||
@@ -328,21 +339,26 @@ static void myFipsCb(int ok, int err, const char* hash)
|
||||
|
||||
#endif /* HAVE_FIPS */
|
||||
|
||||
int wolfcrypt_test(void* args)
|
||||
{
|
||||
int ret = 0;
|
||||
#ifdef WOLFSSL_STATIC_MEMORY
|
||||
#ifdef BENCH_EMBEDDED
|
||||
byte memory[10000];
|
||||
static byte gTestMemory[10000];
|
||||
#else
|
||||
byte memory[100000];
|
||||
static byte gTestMemory[100000];
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_STACK_SIZE
|
||||
THREAD_RETURN WOLFSSL_THREAD wolfcrypt_test(void* args)
|
||||
#else
|
||||
int wolfcrypt_test(void* args)
|
||||
#endif
|
||||
{
|
||||
int ret;
|
||||
|
||||
((func_args*)args)->return_code = -1; /* error state */
|
||||
|
||||
#ifdef WOLFSSL_STATIC_MEMORY
|
||||
if (wc_LoadStaticMemory(&HEAP_HINT, memory, sizeof(memory),
|
||||
if (wc_LoadStaticMemory(&HEAP_HINT, gTestMemory, sizeof(gTestMemory),
|
||||
WOLFMEM_GENERAL, 1) != 0) {
|
||||
printf("unable to load static memory");
|
||||
exit(EXIT_FAILURE);
|
||||
@@ -821,7 +837,7 @@ int wolfcrypt_test(void* args)
|
||||
|
||||
((func_args*)args)->return_code = ret;
|
||||
|
||||
return ret;
|
||||
EXIT_TEST(ret);
|
||||
}
|
||||
|
||||
|
||||
@@ -845,10 +861,14 @@ int wolfcrypt_test(void* args)
|
||||
|
||||
wolfCrypt_Init();
|
||||
|
||||
#ifdef HAVE_STACK_SIZE
|
||||
StackSizeCheck(&args, wolfcrypt_test);
|
||||
#else
|
||||
wolfcrypt_test(&args);
|
||||
#endif
|
||||
|
||||
if (wolfCrypt_Cleanup() != 0) {
|
||||
return err_sys("Error with wolfCrypt_Cleanup!\n", -1239);
|
||||
err_sys("Error with wolfCrypt_Cleanup!\n", -1239);
|
||||
}
|
||||
|
||||
#ifdef HAVE_WNR
|
||||
@@ -856,7 +876,7 @@ int wolfcrypt_test(void* args)
|
||||
err_sys("Failed to free netRandom context", -1238);
|
||||
#endif /* HAVE_WNR */
|
||||
|
||||
EXIT_TEST(args.return_code);
|
||||
return args.return_code;
|
||||
}
|
||||
|
||||
#endif /* NO_MAIN_DRIVER */
|
||||
@@ -4935,7 +4955,11 @@ int idea_test(void)
|
||||
rnd[1000], enc[1000], dec[1000];
|
||||
|
||||
/* random values */
|
||||
#ifndef HAVE_FIPS
|
||||
ret = wc_InitRng_ex(&rng, HEAP_HINT);
|
||||
#else
|
||||
ret = wc_InitRng(&rng);
|
||||
#endif
|
||||
if (ret != 0)
|
||||
return -39;
|
||||
|
||||
@@ -5009,7 +5033,11 @@ static int random_rng_test(void)
|
||||
byte block[32];
|
||||
int ret, i;
|
||||
|
||||
#ifndef HAVE_FIPS
|
||||
ret = wc_InitRng_ex(&rng, HEAP_HINT);
|
||||
#else
|
||||
ret = wc_InitRng(&rng);
|
||||
#endif
|
||||
if (ret != 0) return -39;
|
||||
|
||||
XMEMSET(block, 0, sizeof(block));
|
||||
@@ -6140,7 +6168,12 @@ int rsa_test(void)
|
||||
XFREE(tmp, HEAP_HINT ,DYNAMIC_TYPE_TMP_BUFFER);
|
||||
return -41;
|
||||
}
|
||||
|
||||
#ifndef HAVE_FIPS
|
||||
ret = wc_InitRng_ex(&rng, HEAP_HINT);
|
||||
#else
|
||||
ret = wc_InitRng(&rng);
|
||||
#endif
|
||||
if (ret != 0) {
|
||||
XFREE(tmp, HEAP_HINT ,DYNAMIC_TYPE_TMP_BUFFER);
|
||||
return -42;
|
||||
@@ -7987,7 +8020,11 @@ int dh_test(void)
|
||||
return -52;
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_FIPS
|
||||
ret = wc_InitRng_ex(&rng, HEAP_HINT);
|
||||
#else
|
||||
ret = wc_InitRng(&rng);
|
||||
#endif
|
||||
if (ret != 0)
|
||||
return -53;
|
||||
|
||||
@@ -8059,7 +8096,11 @@ int dsa_test(void)
|
||||
ret = wc_DsaPrivateKeyDecode(tmp, &idx, &key, bytes);
|
||||
if (ret != 0) return -61;
|
||||
|
||||
#ifndef HAVE_FIPS
|
||||
ret = wc_InitRng_ex(&rng, HEAP_HINT);
|
||||
#else
|
||||
ret = wc_InitRng(&rng);
|
||||
#endif
|
||||
if (ret != 0) return -62;
|
||||
|
||||
ret = wc_DsaSign(hash, signature, &key, &rng);
|
||||
@@ -10647,7 +10688,11 @@ int ecc_test(void)
|
||||
return ret;
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_FIPS
|
||||
ret = wc_InitRng_ex(&rng, HEAP_HINT);
|
||||
#else
|
||||
ret = wc_InitRng(&rng);
|
||||
#endif
|
||||
if (ret != 0)
|
||||
return -1001;
|
||||
|
||||
@@ -10775,7 +10820,11 @@ int ecc_encrypt_test(void)
|
||||
word32 plainSz = sizeof(plain);
|
||||
int i;
|
||||
|
||||
#ifndef HAVE_FIPS
|
||||
ret = wc_InitRng_ex(&rng, HEAP_HINT);
|
||||
#else
|
||||
ret = wc_InitRng(&rng);
|
||||
#endif
|
||||
if (ret != 0)
|
||||
return -3001;
|
||||
|
||||
@@ -10926,7 +10975,11 @@ int ecc_test_buffers() {
|
||||
if (ret != 0)
|
||||
return -41;
|
||||
|
||||
#ifndef HAVE_FIPS
|
||||
ret = wc_InitRng_ex(&rng, HEAP_HINT);
|
||||
#else
|
||||
ret = wc_InitRng(&rng);
|
||||
#endif
|
||||
if (ret != 0)
|
||||
return -42;
|
||||
|
||||
@@ -10986,6 +11039,7 @@ int ecc_test_buffers() {
|
||||
int curve25519_test(void)
|
||||
{
|
||||
WC_RNG rng;
|
||||
int ret;
|
||||
#ifdef HAVE_CURVE25519_SHARED_SECRET
|
||||
byte sharedA[32];
|
||||
byte sharedB[32];
|
||||
@@ -11043,7 +11097,12 @@ int curve25519_test(void)
|
||||
};
|
||||
#endif /* HAVE_CURVE25519_SHARED_SECRET */
|
||||
|
||||
if (wc_InitRng(&rng) != 0)
|
||||
#ifndef HAVE_FIPS
|
||||
ret = wc_InitRng_ex(&rng, HEAP_HINT);
|
||||
#else
|
||||
ret = wc_InitRng(&rng);
|
||||
#endif
|
||||
if (ret != 0)
|
||||
return -1001;
|
||||
|
||||
wc_curve25519_init(&userA);
|
||||
@@ -11171,7 +11230,7 @@ int ed25519_test(void)
|
||||
byte exportSKey[ED25519_KEY_SIZE];
|
||||
word32 exportPSz;
|
||||
word32 exportSSz;
|
||||
int i;
|
||||
int i, ret;
|
||||
word32 outlen;
|
||||
#ifdef HAVE_ED25519_VERIFY
|
||||
int verify;
|
||||
@@ -11498,7 +11557,14 @@ int ed25519_test(void)
|
||||
#endif /* HAVE_ED25519_SIGN && HAVE_ED25519_KEY_EXPORT && HAVE_ED25519_KEY_IMPORT */
|
||||
|
||||
/* create ed25519 keys */
|
||||
wc_InitRng(&rng);
|
||||
#ifndef HAVE_FIPS
|
||||
ret = wc_InitRng_ex(&rng, HEAP_HINT);
|
||||
#else
|
||||
ret = wc_InitRng(&rng);
|
||||
#endif
|
||||
if (ret != 0)
|
||||
return -1020;
|
||||
|
||||
wc_ed25519_init(&key);
|
||||
wc_ed25519_init(&key2);
|
||||
wc_ed25519_make_key(&rng, ED25519_KEY_SIZE, &key);
|
||||
@@ -12504,7 +12570,11 @@ int pkcs7signed_test(void)
|
||||
fclose(file);
|
||||
#endif /* USE_CERT_BUFFER_ */
|
||||
|
||||
#ifndef HAVE_FIPS
|
||||
ret = wc_InitRng_ex(&rng, HEAP_HINT);
|
||||
#else
|
||||
ret = wc_InitRng(&rng);
|
||||
#endif
|
||||
if (ret != 0) {
|
||||
XFREE(certDer, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
XFREE(keyDer, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
@@ -12678,7 +12748,11 @@ int mp_test()
|
||||
|
||||
mp_init_copy(&p, &a);
|
||||
|
||||
#ifndef HAVE_FIPS
|
||||
ret = wc_InitRng_ex(&rng, HEAP_HINT);
|
||||
#else
|
||||
ret = wc_InitRng(&rng);
|
||||
#endif
|
||||
if (ret != 0)
|
||||
goto done;
|
||||
|
||||
|
@@ -28,7 +28,11 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_STACK_SIZE
|
||||
THREAD_RETURN WOLFSSL_THREAD wolfcrypt_test(void* args);
|
||||
#else
|
||||
int wolfcrypt_test(void* args);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
@@ -1339,9 +1339,10 @@ static INLINE void CaCb(unsigned char* der, int sz, int type)
|
||||
typedef THREAD_RETURN WOLFSSL_THREAD (*thread_func)(void* args);
|
||||
|
||||
|
||||
static INLINE void StackSizeCheck(func_args* args, thread_func tf)
|
||||
static INLINE int StackSizeCheck(func_args* args, thread_func tf)
|
||||
{
|
||||
int ret, i, used;
|
||||
void* status;
|
||||
unsigned char* myStack = NULL;
|
||||
int stackSize = 1024*128;
|
||||
pthread_attr_t myAttr;
|
||||
@@ -1372,7 +1373,7 @@ static INLINE void StackSizeCheck(func_args* args, thread_func tf)
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
ret = pthread_join(threadId, NULL);
|
||||
ret = pthread_join(threadId, &status);
|
||||
if (ret != 0)
|
||||
err_sys("pthread_join failed");
|
||||
|
||||
@@ -1384,6 +1385,8 @@ static INLINE void StackSizeCheck(func_args* args, thread_func tf)
|
||||
|
||||
used = stackSize - i;
|
||||
printf("stack used = %d\n", used);
|
||||
|
||||
return (int)((size_t)status);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -440,6 +440,8 @@
|
||||
|
||||
#ifdef WOLFSSL_RIOT_OS
|
||||
#define EXIT_TEST(ret) exit(ret)
|
||||
#elif defined(HAVE_STACK_SIZE)
|
||||
#define EXIT_TEST(ret) return (void*)((size_t)(ret))
|
||||
#else
|
||||
#define EXIT_TEST(ret) return ret
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user