Hash passwords asynchronously in NIF
diff --git a/c_src/bcrypt_nif.c b/c_src/bcrypt_nif.c
index 076b4ba..4186ac7 100644
--- a/c_src/bcrypt_nif.c
+++ b/c_src/bcrypt_nif.c
@@ -14,6 +14,7 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -40,15 +41,32 @@
return task;
}
-task_t* alloc_init_task(task_type_t type, ERL_NIF_TERM ref)
+task_t* alloc_init_task(task_type_t type, ERL_NIF_TERM ref, ErlNifPid pid, int num_orig_terms, const ERL_NIF_TERM orig_terms[])
{
task_t* task = alloc_task(type);
+ task->pid = pid;
task->env = enif_alloc_env();
if (task->env == NULL) {
free_task(task);
return NULL;
}
+ if (type == HASH) {
+ assert(num_orig_terms == 2);
+ if (!enif_inspect_iolist_as_binary(
+ task->env, enif_make_copy(task->env, orig_terms[0]),
+ &task->data.hash.salt)) {
+ free_task(task);
+ return NULL;
+ }
+ if (!enif_inspect_iolist_as_binary(
+ task->env, enif_make_copy(task->env, orig_terms[1]),
+ &task->data.hash.password)) {
+ free_task(task);
+ return NULL;
+ }
+ }
+
task->ref = enif_make_copy(task->env, ref);
return task;
}
@@ -97,6 +115,7 @@
task = (task_t*)async_queue_pop(ctx->queue);
if (task->type == SHUTDOWN) {
+ free_task(task);
break;
} else if (task->type == HASH) {
result = hashpw(task);
@@ -108,8 +127,6 @@
free_task(task);
}
- // Cleanup the shutdown task
- free_task(task);
return NULL;
}
@@ -140,24 +157,33 @@
static ERL_NIF_TERM bcrypt_hashpw(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
- char pw[1024];
- char salt[1024];
- char *ret = NULL;
+ ctx_t *ctx;
+ task_t *task;
+ ErlNifPid pid;
- (void)memset(&pw, '\0', sizeof(pw));
- (void)memset(&salt, '\0', sizeof(salt));
-
- if (enif_get_string(env, argv[0], pw, sizeof(pw), ERL_NIF_LATIN1) < 1)
+ if (argc != 5)
return enif_make_badarg(env);
- if (enif_get_string(env, argv[1], salt, sizeof(salt), ERL_NIF_LATIN1) < 1)
+ bcrypt_privdata_t *priv = (bcrypt_privdata_t*)enif_priv_data(env);
+
+ if (!enif_get_resource(env, argv[0], priv->bcrypt_rt, (void**)(&ctx)))
return enif_make_badarg(env);
- if (NULL == (ret = bcrypt(pw, salt)) || 0 == strcmp(ret, ":")) {
+ if (!enif_is_ref(env, argv[1]))
return enif_make_badarg(env);
- }
- return enif_make_string(env, ret, ERL_NIF_LATIN1);
+ if (!enif_get_local_pid(env, argv[2], &pid))
+ return enif_make_badarg(env);
+
+ ERL_NIF_TERM orig_terms[] = { argv[4], argv[3] };
+ task = alloc_init_task(HASH, argv[1], pid, 2, orig_terms);
+
+ if (!task)
+ return enif_make_badarg(env);
+
+ async_queue_push(ctx->queue, task);
+
+ return enif_make_atom(env, "ok");
}
static ERL_NIF_TERM bcrypt_create_ctx(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
@@ -181,7 +207,7 @@
static ErlNifFunc bcrypt_nif_funcs[] =
{
{"encode_salt", 2, bcrypt_encode_salt},
- {"hashpw", 2, bcrypt_hashpw},
+ {"hashpw", 5, bcrypt_hashpw},
{"create_ctx", 0, bcrypt_create_ctx},
};
@@ -191,7 +217,7 @@
task_t *task = alloc_task(SHUTDOWN);
void *result = NULL;
- async_queue_push(ctx->queue, (void*)task);
+ async_queue_push(ctx->queue, task);
enif_thread_join(ctx->tid, &result);
async_queue_destroy(ctx->queue);
enif_thread_opts_destroy(ctx->topts);
diff --git a/src/bcrypt.erl b/src/bcrypt.erl
index 985f55a..86fa4e9 100644
--- a/src/bcrypt.erl
+++ b/src/bcrypt.erl
@@ -6,7 +6,7 @@
%% API
-export([start/0, stop/0]).
-export([mechanism/0]).
--export([gen_salt/0, gen_salt/1, hashpw/2, create_ctx/0]).
+-export([gen_salt/0, gen_salt/1, hashpw/2]).
start() -> application:start(bcrypt).
stop() -> application:stop(bcrypt).
@@ -18,7 +18,6 @@
gen_salt() -> do_gen_salt(mechanism()).
gen_salt(Rounds) -> do_gen_salt(mechanism(), Rounds).
hashpw(Password, Salt) -> do_hashpw(mechanism(), Password, Salt).
-create_ctx() -> do_create_ctx(mechanism()).
do_gen_salt(nif) -> bcrypt_nif_worker:gen_salt();
do_gen_salt(port) -> bcrypt_pool:gen_salt().
@@ -28,6 +27,3 @@
do_hashpw(nif, Password, Salt) -> bcrypt_nif_worker:hashpw(Password, Salt);
do_hashpw(port, Password, Salt) -> bcrypt_pool:hashpw(Password, Salt).
-
-do_create_ctx(nif) -> bcrypt_nif_worker:create_ctx();
-do_create_ctx(port) -> ok.
diff --git a/src/bcrypt_nif.erl b/src/bcrypt_nif.erl
index 1fb7181..af83b0e 100644
--- a/src/bcrypt_nif.erl
+++ b/src/bcrypt_nif.erl
@@ -22,7 +22,7 @@
%% API
-export([init/0]).
--export([gen_salt/1, hashpw/2, create_ctx/0]).
+-export([gen_salt/1, hashpw/5, create_ctx/0]).
-on_load(init/0).
@@ -71,10 +71,14 @@
%%--------------------------------------------------------------------
%% @doc Hash the specified password and the salt using the OpenBSD
%% Blowfish password hashing algorithm. Returns the hashed password.
-%% @spec hashpw(Password::binary(), Salt::binary()) -> string()
+%% @spec hashpw(Ctx::term(),
+%% Ref::reference(),
+%% Pid::pid(),
+%% Password::binary(),
+%% Salt::binary()) -> string()
%% @end
%%--------------------------------------------------------------------
-hashpw(_Password, _Salt) ->
+hashpw(_Ctx, _Ref, _Pid, _Password, _Salt) ->
nif_stub_error(?LINE).
nif_stub_error(Line) ->
diff --git a/src/bcrypt_nif_worker.erl b/src/bcrypt_nif_worker.erl
index 0765ad3..e849597 100644
--- a/src/bcrypt_nif_worker.erl
+++ b/src/bcrypt_nif_worker.erl
@@ -8,13 +8,15 @@
-export([start_link/0]).
-export([gen_salt/0, gen_salt/1]).
-export([hashpw/2]).
--export([create_ctx/0]).
%% gen_server
-export([init/1, code_change/3, terminate/2,
handle_call/3, handle_cast/2, handle_info/2]).
--record(state, {default_log_rounds}).
+-record(state, {
+ default_log_rounds,
+ context
+ }).
start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
@@ -23,12 +25,11 @@
gen_server:call(?MODULE, {gen_salt, Rounds}, infinity).
hashpw(Password, Salt) ->
gen_server:call(?MODULE, {hashpw, Password, Salt}, infinity).
-create_ctx() ->
- gen_server:call(?MODULE, create_ctx, infinity).
init([]) ->
{ok, Default} = application:get_env(bcrypt, default_log_rounds),
- {ok, #state{default_log_rounds = Default}}.
+ Ctx = bcrypt_nif:create_ctx(),
+ {ok, #state{default_log_rounds = Default, context = Ctx}}.
terminate(shutdown, _) -> ok.
@@ -36,10 +37,15 @@
{reply, {ok, bcrypt_nif:gen_salt(R)}, State};
handle_call({gen_salt, R}, _From, State) ->
{reply, {ok, bcrypt_nif:gen_salt(R)}, State};
-handle_call({hashpw, Password, Salt}, _From, State) ->
- {reply, {ok, bcrypt_nif:hashpw(Password, Salt)}, State};
-handle_call(create_ctx, _From, State) ->
- {reply, {ok, bcrypt_nif:create_ctx()}, State};
+handle_call({hashpw, Password, Salt}, _From, #state{context=Ctx}=State) ->
+ Ref = make_ref(),
+ ok = bcrypt_nif:hashpw(Ctx, Ref, self(), Password, Salt),
+ receive
+ {ok, Ref, Result} ->
+ {reply, {ok, Result}, State};
+ {error, Ref, Result} ->
+ {reply, {error, Result}, State}
+ end;
handle_call(Msg, _, _) -> exit({unknown_call, Msg}).
handle_cast(Msg, _) -> exit({unknown_cast, Msg}).
handle_info(Msg, _) -> exit({unknown_info, Msg}).