Rename main NIF module to bcrypt_nif and use rebar-generated .app file
diff --git a/.gitignore b/.gitignore
index 69211a7..f2f2a9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 *.beam
 ebin/*.beam
+ebin/bcrypt.app
 _build/
 lib/bcrypt/bcrypt
 
diff --git a/c_src/bcrypt_nif.c b/c_src/bcrypt_nif.c
index 27a7087..5dcf564 100644
--- a/c_src/bcrypt_nif.c
+++ b/c_src/bcrypt_nif.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Hunter Morris <huntermorris@gmail.com>
+ * Copyright (c) 2011 Hunter Morris <hunter.morris@smarkets.com>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -48,7 +48,7 @@
 
     encode_salt((char *)bin.data, (u_int8_t*)csalt.data, csalt.size, log_rounds);
     enif_release_binary(&csalt);
-    
+
     return enif_make_string(env, (char *)bin.data, ERL_NIF_LATIN1);
 }
 
@@ -57,10 +57,10 @@
     char pw[1024];
     char salt[1024];
     char *ret = NULL;
-    
+
     (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)
         return enif_make_badarg(env);
 
@@ -70,7 +70,7 @@
     if (NULL == (ret = bcrypt(pw, salt)) || 0 == strcmp(ret, ":")) {
         return enif_make_badarg(env);
     }
-    
+
     return enif_make_string(env, ret, ERL_NIF_LATIN1);
 }
 
@@ -80,4 +80,4 @@
     {"hashpw", 2, hashpw}
 };
 
-ERL_NIF_INIT(bcrypt, bcrypt_nif_funcs, NULL, NULL, NULL, NULL)
+ERL_NIF_INIT(bcrypt_nif, bcrypt_nif_funcs, NULL, NULL, NULL, NULL)
diff --git a/ebin/.gitignore b/ebin/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ebin/.gitignore
diff --git a/ebin/bcrypt.app b/ebin/bcrypt.app
deleted file mode 100644
index d58b8c7..0000000
--- a/ebin/bcrypt.app
+++ /dev/null
@@ -1,12 +0,0 @@
-%%% This is the application resource file (.app file) for the bcrypt
-%%% application.
-{application, bcrypt,
-  [{description, "An Erlang wrapper for the OpenBSD password scheme, bcrypt."},
-   {vsn, "0.2.0"},
-   {modules, [bcrypt]},
-   {registered, [bcrypt]},
-   {env, []},
-   {applications, [kernel, stdlib, crypto]},
-   {mod, {bcrypt, []}}
-  ]
-}.
diff --git a/rebar.config b/rebar.config
new file mode 100644
index 0000000..87ccef3
--- /dev/null
+++ b/rebar.config
@@ -0,0 +1,3 @@
+%% -*- mode: erlang;erlang-indent-level: 2;indent-tabs-mode: nil -*-
+{so_name, "bcrypt_nif.so"}.
+{erl_opts, [debug_info]}.
diff --git a/src/bcrypt.app.src b/src/bcrypt.app.src
new file mode 100644
index 0000000..b43f3ea
--- /dev/null
+++ b/src/bcrypt.app.src
@@ -0,0 +1,12 @@
+%% -*- mode: erlang;erlang-indent-level: 2;indent-tabs-mode: nil -*-
+{application, bcrypt,
+ [{description, "An Erlang wrapper (NIF or port program) for the OpenBSD password scheme, bcrypt."},
+  {vsn, "0.3.0"},
+  {registered, []},
+  {applications, [kernel, stdlib, crypto]},
+  {env, [
+         % Default number of 'rounds', defining the hashing complexity
+         {default_log_rounds, 12}
+        ]}
+ ]
+}.
diff --git a/src/bcrypt.erl b/src/bcrypt.erl
index 2a65f35..b69f28d 100644
--- a/src/bcrypt.erl
+++ b/src/bcrypt.erl
@@ -1,5 +1,5 @@
-%% @author Hunter Morris <huntermorris@gmail.com>
-%% @copyright 2009 Hunter Morris
+%% @author Hunter Morris <hunter.morris@smarkets.com>
+%% @copyright 2011 Hunter Morris
 %%
 %% @doc Wrapper around the OpenBSD Blowfish password hashing algorithm, as
 %% described in "A Future-Adaptable Password Scheme" by Niels Provos and
@@ -37,7 +37,13 @@
 %% @end
 %%--------------------------------------------------------------------
 init() ->
-    erlang:load_nif(filename:join(code:priv_dir(?MODULE), atom_to_list(?MODULE) ++ "_drv"), 0).
+    Dir = case code:priv_dir(bcrypt) of
+              {error, bad_name} -> "../priv";
+              Priv              -> Priv
+          end,
+    erlang:load_nif(filename:join(Dir, "bcrypt"), 0).
+
+nif_stub_error(Line) -> erlang:nif_error({nif_not_loaded,module,?MODULE,line,Line}).
 
 %%--------------------------------------------------------------------
 %% @doc Generate a salt with the default number of rounds, 12.
@@ -62,7 +68,7 @@
     encode_salt(R, LogRounds).
 
 encode_salt(_R, _LogRounds) ->
-    nif_error(?LINE).
+    nif_stub_error(?LINE).
 
 %%--------------------------------------------------------------------
 %% @doc Hash the specified password and the salt using the OpenBSD
@@ -74,7 +80,4 @@
     hashpw(Password, Salt).
 
 hashpw(_Password, _Salt) ->
-    nif_error(?LINE).
-
-nif_error(Line) ->
-    exit({nif_not_loaded, module, ?MODULE, line, Line}).
+    nif_stub_error(?LINE).
diff --git a/src/bcrypt_nif.erl b/src/bcrypt_nif.erl
new file mode 100644
index 0000000..5c34901
--- /dev/null
+++ b/src/bcrypt_nif.erl
@@ -0,0 +1,91 @@
+%% @author Hunter Morris <hunter.morris@smarkets.com>
+%% @copyright 2011 Hunter Morris
+%%
+%% @doc Wrapper around the OpenBSD Blowfish password hashing algorithm, as
+%% described in "A Future-Adaptable Password Scheme" by Niels Provos and
+%% David Mazieres: http://www.openbsd.org/papers/bcrypt-paper.ps
+%% @end
+%%
+%% Permission to use, copy, modify, and 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.
+-module(bcrypt_nif).
+-author('Hunter Morris <hunter.morris@smarkets.com>').
+
+%% API
+-export([init/0]).
+-export([gen_salt/0, gen_salt/1]).
+-export([hash/2, hashpw/2]).
+
+-define(DEFAULT_LOG_ROUNDS, 12).
+-define(MAX_LOG_ROUNDS(L), L < 32).
+-define(MIN_LOG_ROUNDS(L), L > 3).
+
+-on_load(init/0).
+
+%%--------------------------------------------------------------------
+%% @doc Load the bcrypt NIFs
+%% @spec init() -> ok
+%% @end
+%%--------------------------------------------------------------------
+init() ->
+    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,
+    erlang:load_nif(filename:join(Dir, "bcrypt_nif"), 0).
+
+nif_stub_error(Line) ->
+    erlang:nif_error({nif_not_loaded, module, ?MODULE, line, Line}).
+
+%%--------------------------------------------------------------------
+%% @doc Generate a salt with the default number of rounds, 12.
+%% @see gen_salt/1
+%% @spec gen_salt() -> string()
+%% @end
+%%--------------------------------------------------------------------
+gen_salt() ->
+    gen_salt(?DEFAULT_LOG_ROUNDS).
+
+%%--------------------------------------------------------------------
+%% @doc Generate a random text salt for use with hashpw/3. LogRounds
+%% defines the complexity of the hashing, increasing the cost as
+%% 2^log_rounds.
+%% @spec gen_salt(integer()) -> string()
+%% @end
+%%--------------------------------------------------------------------
+gen_salt(LogRounds) when is_integer(LogRounds),
+                         ?MAX_LOG_ROUNDS(LogRounds),
+                         ?MIN_LOG_ROUNDS(LogRounds) ->
+    R = crypto:rand_bytes(16),
+    encode_salt(R, LogRounds).
+
+encode_salt(_R, _LogRounds) ->
+    nif_stub_error(?LINE).
+
+%%--------------------------------------------------------------------
+%% @doc Hash the specified password and the salt using the OpenBSD
+%% Blowfish password hashing algorithm. Returns the hashed password.
+%% @spec hash(Password::binary(), Salt::binary()) -> string()
+%% @end
+%%--------------------------------------------------------------------
+hash(Password, Salt) when is_binary(Password), is_binary(Salt) ->
+    hashpw(Password, Salt).
+
+hashpw(_Password, _Salt) ->
+    nif_stub_error(?LINE).