Skip to content

Commit d4408cc

Browse files
committed
Move apply_block to C and optimize hex formatting
1 parent b04f0cb commit d4408cc

File tree

10 files changed

+232
-122
lines changed

10 files changed

+232
-122
lines changed

.travis.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ elixir:
44
otp_release:
55
- 18.0
66
script:
7-
- HASH_IMPL=native mix test --trace
8-
- HASH_IMPL=embedded mix test --trace
7+
- STATE_IMPL=native UTIL_IMPL=native mix test --trace
8+
- STATE_IMPL=embedded UTIL_IMPL=embedded mix test --trace
99
after_success:
10-
- HASH_IMPL=embedded MIX_ENV=test mix coveralls.travis
10+
- STATE_IMPL=embedded UTIL_IMPL=embedded MIX_ENV=test mix coveralls.travis

Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ ifneq ($(OS),Windows_NT)
99
endif
1010
endif
1111

12-
_native/native_impl.so: clean
12+
_native: clean
1313
mkdir -p _native
14-
$(CC) -w $(CFLAGS) -shared $(LDFLAGS) -o $@ c_src/native_impl.c
14+
$(CC) -w $(CFLAGS) -shared $(LDFLAGS) -o $@/state.so c_src/state.c
15+
$(CC) -w $(CFLAGS) -shared $(LDFLAGS) -o $@/util.so c_src/util.c
1516

1617
clean:
1718
$(RM) -r _native/*

c_src/native_impl.c

Lines changed: 0 additions & 55 deletions
This file was deleted.

c_src/state.c

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#include "erl_nif.h"
2+
#include <inttypes.h>
3+
4+
#define ROTATE_LEFT(x, b) (unsigned long)(((x) << (b)) | ((x) >> (64 - (b))))
5+
6+
#define COMPRESS \
7+
v0 += v1; \
8+
v2 += v3; \
9+
v1 = ROTATE_LEFT(v1, 13); \
10+
v3 = ROTATE_LEFT(v3, 16); \
11+
v1 ^= v0; \
12+
v3 ^= v2; \
13+
v0 = ROTATE_LEFT(v0, 32); \
14+
v2 += v1; \
15+
v0 += v3; \
16+
v1 = ROTATE_LEFT(v1, 17); \
17+
v3 = ROTATE_LEFT(v3, 21); \
18+
v1 ^= v2; \
19+
v3 ^= v0; \
20+
v2 = ROTATE_LEFT(v2, 32);
21+
22+
static ERL_NIF_TERM apply_block(ErlNifEnv* env, int arc, const ERL_NIF_TERM argv[]) {
23+
int arity;
24+
const ERL_NIF_TERM** tuple;
25+
26+
enif_get_tuple(env, argv[0], &arity, &tuple);
27+
28+
unsigned long v0;
29+
unsigned long v1;
30+
unsigned long v2;
31+
unsigned long v3;
32+
unsigned long m;
33+
34+
enif_get_ulong(env, tuple[0], &v0);
35+
enif_get_ulong(env, tuple[1], &v1);
36+
enif_get_ulong(env, tuple[2], &v2);
37+
enif_get_ulong(env, tuple[3], &v3);
38+
enif_get_ulong(env, argv[1], &m);
39+
40+
int c;
41+
42+
enif_get_int(env, argv[2], &c);
43+
44+
v3 ^= m;
45+
for(int i = 0; i < c; i++){
46+
COMPRESS
47+
}
48+
v0 ^= m;
49+
50+
return enif_make_tuple4(
51+
env,
52+
enif_make_ulong(env, v0),
53+
enif_make_ulong(env, v1),
54+
enif_make_ulong(env, v2),
55+
enif_make_ulong(env, v3)
56+
);
57+
}
58+
59+
static ERL_NIF_TERM finalize(ErlNifEnv* env, int arc, const ERL_NIF_TERM argv[]) {
60+
int arity;
61+
const ERL_NIF_TERM** tuple;
62+
63+
enif_get_tuple(env, argv[0], &arity, &tuple);
64+
65+
unsigned long v0;
66+
unsigned long v1;
67+
unsigned long v2;
68+
unsigned long v3;
69+
70+
enif_get_ulong(env, tuple[0], &v0);
71+
enif_get_ulong(env, tuple[1], &v1);
72+
enif_get_ulong(env, tuple[2], &v2);
73+
enif_get_ulong(env, tuple[3], &v3);
74+
75+
int d;
76+
77+
enif_get_int(env, argv[1], &d);
78+
79+
v2 ^= 0xff;
80+
for(int i = 0; i < d; i++){
81+
COMPRESS
82+
}
83+
84+
return enif_make_ulong(env, v0 ^ v1 ^ v2 ^ v3);
85+
}
86+
87+
static ErlNifFunc nif_funcs[] = {
88+
{ "apply_internal_block", 3, apply_block },
89+
{ "finalize", 2, finalize }
90+
};
91+
92+
static int upgrade(ErlNifEnv* env, void** new, void** old, ERL_NIF_TERM info){
93+
return 0;
94+
}
95+
96+
ERL_NIF_INIT(Elixir.SipHash.State,nif_funcs,NULL,NULL,upgrade,NULL)

c_src/util.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include "erl_nif.h"
2+
3+
static ERL_NIF_TERM format(ErlNifEnv* env, int arc, const ERL_NIF_TERM argv[]) {
4+
unsigned long n;
5+
ErlNifBinary f, r;
6+
7+
enif_alloc_binary(16, &r);
8+
enif_get_ulong(env, argv[0], &n);
9+
enif_inspect_binary(env, argv[1], &f);
10+
11+
sprintf(r.data, f.data, n);
12+
13+
return enif_make_binary(env, &r);
14+
}
15+
16+
static ErlNifFunc nif_funcs[] = {
17+
{ "format", 2, format }
18+
};
19+
20+
static int upgrade(ErlNifEnv* env, void** new, void** old, ERL_NIF_TERM info){
21+
return 0;
22+
}
23+
24+
ERL_NIF_INIT(Elixir.SipHash.Util,nif_funcs,NULL,NULL,upgrade,NULL)

coveralls.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010
"^\\s+use\\s+"
1111
],
1212
"custom_stop_words": [
13-
"case :erlang.load_nif",
14-
"Logger",
15-
"log_msg"
13+
"_other"
1614
],
1715
"coverage_options": {
1816
"treat_no_relevant_lines_as_covered": false

lib/siphash.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ defmodule SipHash do
99
the NIFs are automatically loaded during the start of the application. Please
1010
note that the use of NIFs brings a significant performance improvement, and so
1111
you should only disable them with good reason.
12+
13+
Due to the use of NIFs, please only use the public `SipHash` functions. Do not
14+
rely on the behaviour of any submodules, as incorrect use of native functions
15+
can result in crashes in your application.
1216
"""
1317

1418
# alias both SipHash.State/Util
@@ -89,7 +93,6 @@ defmodule SipHash do
8993
c_pass = 2
9094
d_pass = 4
9195
to_hex = false
92-
l_pad = false
9396
state = State.initialize(key)
9497

9598
case opts do
@@ -99,7 +102,6 @@ defmodule SipHash do
99102
c_pass = Keyword.get(opts, :c, c_pass)
100103
d_pass = Keyword.get(opts, :d, d_pass)
101104
to_hex = Keyword.get(opts, :hex, to_hex)
102-
l_pad = Keyword.get(opts, :padding, l_pad)
103105
end
104106

105107
input
@@ -114,9 +116,7 @@ defmodule SipHash do
114116
end)
115117
|> State.apply_last_block(in_len, c_pass)
116118
|> State.finalize(d_pass)
117-
|> Util.to_hex(to_hex)
118-
|> Util.to_case(s_case, :upper)
119-
|> Util.pad_left(l_pad)
119+
|> Util.format(to_hex, s_case)
120120
end
121121

122122
@doc """

lib/siphash/state.ex

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ defmodule SipHash.State do
2020
@initial_v3 0x7465646279746573
2121

2222
# define native implementation
23-
@native_impl [".", "_native", "native_impl"] |> Path.join |> Path.expand
23+
@native_impl [".", "_native", "state"] |> Path.join |> Path.expand
2424

2525
# setup init load
2626
@on_load :init
@@ -31,33 +31,28 @@ defmodule SipHash.State do
3131
implementation, we don't have to exit on failure.
3232
"""
3333
def init do
34-
case System.get_env("HASH_IMPL") do
35-
"embedded" ->
36-
Logger.bare_log(:debug, "Loaded embedded compression.")
37-
_other ->
38-
case :erlang.load_nif(@native_impl, 0) do
39-
:ok ->
40-
Logger.bare_log(:debug, "Loaded native compression.")
41-
err ->
42-
log_msg = "Unable to load native compression! Using embedded instead."
43-
Logger.bare_log(:warn, log_msg <> "\n" <> inspect(err))
44-
end
34+
case System.get_env("STATE_IMPL") do
35+
"embedded" -> :ok;
36+
_other -> :erlang.load_nif(@native_impl, 0)
4537
end
4638
end
4739

4840
@doc """
4941
Applies a block (an 8-byte chunk) to the digest, and returns the state after
50-
transformation. If a binary chunk is passed in, it's converted to a number (as
51-
little endian) before being passed to the main body. First we XOR v3 before
52-
compressing the state twice. Once complete, we then XOR v0, and return the
53-
final state.
42+
transformation. A binary chunk is passed in, and then it is converted to a
43+
number (as little endian) before being passed to the internal function
44+
`SipHash.State.apply_internal_block/3`.
5445
"""
55-
@spec apply_block(s, binary | number, number) :: s
46+
@spec apply_block(s, binary, number) :: s
5647
def apply_block({ _v0, _v1, _v2, _v3 } = state, m, c)
5748
when is_binary(m) and is_number(c) do
58-
apply_block(state, Util.bytes_to_long(m), c)
49+
apply_internal_block(state, Util.bytes_to_long(m), c)
5950
end
60-
def apply_block({ v0, v1, v2, v3 }, m, c) when is_number(m) and is_number(c) do
51+
52+
# Applies a block to the digest, and returns the state after transformation.
53+
# First we XOR v3 before compressing the state twice. Once it's complete, we
54+
# then XOR v0, and return the final state.
55+
defp apply_internal_block({ v0, v1, v2, v3 }, m, c) do
6156
state = { v0, v1, v2, v3 ^^^ m }
6257

6358
{ v0, v1, v2, v3 } =

0 commit comments

Comments
 (0)