aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.gitignore15
-rw-r--r--LICENSE15
-rw-r--r--Makefile79
-rw-r--r--common.h33
-rw-r--r--config.mk8
-rw-r--r--libar2simplified.h20
-rw-r--r--libar2simplified_decode.c194
-rw-r--r--libar2simplified_encode.c59
-rw-r--r--libar2simplified_encode_hash.c17
-rw-r--r--libar2simplified_hash.c383
-rw-r--r--mk/linux.mk4
-rw-r--r--mk/macos.mk4
-rw-r--r--mk/windows.mk4
-rw-r--r--test.c136
14 files changed, 971 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c2c489c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*\#*
+*~
+*.o
+*.a
+*.lo
+*.su
+*.so
+*.so.*
+*.dll
+*.dylib
+*.gch
+*.gcov
+*.gcno
+*.gcda
+/test
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d0aa103
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2022 Mattias Andrée <maandree@kth.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..faee51e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,79 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+OS = linux
+# Linux: linux
+# Mac OS: macos
+# Windows: windows
+include mk/$(OS).mk
+
+
+LIB_MAJOR = 1
+LIB_MINOR = 0
+LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR)
+LIB_NAME = ar2simplified
+
+
+OBJ =\
+ libar2simplified_decode.o\
+ libar2simplified_encode.o\
+ libar2simplified_encode_hash.o\
+ libar2simplified_hash.o
+
+HDR =\
+ libar2simplified.h\
+ common.h
+
+LOBJ = $(OBJ:.o=.lo)
+
+
+all: libar2simplified.a libar2simplified.$(LIBEXT) test
+$(OBJ): $(HDR)
+$(LOBJ): $(HDR)
+test.o: test.c $(HDR)
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.lo:
+ $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+test: test.o libar2simplified.a
+ $(CC) -o $@ test.o libar2simplified.a $(LDFLAGS)
+
+libar2simplified.a: $(OBJ)
+ @rm -f -- $@
+ $(AR) rc $@ $(OBJ)
+
+libar2simplified.$(LIBEXT): $(LOBJ)
+ $(CC) $(LIBFLAGS) -o $@ $(LOBJ) $(LDFLAGS)
+
+check: test
+ ./test
+
+install: libar2simplified.a libar2simplified.$(LIBEXT)
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/lib"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/include"
+ cp -- libar2simplified.a "$(DESTDIR)$(PREFIX)/lib/"
+ cp -- libar2simplified.$(LIBEXT) "$(DESTDIR)$(PREFIX)/lib/libar2simplified.$(LIBMINOREXT)"
+ ln -sf -- libar2simplified.$(LIBMINOREXT) "$(DESTDIR)$(PREFIX)/lib/libar2simplified.$(LIBMAJOREXT)"
+ ln -sf -- libar2simplified.$(LIBMAJOREXT) "$(DESTDIR)$(PREFIX)/lib/libar2simplified.$(LIBEXT)"
+ cp -- libar2simplified.h "$(DESTDIR)$(PREFIX)/include/"
+
+uninstall:
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libar2simplified.a"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libar2simplified.$(LIBMAJOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libar2simplified.$(LIBMINOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libar2simplified.$(LIBEXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/include/libar2simplified.h"
+
+clean:
+ -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.dll *.dylib
+ -rm -f -- *.gch *.gcov *.gcno *.gcda *.$(LIBEXT) test
+
+.SUFFIXES:
+.SUFFIXES: .lo .o .c
+
+.PHONY: all check install uninstall clean
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..cb6a5fe
--- /dev/null
+++ b/common.h
@@ -0,0 +1,33 @@
+/* See LICENSE file for copyright and license details. */
+#include "libar2simplified.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libar2.h>
+
+
+#ifndef ALIGNOF
+# ifdef __STDC_VERSION__
+# if __STDC_VERSION__ >= 201112L
+# define ALIGNOF(X) _Alignof(X)
+# endif
+# endif
+#endif
+#ifndef ALIGNOF
+# ifdef __GNUC__
+# define ALIGNOF(X) __alignof__(X)
+# else
+# define ALIGNOF(X) sizeof(X)
+# endif
+#endif
+
+
+#ifndef FALLBACK_NPROC
+# define FALLBACK_NPROC 4
+#endif
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..eb58289
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = cc
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS = -std=c99 -Wall -g -pthread
+LDFLAGS = -lar2 -lblake -pthread -lrt
diff --git a/libar2simplified.h b/libar2simplified.h
new file mode 100644
index 0000000..02a27ec
--- /dev/null
+++ b/libar2simplified.h
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef LIBAR2SIMPLIFIED_H
+#define LIBAR2SIMPLIFIED_H
+
+#include <libar2.h>
+
+LIBAR2_PUBLIC__ LIBAR2_NONNULL__(1)
+char *libar2simplified_encode(const struct libar2_argon2_parameters *params, void *hash);
+
+LIBAR2_PUBLIC__ LIBAR2_NONNULL__(1, 2)
+char *libar2simplified_encode_hash(const struct libar2_argon2_parameters *params, void *hash);
+
+LIBAR2_PUBLIC__ LIBAR2_NONNULL__(1)
+struct libar2_argon2_parameters *
+libar2simplified_decode(const char *str, char **tagp, char **endp, int (*random_byte_generator)(char *out, size_t n));
+
+LIBAR2_PUBLIC__ LIBAR2_NONNULL__(1, 4)
+int libar2simplified_hash(void *hash, void *msg, size_t msglen, struct libar2_argon2_parameters *params);
+
+#endif
diff --git a/libar2simplified_decode.c b/libar2simplified_decode.c
new file mode 100644
index 0000000..ab4db5a
--- /dev/null
+++ b/libar2simplified_decode.c
@@ -0,0 +1,194 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifdef __linux__
+#include <sys/random.h>
+#endif
+#include <time.h>
+
+
+static size_t
+decode_u32(const char *s, uint_least32_t *outp)
+{
+ uint_least32_t digit;
+ size_t i;
+
+ if ((s[0] == '0' && s[1] == '0') || !isdigit(s[0])) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ *outp = 0;
+ for (i = 0; isdigit(s[i]); i++) {
+ digit = (uint_least32_t)(s[i] & 15);
+ if (*outp > ((uint_least32_t)0xFFFFffffUL - digit) / 10) {
+ errno = ERANGE;
+ return 0;
+ }
+ *outp = *outp * 10 + digit;
+ }
+
+ return i;
+}
+
+
+static int
+random_salt(char *out, size_t n, int (*random_byte_generator)(char *out, size_t n))
+{
+#define ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+ static int srand_called = 0;
+
+ double x;
+ size_t i;
+ int xi;
+#ifdef __linux__
+ ssize_t r;
+#endif
+
+ if (random_byte_generator) {
+ if (random_byte_generator(out, n))
+ return -1;
+ } else {
+ i = 0;
+#ifdef __linux__
+ for(; i < n; i += (size_t)r) {
+ r = getrandom(&out[i], n - i, GRND_NONBLOCK);
+ if(r < 0)
+ break;
+ }
+#endif
+ if (!srand_called) {
+ srand((unsigned int)time(NULL));
+ srand_called = 1;
+ }
+ for(; i < n; i++) {
+ xi = rand();
+ x = (double)xi;
+ x /= (double)RAND_MAX;
+ x *= 63;
+ out[i] = (char)x;
+ }
+ }
+
+ for (i = 0; i < n; i++)
+ out[i] = ALPHABET[out[i] % 64];
+ return 0;
+}
+
+
+static void *
+allocate(size_t num, size_t size, size_t alignment, struct libar2_context *ctx)
+{
+ (void) ctx;
+ (void) alignment;
+ return malloc(num * size);
+}
+
+
+static void
+deallocate(void *ptr, struct libar2_context *ctx)
+{
+ (void) ctx;
+ free(ptr);
+}
+
+
+struct libar2_argon2_parameters *
+libar2simplified_decode(const char *str, char **tagp, char **endp, int (*random_byte_generator)(char *out, size_t n))
+{
+ struct libar2_argon2_parameters params, *ret;
+ const char *p = str;
+ const char *end;
+ char *str_free = NULL;
+ char *buf = NULL;
+ size_t n, saltsize, offset;
+ uint_least32_t saltlen, hashlen;
+ struct libar2_context ctx;
+
+ if (*p != '$')
+ goto einval;
+ p = strchr(&p[1], '$');
+ if (!p)
+ goto einval;
+ if (p[1] == 'v' && p[2] == '=') {
+ p = strchr(&p[1], '$');
+ if (!p)
+ goto einval;
+ }
+ p = strchr(&p[1], '$');
+ if (!p)
+ goto einval;
+ p++;
+ end = strchr(p, '$');
+ if (!end)
+ goto einval;
+
+ if (*p == '*') {
+ n = decode_u32(&p[1], &saltlen);
+ if (!n++)
+ goto fail;
+ if (&p[n] != end)
+ goto einval;
+ params.saltlen = (size_t)saltlen;
+ saltsize = libar2_encode_base64(NULL, NULL, saltlen) - 1;
+ offset = (size_t)(p - str);
+ str_free = malloc(offset + saltsize + strlen(&p[n]) + 1);
+ if (!str_free)
+ goto enomem;
+ memcpy(str_free, str, offset);
+ if (random_salt(&str_free[offset], saltsize, random_byte_generator))
+ goto fail;
+ offset += saltsize;
+ stpcpy(&str_free[offset], &p[n]);
+ str = str_free;
+ }
+ end++;
+
+ ctx.allocate = allocate;
+ ctx.deallocate = deallocate;
+
+ if (!libar2_decode_params(str, &params, &buf, &ctx))
+ goto fail;
+
+ if (*end == '*') {
+ n = decode_u32(&end[1], &hashlen);
+ if (!n++)
+ goto fail;
+ end = &end[n];
+ params.hashlen = (size_t)hashlen;
+ if (tagp)
+ *tagp = NULL;
+ } else {
+ if (tagp)
+ *tagp = *(void **)(void *)&end;
+ end = &end[libar2_encode_base64(NULL, NULL, params.hashlen) - 1];
+ }
+
+ ret = malloc(sizeof(params) + params.saltlen);
+ if (!ret)
+ goto enomem;
+ memcpy(ret, &params, sizeof(params));
+ if (buf) {
+ memcpy(&((char *)ret)[sizeof(params)], buf, params.saltlen);
+ ret->salt = &((unsigned char *)ret)[sizeof(params)];
+ deallocate(buf, &ctx);
+ }
+
+ if (endp)
+ *endp = *(void **)(void *)&end;
+
+ free(str_free);
+ return ret;
+
+einval:
+ errno = EINVAL;
+ return NULL;
+
+fail:
+ free(str_free);
+ return NULL;
+
+enomem:
+ free(str_free);
+ errno = ENOMEM;
+ return NULL;
+}
diff --git a/libar2simplified_encode.c b/libar2simplified_encode.c
new file mode 100644
index 0000000..054e574
--- /dev/null
+++ b/libar2simplified_encode.c
@@ -0,0 +1,59 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+static size_t
+encode_params(char *buf, size_t bufsize, const struct libar2_argon2_parameters *params)
+{
+ if (params->salt) {
+ return libar2_encode_params(buf, params);
+ }
+
+ return 1 + (size_t)snprintf(buf, bufsize, "$%s$v=%i$m=%lu,t=%lu,p=%lu$*%zu$",
+ libar2_type_to_string(params->type, LIBAR2_LOWER_CASE),
+ (int)params->version,
+ (unsigned long int)params->m_cost,
+ (unsigned long int)params->t_cost,
+ (unsigned long int)params->lanes,
+ params->saltlen);
+}
+
+
+char *
+libar2simplified_encode(const struct libar2_argon2_parameters *params_, void *hash)
+{
+ struct libar2_argon2_parameters params = *params_;
+ size_t size, off;
+ char *ret;
+
+ if (libar2_validate_params(&params, NULL) != LIBAR2_OK) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ size = encode_params(NULL, 0, &params);
+ if (hash)
+ size += libar2_encode_base64(NULL, NULL, params.hashlen) - 1;
+ else
+ size += (size_t)snprintf(NULL, 0, "*%zu", params.hashlen);
+
+ ret = malloc(size);
+ if (!ret) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ off = encode_params(ret, size, &params) - 1;
+ if (off > size - 1)
+ abort();
+
+ if (hash)
+ off += libar2_encode_base64(&ret[off], hash, params.hashlen) - 1;
+ else
+ off += (size_t)sprintf(&ret[off], "*%zu", params.hashlen);
+
+ if (off > size - 1)
+ abort();
+
+ return ret;
+}
diff --git a/libar2simplified_encode_hash.c b/libar2simplified_encode_hash.c
new file mode 100644
index 0000000..da80ff6
--- /dev/null
+++ b/libar2simplified_encode_hash.c
@@ -0,0 +1,17 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+char *
+libar2simplified_encode_hash(const struct libar2_argon2_parameters *params, void *hash)
+{
+ size_t size = libar2_encode_base64(NULL, hash, params->hashlen);
+ char *ret = malloc(size);
+ if (!ret) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ if (libar2_encode_base64(ret, hash, params->hashlen) != size)
+ abort();
+ return ret;
+}
diff --git a/libar2simplified_hash.c b/libar2simplified_hash.c
new file mode 100644
index 0000000..bdc5dd2
--- /dev/null
+++ b/libar2simplified_hash.c
@@ -0,0 +1,383 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#include <pthread.h>
+#include <semaphore.h>
+
+
+struct thread_data {
+ pthread_t thread;
+ pthread_mutex_t mutex;
+ sem_t semaphore;
+ pthread_mutex_t *master_mutex;
+ sem_t *master_semaphore;
+ int *master_needs_a_thread;
+ int error;
+ void (*function)(void *data);
+ void *function_input;
+};
+
+struct user_data {
+ struct thread_data *threads;
+ size_t nthreads;
+ int need_a_thread;
+ pthread_mutex_t master_mutex;
+ sem_t master_semaphore;
+};
+
+
+static void *
+alignedalloc(size_t num, size_t size, size_t alignment, size_t extra)
+{
+ void *ptr;
+ int err;
+ if (num > (SIZE_MAX - extra) / size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ if (alignment < sizeof(void *))
+ alignment = sizeof(void *);
+ err = posix_memalign(&ptr, alignment, num * size + extra);
+ if (err) {
+ errno = err;
+ return NULL;
+ } else {
+ return ptr;
+ }
+}
+
+
+static void *
+allocate(size_t num, size_t size, size_t alignment, struct libar2_context *ctx)
+{
+ size_t pad = (alignment - ((2 * sizeof(size_t)) & (alignment - 1))) & (alignment - 1);
+ char *ptr = alignedalloc(num, size, alignment, pad + 2 * sizeof(size_t));
+ if (ptr) {
+ ptr = &ptr[pad];
+ *(size_t *)ptr = pad;
+ ptr = &ptr[sizeof(size_t)];
+ *(size_t *)ptr = num * size;
+ ptr = &ptr[sizeof(size_t)];
+ }
+ (void) ctx;
+ return ptr;
+}
+
+
+static void
+deallocate(void *ptr, struct libar2_context *ctx)
+{
+ char *p = ptr;
+ p -= sizeof(size_t);
+ libar2_erase(ptr, *(size_t *)p);
+ p -= sizeof(size_t);
+ p -= *(size_t *)p;
+ free(p);
+ (void) ctx;
+}
+
+
+static void *
+thread_loop(void *data_)
+{
+ struct thread_data *data = data_;
+ int err;
+ void (*function)(void *data);
+ void *function_input;
+
+ for (;;) {
+ if (sem_wait(&data->semaphore)) {
+ data->error = errno;
+ return NULL;
+ }
+
+ err = pthread_mutex_lock(&data->mutex);
+ if (err) {
+ data->error = err;
+ return NULL;
+ }
+ function_input = data->function_input;
+ function = data->function;
+ pthread_mutex_unlock(&data->mutex);
+
+ if (function) {
+ function(function_input);
+
+ err = pthread_mutex_lock(data->master_mutex);
+ if (err) {
+ data->error = err;
+ return NULL;
+ }
+
+ err = pthread_mutex_lock(&data->mutex);
+ if (err) {
+ pthread_mutex_unlock(data->master_mutex);
+ data->error = err;
+ return NULL;
+ }
+ data->function = NULL;
+ data->function_input = NULL;
+ pthread_mutex_unlock(&data->mutex);
+ if (*data->master_needs_a_thread) {
+ *data->master_needs_a_thread = 0;
+ if (sem_post(data->master_semaphore)) {
+ err = errno;
+ pthread_mutex_unlock(data->master_mutex);
+ data->error = err;
+ return NULL;
+ }
+ }
+ pthread_mutex_unlock(data->master_mutex);
+ }
+ }
+}
+
+
+static int
+run_thread(size_t index, void (*function)(void *arg), void *arg, struct libar2_context *ctx)
+{
+ struct user_data *data = ctx->user_data;
+ int err;
+ err = pthread_mutex_lock(&data->threads[index].mutex);
+ if (err) {
+ errno = err;
+ return -1;
+ }
+ if (data->threads[index].error) {
+ err = data->threads[index].error;
+ pthread_mutex_unlock(&data->threads[index].mutex);
+ errno = err;
+ return -1;
+ }
+ data->threads[index].function_input = arg;
+ data->threads[index].function = function;
+ if (sem_post(&data->threads[index].semaphore)) {
+ return -1;
+ }
+ pthread_mutex_unlock(&data->threads[index].mutex);
+ return 0;
+}
+
+
+static int
+destroy_thread_pool(struct libar2_context *ctx)
+{
+ struct user_data *data = ctx->user_data;
+ size_t i;
+ int ret = 0, err;
+ for (i = data->nthreads; i--;)
+ if (run_thread(i, pthread_exit, NULL, ctx))
+ return -1;
+ for (i = data->nthreads; i--;) {
+ pthread_join(data->threads[i].thread, NULL);
+ err = pthread_mutex_lock(&data->threads[i].mutex);
+ if (err)
+ ret = err;
+ sem_destroy(&data->threads[i].semaphore);
+ if (data->threads[i].error)
+ ret = data->threads[i].error;
+ pthread_mutex_unlock(&data->threads[i].mutex);
+ pthread_mutex_destroy(&data->threads[i].mutex);
+ }
+ free(data->threads);
+ sem_destroy(&data->master_semaphore);
+ pthread_mutex_destroy(&data->master_mutex);
+ return ret;
+}
+
+
+static int
+init_thread_pool(size_t desired, size_t *createdp, struct libar2_context *ctx)
+{
+ struct user_data *data = ctx->user_data;
+ int err;
+ size_t i;
+ long int nproc, nproc_limit;
+#ifdef __linux__
+ char path[sizeof("/sys/devices/system/cpu/cpu") + 3 * sizeof(nproc)];
+#endif
+
+#ifdef TODO
+ if (desired < 2) {
+ *createdp = 0;
+ return 0;
+ }
+#endif
+
+ nproc = sysconf(_SC_NPROCESSORS_ONLN);
+#ifdef __linux__
+ if (nproc < 1) {
+ nproc_limit = desired > LONG_MAX ? LONG_MAX : (long int)desired;
+ for (nproc = 0; nproc < nproc_limit; nproc++) {
+ sprintf(path, "%s%li", "/sys/devices/system/cpu/cpu", nproc);
+ if (access(path, F_OK))
+ break;
+ }
+ }
+#endif
+ if (nproc < 1)
+ nproc = FALLBACK_NPROC;
+
+ if (nproc == 1) {
+ *createdp = 0;
+ return 0;
+ }
+
+ data->nthreads = (size_t)nproc < desired ? (size_t)nproc : desired;
+ *createdp = data->nthreads;
+
+ data->threads = alignedalloc(data->nthreads, sizeof(*data->threads), ALIGNOF(struct thread_data), 0);
+ if (!data->threads)
+ return -1;
+
+ err = pthread_mutex_init(&data->master_mutex, NULL);
+ if (err) {
+ free(data->threads);
+ return -1;
+ }
+ err = sem_init(&data->master_semaphore, 0, 0);
+ if (err) {
+ pthread_mutex_destroy(&data->master_mutex);
+ free(data->threads);
+ return -1;
+ }
+ data->need_a_thread = 0;
+
+ for (i = 0; i < data->nthreads; i++) {
+ memset(&data->threads[i], 0, sizeof(data->threads[i]));
+ data->threads[i].master_mutex = &data->master_mutex;
+ data->threads[i].master_semaphore = &data->master_semaphore;
+ data->threads[i].master_needs_a_thread = &data->need_a_thread;
+ err = pthread_mutex_init(&data->threads[i].mutex, NULL);
+ if (err)
+ goto fail_post_mutex;
+ if (sem_init(&data->threads[i].semaphore, 0, 0)) {
+ err = errno;
+ goto fail_post_cond;
+ }
+ err = pthread_create(&data->threads[i].thread, NULL, thread_loop, &data->threads[i]);
+ if (err) {
+ sem_destroy(&data->threads[i].semaphore);
+ fail_post_cond:
+ pthread_mutex_destroy(&data->threads[i].mutex);
+ fail_post_mutex:
+ data->nthreads = i;
+ destroy_thread_pool(ctx);
+ errno = err;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+static int
+set_need_a_thread(struct user_data *data, int need)
+{
+ int err;
+ err = pthread_mutex_lock(&data->master_mutex);
+ if (err) {
+ errno = err;
+ return -1;
+ }
+ data->need_a_thread = need;
+ pthread_mutex_unlock(&data->master_mutex);
+ return 0;
+}
+
+
+static int
+await_some_thread(struct user_data *data)
+{
+ int err, need_a_thread;
+ err = pthread_mutex_lock(&data->master_mutex);
+ if (err) {
+ errno = err;
+ return -1;
+ }
+ need_a_thread = data->need_a_thread;
+ pthread_mutex_unlock(&data->master_mutex);
+ if (need_a_thread) {
+ if (sem_wait(&data->master_semaphore)) {
+ err = errno;
+ pthread_mutex_unlock(&data->master_mutex);
+ errno = err;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+static size_t
+await_threads(size_t *indices, size_t n, size_t require, struct libar2_context *ctx)
+{
+ struct user_data *data = ctx->user_data;
+ size_t i, ret = 0, first = 0;
+ int err;
+ for (;;) {
+ if (set_need_a_thread(data, 1))
+ return 0;
+ for (i = first; i < data->nthreads; i++) {
+ err = pthread_mutex_lock(&data->threads[i].mutex);
+ if (err) {
+ errno = err;
+ return 0;
+ }
+ if (!data->threads[i].function) {
+ if (ret++ < n)
+ indices[ret - 1] = i;
+ first += (i == first);
+ }
+ if (data->threads[i].error) {
+ errno = data->threads[i].error;
+ return 0;
+ }
+ pthread_mutex_unlock(&data->threads[i].mutex);
+ }
+ if (ret >= require) {
+ if (set_need_a_thread(data, 0))
+ return 0;
+ return ret;
+ }
+ if (await_some_thread(data))
+ return 0;
+ }
+}
+
+
+static size_t
+get_ready_threads(size_t *indices, size_t n, struct libar2_context *ctx)
+{
+ return await_threads(indices, n, 1, ctx);
+}
+
+
+static int
+join_thread_pool(struct libar2_context *ctx)
+{
+ struct user_data *data = ctx->user_data;
+ return await_threads(NULL, 0, data->nthreads, ctx) ? 0 : -1;
+}
+
+
+int
+libar2simplified_hash(void *hash, void *msg, size_t msglen, struct libar2_argon2_parameters *params)
+{
+ struct user_data ctx_data;
+ struct libar2_context ctx;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.user_data = &ctx_data;
+ ctx.autoerase_message = 1;
+ ctx.autoerase_salt = 1;
+ ctx.allocate = allocate;
+ ctx.deallocate = deallocate;
+ ctx.init_thread_pool = init_thread_pool;
+ ctx.get_ready_threads = get_ready_threads;
+ ctx.run_thread = run_thread;
+ ctx.join_thread_pool = join_thread_pool;
+ ctx.destroy_thread_pool = destroy_thread_pool;
+
+ return libar2_hash(hash, msg, msglen, params, &ctx);
+}
diff --git a/mk/linux.mk b/mk/linux.mk
new file mode 100644
index 0000000..d016d31
--- /dev/null
+++ b/mk/linux.mk
@@ -0,0 +1,4 @@
+LIBEXT = so
+LIBFLAGS = -shared -Wl,-soname,lib$(LIB_NAME).$(LIBEXT).$(LIB_MAJOR)
+LIBMAJOREXT = $(LIBEXT).$(LIB_MAJOR)
+LIBMINOREXT = $(LIBEXT).$(LIB_VERSION)
diff --git a/mk/macos.mk b/mk/macos.mk
new file mode 100644
index 0000000..bd92de6
--- /dev/null
+++ b/mk/macos.mk
@@ -0,0 +1,4 @@
+LIBEXT = dylib
+LIBFLAGS = -dynamiclib
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
diff --git a/mk/windows.mk b/mk/windows.mk
new file mode 100644
index 0000000..e9602e1
--- /dev/null
+++ b/mk/windows.mk
@@ -0,0 +1,4 @@
+LIBEXT = dll
+LIBFLAGS = -mdll
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
diff --git a/test.c b/test.c
new file mode 100644
index 0000000..6c4bf6f
--- /dev/null
+++ b/test.c
@@ -0,0 +1,136 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+#define MEM(S) S, sizeof(S) - 1
+
+
+#define assert(TRUTH) assert_(TRUTH, #TRUTH, __LINE__)
+#define assert_streq(RESULT, EXPECT) assert_streq_(RESULT, EXPECT, #RESULT, __LINE__)
+#define assert_zueq(RESULT, EXPECT) assert_zueq_(RESULT, EXPECT, #RESULT, __LINE__)
+
+static int from_lineno = 0;
+
+
+static int
+nulstrcmp(const char *a, const char *b)
+{
+ return !a ? -!!b : !b ? +1 : strcmp(a, b);
+}
+
+
+static void
+assert_(int truth, const char *truthstr, int lineno)
+{
+ if (!truth) {
+ if (from_lineno)
+ fprintf(stderr, "Assertion at line %i, from line %i failed: %s\n", lineno, from_lineno, truthstr);
+ else
+ fprintf(stderr, "Assertion at line %i failed: %s\n", lineno, truthstr);
+ fprintf(stderr, "\terrno: %i (%s)\n", errno, strerror(errno));
+ exit(1);
+ }
+}
+
+
+static void
+assert_streq_(const char *result, const char *expect, const char *code, int lineno)
+{
+ if (nulstrcmp(result, expect)) {
+ if (from_lineno)
+ fprintf(stderr, "Assertion at line %i, form line %i failed:\n", lineno, from_lineno);
+ else
+ fprintf(stderr, "Assertion at line %i failed:\n", lineno);
+ fprintf(stderr, "\tcode: %s\n", code);
+ fprintf(stderr, "\tresult: %s\n", result);
+ fprintf(stderr, "\texpected: %s\n", expect);
+ fprintf(stderr, "\terrno: %i (%s)\n", errno, strerror(errno));
+ exit(1);
+ }
+}
+
+
+static void
+assert_zueq_(size_t result, size_t expect, const char *code, int lineno)
+{
+ if (result != expect) {
+ if (from_lineno)
+ fprintf(stderr, "Assertion at line %i, form line %i failed:\n", lineno, from_lineno);
+ else
+ fprintf(stderr, "Assertion at line %i failed:\n", lineno);
+ fprintf(stderr, "\tcode: %s\n", code);
+ fprintf(stderr, "\tresult: %zu\n", result);
+ fprintf(stderr, "\texpected: %zu\n", expect);
+ fprintf(stderr, "\terrno: %i (%s)\n", errno, strerror(errno));
+ exit(1);
+ }
+}
+
+
+static void
+check_hash(const char *pwd_, size_t pwdlen, const char *hash, int lineno)
+{
+ struct libar2_argon2_parameters *params;
+ char *output[512], pwd[512], *tag_expect, *tag_got, *paramstr;
+ size_t taglen;
+
+ from_lineno = lineno;
+ errno = 0;
+
+ strcpy(pwd, pwd_);
+
+ assert(!!(params = libar2simplified_decode(hash, &tag_expect, NULL, NULL)));
+ assert_zueq(libar2_decode_base64(tag_expect, output, &taglen), strlen(tag_expect));
+ assert_zueq(taglen, params->hashlen);
+ assert(!!(paramstr = libar2simplified_encode(params, output)));
+ assert_streq(paramstr, hash);
+ free(paramstr);
+
+ assert(!libar2simplified_hash(output, pwd, pwdlen, params));
+ tag_got = libar2simplified_encode_hash(params, output);
+ free(params);
+ assert_streq(tag_got, tag_expect);
+ free(tag_got);
+
+ from_lineno = 0;
+}
+
+
+int
+main(void)
+{
+#define CHECK(PWD, HASH)\
+ check_hash(MEM(PWD), HASH, __LINE__)
+
+ CHECK("\x00", "$argon2d$v=16$m=8,t=1,p=1$ICAgICAgICA$Eyx1BxGazSuPQoy7osaQuo20Dw9VI97dYUOgcC3cMgw");
+ CHECK("test", "$argon2i$v=19$m=4096,t=3,p=1$fn5/f35+f38$9tqKA4WMEsSAOEUwatjxvJLSqL1j0GQkgbsfnpresDw");
+ CHECK("\x00", "$argon2id$v=16$m=8,t=1,p=1$ICAgICAgICA$fXq1aUbp9yhbn+EQc4AzUUE6AKnHAkvzIXsN6J4ukvE");
+ CHECK("", "$argon2d$v=16$m=8,t=1,p=1$ICAgICAgICA$X54KZYxUSfMUihzebb70sKbheabHilo8gsUldrVU4IU");
+ CHECK("", "$argon2d$v=16$m=8,t=1,p=1$ICAgICAgICA$NjODMrWrS7zeivNNpHsuxD9c6uDmUQ6YqPRhb8H5DSNw9n683FUCJZ3tyxgfJpYYANI+01WT/S5zp1UVs+qNRwnkdEyLKZMg+DIOXVc9z1po9ZlZG8+Gp4g5brqfza3lvkR9vw");
+ CHECK("", "$argon2ds$v=16$m=8,t=1,p=1$ICAgICAgICA$zgdykk9ZjN5VyrW0LxGw8LmrJ1Z6fqSC+3jPQtn4n0s");
+
+ CHECK("password", "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ");
+ CHECK("password", "$argon2i$m=256,t=2,p=1$c29tZXNhbHQ$/U3YPXYsSb3q9XxHvc0MLxur+GP960kN9j7emXX8zwY");
+ CHECK("password", "$argon2i$m=65536,t=1,p=1$c29tZXNhbHQ$gWMFUrjzsfSM2xmSxMZ4ZD1JCytetP9sSzQ4tWIXJLI");
+ CHECK("password", "$argon2i$m=65536,t=4,p=1$c29tZXNhbHQ$8hLwFhXm6110c03D70Ct4tUdBSRo2MaUQKOh8sHChHs");
+ CHECK("differentpassword", "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ$6ckCB0tnVFMaOgvlGeW69ASzDOabPwGsO/ISKZYBCaM");
+ CHECK("password", "$argon2i$m=65536,t=2,p=1$ZGlmZnNhbHQ$eaEDuQ/orvhXDLMfyLIiWXeJFvgza3vaw4kladTxxJc");
+
+ CHECK("password", "$argon2i$v=16$m=256,t=2,p=1$c29tZXNhbHQ$/U3YPXYsSb3q9XxHvc0MLxur+GP960kN9j7emXX8zwY");
+
+ CHECK("password", "$argon2i$v=19$m=256,t=2,p=1$c29tZXNhbHQ$iekCn0Y3spW+sCcFanM2xBT63UP2sghkUoHLIUpWRS8");
+ CHECK("password", "$argon2i$v=19$m=65536,t=1,p=1$c29tZXNhbHQ$0WgHXE2YXhPr6uVgz4uUw7XYoWxRkWtvSsLaOsEbvs8");
+ CHECK("differentpassword", "$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$FK6NoBr+qHAMI1jc73xTWNkCEoK9iGY6RWL1n7dNIu4");
+
+ CHECK("password", "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4");
+ CHECK("password", "$argon2id$v=19$m=65536,t=1,p=1$c29tZXNhbHQ$9qWtwbpyPd3vm1rB1GThgPzZ3/ydHL92zKL+15XZypg");
+ CHECK("password", "$argon2id$v=19$m=65536,t=4,p=1$c29tZXNhbHQ$kCXUjmjvc5XMqQedpMTsOv+zyJEf5PhtGiUghW9jFyw");
+ CHECK("differentpassword", "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$C4TWUs9rDEvq7w3+J4umqA32aWKB1+DSiRuBfYxFj94");
+
+ CHECK("password", "$argon2i$m=256,t=2,p=2$c29tZXNhbHQ$tsEVYKap1h6scGt5ovl9aLRGOqOth+AMB+KwHpDFZPs");
+ CHECK("password", "$argon2i$v=16$m=256,t=2,p=2$c29tZXNhbHQ$tsEVYKap1h6scGt5ovl9aLRGOqOth+AMB+KwHpDFZPs");
+ CHECK("password", "$argon2i$v=19$m=256,t=2,p=2$c29tZXNhbHQ$T/XOJ2mh1/TIpJHfCdQan76Q5esCFVoT5MAeIM1Oq2E");
+ CHECK("password", "$argon2id$v=19$m=256,t=2,p=2$c29tZXNhbHQ$bQk8UB/VmZZF4Oo79iDXuL5/0ttZwg2f/5U52iv1cDc");
+
+ return 0;
+}