This repository has been archived by the owner. It is now read-only.
Permalink
Show file tree
Hide file tree
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Add bcrypt_pool for pooling a set of 'worker' port processes
- Loading branch information
Showing
7 changed files
with
212 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@@ -5,6 +5,7 @@ _build/ | ||
priv/bcrypt | ||
priv/bcrypt_nif.so | ||
logs/* | ||
.eunit | ||
|
||
## libtool | ||
.deps | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@@ -1,10 +1,22 @@ | ||
CC = gcc | ||
CFLAGS = -g -O2 -Wall | ||
ERLANG_LIBS = -lerl_interface -lei -lpthread | ||
|
||
BCRYPTLIBS = c_src/bcrypt_port.o c_src/bcrypt.o c_src/blowfish.o | ||
|
||
all: compile | ||
|
||
compile_port: priv/bcrypt | ||
|
||
priv/bcrypt: $(BCRYPTLIBS) | ||
$(CC) $(CFLAGS) $(BCRYPTLIBS) $(ERLANG_LIBS) -o $@ | ||
|
||
compile: | ||
@ ./rebar compile | ||
|
||
tests: | ||
@ ./rebar eunit | ||
|
||
clean: | ||
@ ./rebar clean | ||
@ ./rebar clean | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@@ -1,8 +1,8 @@ | ||
%% -*- mode: erlang;erlang-indent-level: 2;indent-tabs-mode: nil -*- | ||
{so_specs, | ||
[{"priv/bcrypt_nif.so", | ||
["c_src/blowfish.c", "c_src/bcrypt.c", "c_src/bcrypt_nif.c"]}, | ||
{"priv/bcrypt", | ||
["c_src/blowfish.c", "c_src/bcrypt.c", "c_src/bcrypt_port.c"]}]}. | ||
{so_name, "bcrypt_nif.so"}. | ||
{port_sources, ["c_src/blowfish.c", "c_src/bcrypt.c", "c_src/bcrypt_nif.c"]}. | ||
|
||
{erl_opts, [debug_info]}. | ||
|
||
{pre_hooks, [{clean, "rm -f priv/bcrypt c_src/bcrypt_port.o"}, | ||
{compile, "make compile_port"}]}. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@@ -10,7 +10,7 @@ | ||
{default_log_rounds, 12}, | ||
|
||
% Mechanism to use 'nif' or 'port' | ||
{mechanism, nif}, | ||
{mechanism, port}, | ||
|
||
% Size of port program pool | ||
{pool_size, 4} | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@@ -0,0 +1,81 @@ | ||
%% Copyright (c) 2011 Hunter Morris | ||
%% Distributed under the MIT license; see LICENSE for details. | ||
-module(bcrypt_pool). | ||
-author('Hunter Morris <huntermorris@gmail.com>'). | ||
|
||
-behaviour(gen_server). | ||
|
||
-export([start_link/0, available/1]). | ||
-export([gen_salt/0, gen_salt/1]). | ||
-export([hashpw/2]). | ||
|
||
%% gen_server | ||
-export([init/1, code_change/3, terminate/2, | ||
handle_call/3, handle_cast/2, handle_info/2]). | ||
|
||
-record(state, { | ||
size = 0, | ||
busy = 0, | ||
requests = queue:new(), | ||
ports = queue:new() | ||
}). | ||
|
||
-record(req, {mon, from}). | ||
|
||
start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). | ||
available(Pid) -> gen_server:cast(?MODULE, {available, Pid}). | ||
|
||
gen_salt() -> do_call(fun bcrypt_port:gen_salt/1, []). | ||
gen_salt(Rounds) -> do_call(fun bcrypt_port:gen_salt/2, [Rounds]). | ||
hashpw(Password, Salt) -> do_call(fun bcrypt_port:hashpw/3, [Password, Salt]). | ||
|
||
init([]) -> | ||
{ok, Size} = application:get_env(bcrypt, pool_size), | ||
{ok, #state{size = Size}}. | ||
|
||
terminate(shutdown, _) -> ok. | ||
|
||
handle_call(request, {RPid, _} = From, #state{ports = P} = State) -> | ||
case queue:out(P) of | ||
{empty, P} -> | ||
#state{size = Size, busy = B, requests = R} = State, | ||
B1 = | ||
if Size > B -> | ||
{ok, _} = bcrypt_port_sup:start_child(), | ||
B + 1; | ||
true -> | ||
B | ||
end, | ||
RRef = erlang:monitor(process, RPid), | ||
R1 = queue:in(#req{mon = RRef, from = From}, R), | ||
{noreply, State#state{requests = R1, | ||
busy = B1}}; | ||
{{value, PPid}, P1} -> | ||
#state{busy = B} = State, | ||
{reply, {ok, PPid}, State#state{busy = B + 1, ports = P1}} | ||
end; | ||
handle_call(Msg, _, _) -> exit({unknown_call, Msg}). | ||
|
||
handle_cast( | ||
{available, Pid}, | ||
#state{requests = R, ports = P, busy = B} = S) -> | ||
case queue:out(R) of | ||
{empty, R} -> | ||
{noreply, S#state{ports = queue:in(Pid, P), busy = B - 1}}; | ||
{{value, #req{mon = Mon, from = F}}, R1} -> | ||
true = erlang:demonitor(Mon, [flush]), | ||
gen_server:reply(F, {ok, Pid}), | ||
{noreply, S#state{requests = R1}} | ||
end; | ||
handle_cast(Msg, _) -> exit({unknown_cast, Msg}). | ||
|
||
handle_info({'DOWN', Ref, process, _Pid, _Reason}, #state{requests = R} = State) -> | ||
R1 = queue:from_list(lists:keydelete(Ref, #req.mon, queue:to_list(R))), | ||
{noreply, State#state{requests = R1}}; | ||
handle_info(Msg, _) -> exit({unknown_info, Msg}). | ||
code_change(_OldVsn, State, _Extra) -> {ok, State}. | ||
|
||
do_call(F, Args0) -> | ||
{ok, Pid} = gen_server:call(?MODULE, request, infinity), | ||
Args = [Pid|Args0], | ||
apply(F, Args). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@@ -0,0 +1,108 @@ | ||
%% Copyright (c) 2011 Hunter Morris | ||
%% Distributed under the MIT license; see LICENSE for details. | ||
-module(bcrypt_port). | ||
-author('Hunter Morris <hunter.morris@smarkets.com>'). | ||
|
||
-behaviour(gen_server). | ||
|
||
%% API | ||
-export([start_link/0, stop/0]). | ||
-export([gen_salt/1, gen_salt/2]). | ||
-export([hashpw/3]). | ||
|
||
%% gen_server callbacks | ||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, | ||
code_change/3]). | ||
|
||
-record(state, {port, default_log_rounds}). | ||
|
||
-define(CMD_SALT, 0). | ||
-define(CMD_HASHPW, 1). | ||
-define(BCRYPT_ERROR(F, D), error_logger:error_msg(F, D)). | ||
-define(BCRYPT_WARNING(F, D), error_logger:warning_msg(F, D)). | ||
|
||
start_link() -> | ||
Dir = case code:priv_dir(bcrypt) of | ||
{error, bad_name} -> | ||
case code:which(bcrypt) of | ||
Filename when is_list(Filename) -> | ||
filename:join( | ||
[filename:dirname(Filename), "../priv"]); | ||
_ -> | ||
"../priv" | ||
end; | ||
Priv -> Priv | ||
end, | ||
Port = filename:join(Dir, "bcrypt"), | ||
gen_server:start_link(?MODULE, [Port], []). | ||
|
||
stop() -> gen_server:call(?MODULE, stop). | ||
|
||
gen_salt(Pid) -> | ||
R = crypto:rand_bytes(16), | ||
gen_server:call(Pid, {encode_salt, R}, infinity). | ||
|
||
gen_salt(Pid, LogRounds) -> | ||
R = crypto:rand_bytes(16), | ||
gen_server:call(Pid, {encode_salt, R, LogRounds}, infinity). | ||
|
||
hashpw(Pid, Password, Salt) -> | ||
gen_server:call(Pid, {hashpw, Password, Salt}, infinity). | ||
|
||
%%==================================================================== | ||
%% gen_server callbacks | ||
%%==================================================================== | ||
init([Filename]) -> | ||
case file:read_file_info(Filename) of | ||
{ok, _Info} -> | ||
Port = open_port( | ||
{spawn, Filename}, [{packet, 2}, binary, exit_status]), | ||
ok = bcrypt_pool:available(self()), | ||
{ok, Rounds} = application:get_env(bcrypt, default_log_rounds), | ||
{ok, #state{port = Port, default_log_rounds = Rounds}}; | ||
{error, Reason} -> | ||
?BCRYPT_ERROR("Can't open file ~p: ~p", [Filename, Reason]), | ||
{stop, error_opening_bcrypt_file} | ||
end. | ||
|
||
terminate(_Reason, #state{port=Port}) -> | ||
catch port_close(Port), | ||
ok. | ||
|
||
handle_call({encode_salt, R}, From, #state{default_log_rounds = LogRounds} = State) -> | ||
handle_call({encode_salt, R, LogRounds}, From, State); | ||
handle_call({encode_salt, R, LogRounds}, From, State) -> | ||
Port = State#state.port, | ||
Data = term_to_binary({?CMD_SALT, From, {R, LogRounds}}), | ||
port_command(Port, Data), | ||
{noreply, State}; | ||
handle_call({hashpw, Password, Salt}, From, State) -> | ||
Port = State#state.port, | ||
Data = term_to_binary({?CMD_HASHPW, From, {Password, Salt}}), | ||
port_command(Port, Data), | ||
{noreply, State}; | ||
handle_call(stop, _From, State) -> | ||
{stop, normal, ok, State}; | ||
handle_call(Msg, _, _) -> exit({unknown_call, Msg}). | ||
|
||
handle_cast(Msg, _) -> exit({unknown_cast, Msg}). | ||
|
||
code_change(_OldVsn, State, _Extra) -> {ok, State}. | ||
|
||
handle_info({Port, {data, Data}}, #state{port=Port}=State) -> | ||
{Cmd, To, Reply0} = binary_to_term(Data), | ||
Reply = | ||
case Reply0 of | ||
"Invalid salt" -> {error, invalid_salt}; | ||
"Invalid salt length" -> {error, invalid_salt_length}; | ||
"Invalid number of rounds" -> {error, invalid_rounds}; | ||
_ -> {ok, Reply0} | ||
end, | ||
gen_server:reply(To, Reply), | ||
ok = bcrypt_pool:available(self()), | ||
{noreply, State}; | ||
handle_info({Port, {exit_status, Status}}, #state{port=Port}=State) -> | ||
%% Rely on whomever is supervising this process to restart. | ||
?BCRYPT_WARNING("Port died: ~p", [Status]), | ||
{stop, port_died, State}; | ||
handle_info(Msg, _) -> exit({unknown_info, Msg}). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters