mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-27 13:53:31 +01:00
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:
parent
6fd5ca4914
commit
5a4f92ea7b
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user