hashx: API changes to allow recovery from late compile failures

This is an API breaking change to hashx, which modifies the error
handling strategy. The main goal here is to allow unproblematic
recovery from hashx_compile failures.

hashx_alloc can no longer fail for reasons other than memory
allocation. All platform-specific compile failures are now reported via
hashx_make(), in order to both allow later failure and avoid requiring
users of the API to maintain and test multiple failure paths.

Note that late failures may be more common in actual use than early
failures. Early failures represent architectures other than x86_64 and
aarch64. Late failures could represent a number of system configurations
where syscalls are restricted.

The definition of a hashx context no longer tries to overlay storage for
the different types of program, and instead allows one context to always
contain an interpretable description of the program as well as an optional
buffer for compiled code.

The hashx_type enum is now used to mean either a specific type of hash
function or a type of hashx context. You can allocate a context for use
only with interpreted or compiled functions, or you can use
HASHX_TRY_COMPILE to prefer the compiler with an automatic fallback on
the interpreter. After calling hashx_make(), the new hashx_query_type()
can be used if needed to determine which implementation was actually
chosen.

The error return types have been overhauled so that everyone uses the
hashx_result enum, and seed failures vs compile failures are always
clearly distinguishable.

Signed-off-by: Micah Elizabeth Scott <beth@torproject.org>
This commit is contained in:
Micah Elizabeth Scott 2023-05-25 17:37:52 -07:00
parent 6fd5ca4914
commit 5a4f92ea7b
10 changed files with 271 additions and 148 deletions

View File

@ -13,7 +13,7 @@ and to ensure that each function takes exactly the same number of CPU cycles
## API
The API consists of 4 functions and is documented in the public header file
The API consists of 5 functions and is documented in the public header file
[hashx.h](include/hashx.h).
Example of usage:
@ -25,13 +25,15 @@ Example of usage:
int main() {
char seed[] = "this is a seed that will generate a hash function";
char hash[HASHX_SIZE];
hashx_ctx* ctx = hashx_alloc(HASHX_COMPILED);
if (ctx == HASHX_NOTSUPP)
ctx = hashx_alloc(HASHX_INTERPRETED);
hashx_type func_type;
hashx_ctx* ctx = hashx_alloc(HASHX_TRY_COMPILE);
if (ctx == NULL)
return 1;
if (!hashx_make(ctx, seed, sizeof(seed))) /* generate a hash function */
/* generate a hash function */
if (hashx_make(ctx, seed, sizeof(seed)) != HASHX_OK)
return 1;
if (hashx_query_type(ctx, &func_type) == HASHX_OK && func_type == HASHX_TYPE_COMPILED)
printf("Using the compiled implementation of HashX\n");
hashx_exec(ctx, 123456789, hash); /* calculate the hash of a nonce value */
hashx_free(ctx);
for (unsigned i = 0; i < HASHX_SIZE; ++i)
@ -84,6 +86,31 @@ A benchmark executable is included:
./hashx-bench --seeds 500
```
## Error fallback
The compiled implementation of HashX is much faster (very roughly 20x) so it
should be used whenever possible. It may be necessary to use the interpreter
for multiple reasons: either the platform is not supported at compile time,
or various runtime policies disallow the memory protection changes that are
necessary to do just-in-time compilation. Failures may be detected late, so
the library provides a built-in mechanism to fall back from the compiled
implementation to interpreted quickly without duplicating the whole context.
The `hashx_query_type()` function is optional, provided for users of the
`HASHX_TRY_COMPILE` context who need to know which implementation was
ultimately used.
The actual hash function, `hashx_exec()`, returns an error code for
completeness in reporting programming errors, but if a caller has invoked
`hashx_make()` successfully it can be considered infallible.
It is always possible for `hashx_make()` to fail. In addition to the
OS-specific failures you may see when forcing HashX to use the compiled
implementation with `hashx_alloc(HASHX_TYPE_COMPILED)`, it's always possible
for `hashx_make` to fail unpredictably for a particular seed value. These
seeds should be discarded and a new one attempted by the caller when
`hashx_make` returns `HASHX_FAIL_SEED`.
## Security
HashX should provide strong preimage resistance. No other security guarantees are made. About

View File

@ -15,14 +15,13 @@
int main() {
char seed[] = "this is a seed that will generate a hash function";
char hash[HASHX_SIZE];
hashx_ctx* ctx = hashx_alloc(HASHX_COMPILED);
if (ctx == HASHX_NOTSUPP)
ctx = hashx_alloc(HASHX_INTERPRETED);
hashx_ctx* ctx = hashx_alloc(HASHX_TRY_COMPILE);
if (ctx == NULL)
return 1;
if (!hashx_make(ctx, seed, sizeof(seed)))
if (hashx_make(ctx, seed, sizeof(seed)) != EQUIX_OK)
return 1;
if (hashx_exec(ctx, 123456789, hash) != EQUIX_OK)
return 1;
hashx_exec(ctx, 123456789, hash);
hashx_free(ctx);
for (unsigned i = 0; i < HASHX_SIZE; ++i)
printf("%02x", hash[i] & 0xff);
@ -58,14 +57,21 @@
/* Opaque struct representing a HashX instance */
typedef struct hashx_ctx hashx_ctx;
/* Type of hash function */
/* Type of hash context / type of compiled function */
typedef enum hashx_type {
HASHX_INTERPRETED,
HASHX_COMPILED
HASHX_TYPE_INTERPRETED = 1, /* Only the interpreted implementation */
HASHX_TYPE_COMPILED, /* Require the compiler, fail if unavailable */
HASHX_TRY_COMPILE, /* (hashx_alloc) Try compiler, don't require */
} hashx_type;
/* Sentinel value used to indicate unsupported type */
#define HASHX_NOTSUPP ((hashx_ctx*)-1)
/* Result code for hashx_make and hashx_exec */
typedef enum hashx_result {
HASHX_OK = 0,
HASHX_FAIL_UNPREPARED, /* Trying to run an unmade hash funciton */
HASHX_FAIL_UNDEFINED, /* Unrecognized hashx_type enum value */
HASHX_FAIL_SEED, /* Can't construct a hash function from this seed */
HASHX_FAIL_COMPILE, /* Can't compile, and no fallback is enabled. */
} hashx_result;
#if defined(_WIN32) || defined(__CYGWIN__)
#define HASHX_WIN
@ -100,35 +106,65 @@ extern "C" {
* @param type is the type of instance to be created.
*
* @return pointer to a new HashX instance. Returns NULL on memory allocation
* failure and HASHX_NOTSUPP if the requested type is not supported.
*/
* failures only. Other failures are reported in hashx_make.
*/
HASHX_API hashx_ctx* hashx_alloc(hashx_type type);
/*
* Create a new HashX function from seed.
* Create a new HashX function from a variable-length seed value.
*
* The seed value will be hashed internally in order to initialize the state
* of the HashX program generator and create a new unique hash function.
*
* @param ctx is pointer to a HashX instance.
* @param seed is a pointer to the seed value.
* @param size is the size of the seed.
*
* @return 1 on success, 0 on failure.
* @return HASHX_OK on success, HASHX_FAIL_SEED if the specific seed is
* not associated with a valid hash program, and HASHX_FAIL_COMPILE
* if the compiler failed for OS-specific reasons and the interpreter
* fallback was disabled by allocating the context with
* HASHX_TYPE_COMPILED rather than HASHX_TRY_COMPILE.
*/
HASHX_API hashx_result hashx_make(hashx_ctx* ctx,
const void* seed, size_t size);
/*
* Asks the specific implementation of a function created with hashx_make.
*
* This will equal the parameter to hashx_alloc() if a specific type was
* chosen there, but a context allocated with HASHX_TRY_COMPILE will allow
* the implementation to vary dynamically during hashx_make.
*
* @param ctx is pointer to a HashX instance.
* @param type_out is a pointer to which, on success, we write
* a HASHX_TYPE_* value.
*
* @return HASHX_OK on success, or HASHX_FAIL_UNPREPARED if hashx_make has not
* been invoked successfully on this context.
*/
HASHX_API int hashx_make(hashx_ctx* ctx, const void* seed, size_t size);
HASHX_API hashx_result hashx_query_type(hashx_ctx* ctx, hashx_type *type_out);
/*
* Execute the HashX function.
*
* @param ctx is pointer to a HashX instance. A HashX function must have
* been previously created by calling hashx_make.
* been previously created by invoking hashx_make successfully.
* @param HASHX_INPUT is the input to be hashed (see definition above).
* @param output is a pointer to the result buffer. HASHX_SIZE bytes will be
* written.
s*/
HASHX_API void hashx_exec(const hashx_ctx* ctx, HASHX_INPUT, void* output);
*
* @return HASHX_OK on success, or HASHX_FAIL_UNPREPARED if hashx_make has not
* been invoked successfully on this context.
*/
HASHX_API hashx_result hashx_exec(const hashx_ctx* ctx,
HASHX_INPUT, void* output);
/*
* Free a HashX instance.
*
* Has no effect if ctx is NULL.
*
* @param ctx is pointer to a HashX instance.
*/
HASHX_API void hashx_free(hashx_ctx* ctx);

View File

@ -5,6 +5,7 @@
#include "hashx_thread.h"
#include "hashx_endian.h"
#include "hashx_time.h"
#include <assert.h>
#include <limits.h>
#include <inttypes.h>
@ -26,16 +27,31 @@ static hashx_thread_retval worker(void* args) {
job->total_hashes = 0;
job->best_hash = UINT64_MAX;
for (int seed = job->start; seed < job->end; seed += job->step) {
if (!hashx_make(job->ctx, &seed, sizeof(seed))) {
continue;
{
hashx_result result = hashx_make(job->ctx, &seed, sizeof(seed));
if (result == HASHX_FAIL_SEED) {
continue;
}
if (result == HASHX_FAIL_COMPILE) {
printf("Error: not supported. Try with --interpret\n");
}
assert(result == HASHX_OK);
if (result != HASHX_OK)
break;
}
for (int nonce = 0; nonce < job->nonces; ++nonce) {
uint8_t hash[HASHX_SIZE] = { 0 };
{
#ifndef HASHX_BLOCK_MODE
hashx_exec(job->ctx, nonce, hash);
hashx_result result = hashx_exec(job->ctx, nonce, hash);
#else
hashx_exec(job->ctx, &nonce, sizeof(nonce), hash);
hashx_result result = hashx_exec(job->ctx,
&nonce, sizeof(nonce), hash);
#endif
assert(result == HASHX_OK);
if (result != HASHX_OK)
break;
}
uint64_t hashval = load64(hash);
if (hashval < job->best_hash) {
job->best_hash = hashval;
@ -70,9 +86,9 @@ int main(int argc, char** argv) {
read_int_option("--nonces", argc, argv, &nonces, 65536);
read_int_option("--threads", argc, argv, &threads, 1);
read_option("--interpret", argc, argv, &interpret);
hashx_type flags = HASHX_INTERPRETED;
hashx_type ctx_type = HASHX_TYPE_INTERPRETED;
if (!interpret) {
flags = HASHX_COMPILED;
ctx_type = HASHX_TYPE_COMPILED;
}
uint64_t best_hash = UINT64_MAX;
uint64_t diff_ex = (uint64_t)diff * 1000ULL;
@ -88,15 +104,11 @@ int main(int argc, char** argv) {
return 1;
}
for (int thd = 0; thd < threads; ++thd) {
jobs[thd].ctx = hashx_alloc(flags);
jobs[thd].ctx = hashx_alloc(ctx_type);
if (jobs[thd].ctx == NULL) {
printf("Error: memory allocation failure\n");
return 1;
}
if (jobs[thd].ctx == HASHX_NOTSUPP) {
printf("Error: not supported. Try with --interpret\n");
return 1;
}
jobs[thd].id = thd;
jobs[thd].start = start + thd;
jobs[thd].step = threads;

View File

@ -8,11 +8,12 @@
#include "program.h"
#include "context.h"
bool hashx_compiler_init(hashx_ctx* ctx) {
ctx->code = hashx_vm_alloc(COMP_CODE_SIZE);
return ctx->code != NULL;
void hashx_compiler_init(hashx_ctx* ctx) {
/* This can fail, but it's uncommon. We report this up the call chain
* later, at the same time as an mprotect or similar failure. */
ctx->compiler_mem = hashx_vm_alloc(COMP_CODE_SIZE);
}
void hashx_compiler_destroy(hashx_ctx* ctx) {
hashx_vm_free(ctx->code, COMP_CODE_SIZE);
hashx_vm_free(ctx->compiler_mem, COMP_CODE_SIZE);
}

View File

@ -15,19 +15,16 @@ HASHX_PRIVATE bool hashx_compile_x86(const hashx_program* program, uint8_t* code
HASHX_PRIVATE bool hashx_compile_a64(const hashx_program* program, uint8_t* code);
#if defined(_M_X64) || defined(__x86_64__)
#define HASHX_COMPILER 1
#define HASHX_COMPILER_X86
#define hashx_compile(p,c) hashx_compile_x86(p,c)
#elif defined(__aarch64__)
#define HASHX_COMPILER 1
#define HASHX_COMPILER_A64
#define hashx_compile(p,c) hashx_compile_a64(p,c)
#else
#define HASHX_COMPILER 0
#define hashx_compile(p,c) (false)
#endif
HASHX_PRIVATE bool hashx_compiler_init(hashx_ctx* compiler);
HASHX_PRIVATE void hashx_compiler_init(hashx_ctx* compiler);
HASHX_PRIVATE void hashx_compiler_destroy(hashx_ctx* compiler);
#define COMP_PAGE_SIZE 4096

View File

@ -33,50 +33,25 @@ const blake2b_param hashx_blake2_params = {
};
hashx_ctx* hashx_alloc(hashx_type type) {
if (!HASHX_COMPILER && (type & HASHX_COMPILED)) {
return HASHX_NOTSUPP;
}
hashx_ctx* ctx = malloc(sizeof(hashx_ctx));
if (ctx == NULL) {
goto failure;
}
ctx->code = NULL;
ctx->type = 0;
if (type & HASHX_COMPILED) {
if (!hashx_compiler_init(ctx)) {
goto failure;
}
ctx->type = HASHX_COMPILED;
}
else {
ctx->program = malloc(sizeof(hashx_program));
if (ctx->program == NULL) {
goto failure;
}
ctx->type = HASHX_INTERPRETED;
if (ctx == NULL)
return NULL;
memset(ctx, 0, sizeof *ctx);
ctx->ctx_type = type;
if (type == HASHX_TYPE_COMPILED || type == HASHX_TRY_COMPILE) {
hashx_compiler_init(ctx);
}
#ifdef HASHX_BLOCK_MODE
memcpy(&ctx->params, &hashx_blake2_params, 32);
#endif
#ifndef NDEBUG
ctx->has_program = false;
#endif
return ctx;
failure:
hashx_free(ctx);
return NULL;
}
void hashx_free(hashx_ctx* ctx) {
if (ctx != NULL && ctx != HASHX_NOTSUPP) {
if (ctx->code != NULL) {
if (ctx->type & HASHX_COMPILED) {
hashx_compiler_destroy(ctx);
}
else {
free(ctx->program);
}
}
if (ctx != NULL) {
hashx_compiler_destroy(ctx);
free(ctx);
}
}

View File

@ -9,8 +9,7 @@
#include "hashx.h"
#include "blake2.h"
#include "siphash.h"
typedef void program_func(uint64_t r[8]);
#include "program.h"
#ifdef __cplusplus
extern "C" {
@ -26,20 +25,15 @@ typedef struct hashx_program hashx_program;
/* HashX context. */
typedef struct hashx_ctx {
union {
uint8_t* code;
program_func* func;
hashx_program* program;
};
hashx_type type;
uint8_t* compiler_mem;
hashx_type ctx_type;
hashx_type func_type;
hashx_program program;
#ifndef HASHX_BLOCK_MODE
siphash_state keys;
#else
blake2b_param params;
#endif
#ifndef NDEBUG
bool has_program;
#endif
} hashx_ctx;
#endif

View File

@ -22,25 +22,20 @@
#define HASHX_INPUT_ARGS input, size
#endif
static int initialize_program(hashx_ctx* ctx, hashx_program* program,
siphash_state keys[2]) {
if (!hashx_program_generate(&keys[0], program)) {
return 0;
static bool initialize_program(hashx_ctx* ctx, siphash_state keys[2]) {
if (!hashx_program_generate(&keys[0], &ctx->program)) {
return false;
}
#ifndef HASHX_BLOCK_MODE
memcpy(&ctx->keys, &keys[1], 32);
#else
memcpy(&ctx->params.salt, &keys[1], 32);
#endif
#ifndef NDEBUG
ctx->has_program = true;
#endif
return 1;
return true;
}
int hashx_make(hashx_ctx* ctx, const void* seed, size_t size) {
assert(ctx != NULL && ctx != HASHX_NOTSUPP);
hashx_result hashx_make(hashx_ctx* ctx, const void* seed, size_t size) {
assert(ctx != NULL);
assert(seed != NULL || size == 0);
uint8_t keys_bytes[2 * sizeof(siphash_state)];
@ -59,23 +54,48 @@ int hashx_make(hashx_ctx* ctx, const void* seed, size_t size) {
keys[1].v2 = load64(keys_bytes + 6 * sizeof(uint64_t));
keys[1].v3 = load64(keys_bytes + 7 * sizeof(uint64_t));
if (ctx->type & HASHX_COMPILED) {
hashx_program program;
if (!initialize_program(ctx, &program, keys)) {
return 0;
}
if (!hashx_compile(&program, ctx->code)) {
return 0;
}
return 1;
ctx->func_type = (hashx_type)0;
if (!initialize_program(ctx, keys)) {
return HASHX_FAIL_SEED;
}
switch (ctx->ctx_type) {
case HASHX_TYPE_INTERPRETED:
ctx->func_type = HASHX_TYPE_INTERPRETED;
return HASHX_OK;
case HASHX_TYPE_COMPILED:
case HASHX_TRY_COMPILE:
if (ctx->compiler_mem != NULL &&
hashx_compile(&ctx->program, ctx->compiler_mem)) {
ctx->func_type = HASHX_TYPE_COMPILED;
return HASHX_OK;
}
if (ctx->ctx_type == HASHX_TRY_COMPILE) {
ctx->func_type = HASHX_TYPE_INTERPRETED;
return HASHX_OK;
} else {
return HASHX_FAIL_COMPILE;
}
default:
return HASHX_FAIL_UNDEFINED;
}
return initialize_program(ctx, ctx->program, keys);
}
void hashx_exec(const hashx_ctx* ctx, HASHX_INPUT, void* output) {
assert(ctx != NULL && ctx != HASHX_NOTSUPP);
hashx_result hashx_query_type(hashx_ctx* ctx, hashx_type *type_out) {
assert(ctx != NULL);
assert(type_out != NULL);
if (ctx->func_type == (hashx_type)0) {
return HASHX_FAIL_UNPREPARED;
}
*type_out = ctx->func_type;
return HASHX_OK;
}
hashx_result hashx_exec(const hashx_ctx* ctx, HASHX_INPUT, void* output) {
assert(ctx != NULL);
assert(output != NULL);
assert(ctx->has_program);
uint64_t r[8];
#ifndef HASHX_BLOCK_MODE
hashx_siphash24_ctr_state512(&ctx->keys, input, r);
@ -83,11 +103,14 @@ void hashx_exec(const hashx_ctx* ctx, HASHX_INPUT, void* output) {
hashx_blake2b_4r(&ctx->params, input, size, r);
#endif
if (ctx->type & HASHX_COMPILED) {
ctx->func(r);
}
else {
hashx_program_execute(ctx->program, r);
if (ctx->func_type == HASHX_TYPE_COMPILED) {
typedef void program_func(uint64_t r[8]);
assert(ctx->compiler_mem != NULL);
((program_func*)ctx->compiler_mem)(r);
} else if (ctx->func_type == HASHX_TYPE_INTERPRETED) {
hashx_program_execute(&ctx->program, r);
} else {
return HASHX_FAIL_UNPREPARED;
}
/* Hash finalization to remove bias toward 0 caused by multiplications */
@ -145,4 +168,5 @@ void hashx_exec(const hashx_ctx* ctx, HASHX_INPUT, void* output) {
memcpy(output, temp_out, HASHX_SIZE);
#endif
#endif
return HASHX_OK;
}

View File

@ -14,6 +14,7 @@ static int test_no = 0;
static hashx_ctx* ctx_int = NULL;
static hashx_ctx* ctx_cmp = NULL;
static hashx_ctx* ctx_auto = NULL;
static const char seed1[] = "This is a test";
static const char seed2[] = "Lorem ipsum dolor sit amet";
@ -42,20 +43,21 @@ static void run_test(const char* name, test_func* func) {
}
static bool test_alloc() {
ctx_int = hashx_alloc(HASHX_INTERPRETED);
assert(ctx_int != NULL && ctx_int != HASHX_NOTSUPP);
ctx_int = hashx_alloc(HASHX_TYPE_INTERPRETED);
assert(ctx_int != NULL);
return true;
}
static bool test_free() {
hashx_free(ctx_int);
hashx_free(ctx_cmp);
hashx_free(ctx_auto);
return true;
}
static bool test_make1() {
int result = hashx_make(ctx_int, seed1, sizeof(seed1));
assert(result == 1);
hashx_result result = hashx_make(ctx_int, seed1, sizeof(seed1));
assert(result == HASHX_OK);
return true;
}
@ -65,7 +67,8 @@ static bool test_hash_ctr1() {
#endif
#ifndef HASHX_BLOCK_MODE
char hash[HASHX_SIZE];
hashx_exec(ctx_int, counter2, hash);
hashx_result result = hashx_exec(ctx_int, counter2, hash);
assert(result == HASHX_OK);
/* printf("\n");
output_hex(hash, HASHX_SIZE);
printf("\n"); */
@ -82,7 +85,8 @@ static bool test_hash_ctr2() {
#endif
#ifndef HASHX_BLOCK_MODE
char hash[HASHX_SIZE];
hashx_exec(ctx_int, counter1, hash);
hashx_result result = hashx_exec(ctx_int, counter1, hash);
assert(result == HASHX_OK);
assert(equals_hex(hash, "2b2f54567dcbea98fdb5d5e5ce9a65983c4a4e35ab1464b1efb61e83b7074bb2"));
return true;
#else
@ -91,8 +95,8 @@ static bool test_hash_ctr2() {
}
static bool test_make2() {
int result = hashx_make(ctx_int, seed2, sizeof(seed2));
assert(result == 1);
hashx_result result = hashx_make(ctx_int, seed2, sizeof(seed2));
assert(result == HASHX_OK);
return true;
}
@ -102,7 +106,8 @@ static bool test_hash_ctr3() {
#endif
#ifndef HASHX_BLOCK_MODE
char hash[HASHX_SIZE];
hashx_exec(ctx_int, counter2, hash);
hashx_result result = hashx_exec(ctx_int, counter2, hash);
assert(result == HASHX_OK);
assert(equals_hex(hash, "ab3d155bf4bbb0aa3a71b7801089826186e44300e6932e6ffd287cf302bbb0ba"));
return true;
#else
@ -116,7 +121,8 @@ static bool test_hash_ctr4() {
#endif
#ifndef HASHX_BLOCK_MODE
char hash[HASHX_SIZE];
hashx_exec(ctx_int, counter3, hash);
hashx_result result = hashx_exec(ctx_int, counter3, hash);
assert(result == HASHX_OK);
assert(equals_hex(hash, "8dfef0497c323274a60d1d93292b68d9a0496379ba407b4341cf868a14d30113"));
return true;
#else
@ -132,36 +138,40 @@ static bool test_hash_block1() {
return false;
#else
char hash[HASHX_SIZE];
hashx_exec(ctx_int, long_input, sizeof(long_input), hash);
hashx_result result = hashx_exec(ctx_int, long_input, sizeof(long_input), hash);
assert(result == HASHX_OK);
assert(equals_hex(hash, "d0b232b832459501ca1ac9dc0429fd931414ead7624a457e375a43ea3e5e737a"));
return true;
#endif
}
static bool test_alloc_compiler() {
ctx_cmp = hashx_alloc(HASHX_COMPILED);
ctx_cmp = hashx_alloc(HASHX_TYPE_COMPILED);
assert(ctx_cmp != NULL);
return ctx_cmp != HASHX_NOTSUPP;
return true;
}
static bool test_make3() {
if (ctx_cmp == HASHX_NOTSUPP)
hashx_result result = hashx_make(ctx_cmp, seed2, sizeof(seed2));
if (result == HASHX_FAIL_COMPILE) {
return false;
int result = hashx_make(ctx_cmp, seed2, sizeof(seed2));
assert(result == 1);
}
assert(result == HASHX_OK);
return true;
}
static bool test_compiler_ctr1() {
if (ctx_cmp == HASHX_NOTSUPP)
return false;
#ifndef HASHX_BLOCK_MODE
hashx_result result;
char hash1[HASHX_SIZE];
char hash2[HASHX_SIZE];
hashx_exec(ctx_int, counter2, hash1);
hashx_exec(ctx_cmp, counter2, hash2);
result = hashx_exec(ctx_int, counter2, hash1);
assert(result == HASHX_OK);
result = hashx_exec(ctx_cmp, counter2, hash2);
if (result == HASHX_FAIL_UNPREPARED) {
return false;
}
assert(result == HASHX_OK);
assert(hashes_equal(hash1, hash2));
return true;
#else
@ -170,14 +180,17 @@ static bool test_compiler_ctr1() {
}
static bool test_compiler_ctr2() {
if (ctx_cmp == HASHX_NOTSUPP)
return false;
#ifndef HASHX_BLOCK_MODE
hashx_result result;
char hash1[HASHX_SIZE];
char hash2[HASHX_SIZE];
hashx_exec(ctx_int, counter1, hash1);
hashx_exec(ctx_cmp, counter1, hash2);
result = hashx_exec(ctx_int, counter1, hash1);
assert(result == HASHX_OK);
result = hashx_exec(ctx_cmp, counter1, hash2);
if (result == HASHX_FAIL_UNPREPARED) {
return false;
}
assert(result == HASHX_OK);
assert(hashes_equal(hash1, hash2));
return true;
#else
@ -186,20 +199,58 @@ static bool test_compiler_ctr2() {
}
static bool test_compiler_block1() {
if (ctx_cmp == HASHX_NOTSUPP)
return false;
#ifndef HASHX_BLOCK_MODE
return false;
#else
hashx_result result;
char hash1[HASHX_SIZE];
char hash2[HASHX_SIZE];
hashx_exec(ctx_int, long_input, sizeof(long_input), hash1);
hashx_exec(ctx_cmp, long_input, sizeof(long_input), hash2);
result = hashx_exec(ctx_int, long_input, sizeof(long_input), hash1);
assert(result == HASHX_OK);
result = hashx_exec(ctx_cmp, long_input, sizeof(long_input), hash2);
if (result == HASHX_FAIL_UNPREPARED) {
return false;
}
assert(result == HASHX_OK);
assert(hashes_equal(hash1, hash2));
return true;
#endif
}
static bool test_alloc_automatic() {
ctx_auto = hashx_alloc(HASHX_TRY_COMPILE);
assert(ctx_auto != NULL);
return true;
}
static bool test_auto_fallback() {
hashx_result result = hashx_make(ctx_auto, seed2, sizeof(seed2));
assert(result == HASHX_OK);
hashx_type actual_type = (hashx_type)-1;
result = hashx_query_type(ctx_auto, &actual_type);
assert(result == HASHX_OK);
assert(actual_type == HASHX_TYPE_INTERPRETED ||
actual_type == HASHX_TYPE_COMPILED);
return actual_type == HASHX_TYPE_INTERPRETED;
}
static bool test_bad_seeds() {
#ifdef HASHX_SALT
return false;
#else
hashx_result result;
result = hashx_make(ctx_auto, "\xf8\x05\x00\x00", 4);
assert(result == HASHX_OK);
result = hashx_make(ctx_auto, "\xf9\x05\x00\x00", 4);
assert(result == HASHX_FAIL_SEED);
result = hashx_make(ctx_auto, "\x5d\x93\x02\x00", 4);
assert(result == HASHX_FAIL_SEED);
result = hashx_make(ctx_auto, "\x5e\x93\x02\x00", 4);
assert(result == HASHX_OK);
return true;
#endif
}
int main() {
RUN_TEST(test_alloc);
RUN_TEST(test_make1);
@ -214,6 +265,9 @@ int main() {
RUN_TEST(test_compiler_ctr2);
RUN_TEST(test_hash_block1);
RUN_TEST(test_compiler_block1);
RUN_TEST(test_alloc_automatic);
RUN_TEST(test_auto_fallback);
RUN_TEST(test_bad_seeds);
RUN_TEST(test_free);
printf("\nAll tests were successful\n");

View File

@ -120,6 +120,9 @@ void* hashx_vm_alloc_huge(size_t bytes) {
}
void hashx_vm_free(void* ptr, size_t bytes) {
if (!ptr) {
return;
}
#ifdef HASHX_WIN
(void)bytes;
VirtualFree(ptr, 0, MEM_RELEASE);