This is an automated email from the ASF dual-hosted git repository. jaydoane pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/couchdb-b64url.git
commit 13e21f49d4dee19255b345bca5bfaab1f29b38db Author: Paul J. Davis <paul.joseph.da...@gmail.com> AuthorDate: Thu Oct 31 17:59:48 2013 -0500 Initial commit --- .gitignore | 4 + c_src/couch_seqs_b64.c | 409 +++++++++++++++++++++++++++++++++++++++ rebar.config | 12 ++ src/couch_seqs.app.src | 6 + src/couch_seqs_b64url.erl | 72 +++++++ test/couch_seqs_b64url_tests.erl | 35 ++++ 6 files changed, 538 insertions(+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1356fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.eunit/ +c_src/*.o +ebin/ +priv/*.so diff --git a/c_src/couch_seqs_b64.c b/c_src/couch_seqs_b64.c new file mode 100644 index 0000000..8477508 --- /dev/null +++ b/c_src/couch_seqs_b64.c @@ -0,0 +1,409 @@ + +#include <assert.h> +#include <string.h> + +#include "erl_nif.h" + + +typedef ERL_NIF_TERM ENTERM; + + +typedef struct +{ + ENTERM atom_ok; + ENTERM atom_error; + ENTERM atom_partial; + + ENTERM atom_nomem; + + ErlNifResourceType* res_st; +} cseq_priv; + + +typedef struct +{ + ErlNifPid pid; + ErlNifBinary* tgt; + size_t len; + size_t si; + size_t ti; +} cseq_st; + + +typedef enum +{ + ST_OK, + ST_ERROR, + ST_PARTIAL +} cseq_status; + + +const char B64URL_B2A[256] = { + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, // 0 - 15 + 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99,100,101,102, // 16 - 31 + 103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118, // 32 - 47 + 119,120,121,122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 45, 95, // 48 - 63 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 64 - 79 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 80 - 95 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 96 - 111 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 112 - 127 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 128 - 143 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 144 - 159 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 160 - 175 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 176 - 191 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 192 - 207 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 208 - 223 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 224 - 239 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255 // 240 - 255 +}; + +const char B64URL_A2B[256] = { + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 0 - 15 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 16 - 31 + 255,255,255,255,255,255,255,255,255,255,255,255,255, 62,255,255, // 32 - 47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,255,255,255, // 48 - 63 + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64 - 79 + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255, 63, // 80 - 95 + 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96 - 111 + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, // 112 - 127 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 128 - 143 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 144 - 159 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 160 - 175 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 176 - 191 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 192 - 207 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 208 - 223 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 224 - 239 + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255 // 240 - 255 +}; + + +#define BYTES_PER_PERCENT 64 + +static inline int +do_consume_timeslice(ErlNifEnv* env) { +#if(ERL_NIF_MAJOR_VERSION >= 2 && ERL_NIF_MINOR_VERSION >= 4) + return enif_consume_timeslice(env, 1); +#else + return 0; +#endif +} + + +static inline ENTERM +make_atom(ErlNifEnv* env, const char* name) +{ + ENTERM ret; + if(enif_make_existing_atom(env, name, &ret, ERL_NIF_LATIN1)) { + return ret; + } + return enif_make_atom(env, name); +} + + +static inline ENTERM +make_ok(ErlNifEnv* env, cseq_priv* priv, ENTERM value) +{ + return enif_make_tuple2(env, priv->atom_ok, value); +} + + +static inline ENTERM +make_error(ErlNifEnv* env, cseq_priv* priv, ENTERM value) +{ + return enif_make_tuple2(env, priv->atom_error, value); +} + + +static inline ENTERM +make_partial(ErlNifEnv* env, cseq_priv* priv, ENTERM value) +{ + return enif_make_tuple2(env, priv->atom_partial, value); +} + + +static inline int +check_pid(ErlNifEnv* env, cseq_st* st) +{ + ErlNifPid self_pid; + ENTERM self; + ENTERM orig; + + enif_self(env, &self_pid); + self = enif_make_pid(env, &self_pid); + orig = enif_make_pid(env, &(st->pid)); + + if(enif_compare(self, orig) == 0) { + return 1; + } + + return 0; +} + + +static void +cseq_free(ErlNifEnv* env, void* obj) +{ + cseq_st* st = (cseq_st*) obj; + + if(st->tgt != NULL) { + enif_release_binary(st->tgt); + enif_free(st->tgt); + } +} + + +static int +load(ErlNifEnv* env, void** priv, ENTERM info) +{ + int flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; + ErlNifResourceType* res; + + cseq_priv* new_priv = (cseq_priv*) enif_alloc(sizeof(cseq_priv)); + if(new_priv == NULL) { + return 1; + } + + res = enif_open_resource_type( + env, NULL, "couch_seq", cseq_free, flags, NULL); + if(res == NULL) { + return 1; + } + new_priv->res_st = res; + + new_priv->atom_ok = make_atom(env, "ok"); + new_priv->atom_error = make_atom(env, "error"); + new_priv->atom_partial = make_atom(env, "partial"); + + new_priv->atom_nomem = make_atom(env, "enomem"); + + *priv = (void*) new_priv; + + return 0; +} + + +static int +reload(ErlNifEnv* env, void** priv, ENTERM info) +{ + return 0; +} + + +static int +upgrade(ErlNifEnv* env, void** priv, void** old_priv, ENTERM info) +{ + return 0; +} + + +static void +unload(ErlNifEnv* env, void* priv) +{ + return; +} + + +static inline cseq_status +cseq_b64url_encode(ErlNifEnv* env, ErlNifBinary* src, cseq_st* st) +{ + size_t chunk_start = st->si; + unsigned char c1; + unsigned char c2; + unsigned char c3; + + assert(st->si % 3 == 0 && "invalid source index"); + assert(st->ti % 4 == 0 && "invalid target index"); + + while(st->si + 3 <= src->size) { + c1 = src->data[st->si++]; + c2 = src->data[st->si++]; + c3 = src->data[st->si++]; + + st->tgt->data[st->ti++] = B64URL_B2A[(c1 >> 2) & 0x3F]; + st->tgt->data[st->ti++] = B64URL_B2A[((c1 << 4) | (c2 >> 4)) & 0x3F]; + st->tgt->data[st->ti++] = B64URL_B2A[((c2 << 2) | (c3 >> 6)) & 0x3F]; + st->tgt->data[st->ti++] = B64URL_B2A[c3 & 0x3F]; + + if(st->si - chunk_start > BYTES_PER_PERCENT) { + if(do_consume_timeslice(env)) { + return ST_PARTIAL; + } else { + chunk_start = st->si; + } + } + } + + if(src->size - st->si == 2) { + c1 = src->data[st->si]; + st->tgt->data[st->ti++] = B64URL_B2A[(c1 >> 2) & 0x3F]; + st->tgt->data[st->ti++] = B64URL_B2A[(c1 << 4) & 0x3F]; + } else if(src->size - st->si == 1) { + c1 = src->data[st->si]; + c2 = src->data[st->si+1]; + st->tgt->data[st->ti++] = B64URL_B2A[(c1 >> 2) & 0x3F]; + st->tgt->data[st->ti++] = B64URL_B2A[((c1 << 4) | (c2 >> 4)) & 0x3F]; + st->tgt->data[st->ti++] = B64URL_B2A[(c2 << 2) & 0x3F]; + } else { + assert(0 == 1 && "Inavlid length calculation"); + } + + return ST_OK; +} + + +static ENTERM +cseq_b64url_encode_init(ErlNifEnv* env, int argc, const ENTERM argv[]) +{ + ErlNifBinary src; + cseq_priv* priv = (cseq_priv*) enif_priv_data(env); + cseq_st* st = NULL; + size_t tlen; + size_t rem; + int status; + ENTERM ret; + + if(argc != 1) { + return enif_make_badarg(env); + } + + if(!enif_inspect_iolist_as_binary(env, argv[0], &src)) { + return enif_make_badarg(env); + } + + if(src.size <= 0) { + return enif_make_badarg(env); + } + + st = (cseq_st*) enif_alloc_resource(priv->res_st, sizeof(cseq_st)); + if(st == NULL) { + ret = make_error(env, priv, priv->atom_nomem); + goto error; + } + + memset(st, '\0', sizeof(cseq_st)); + enif_self(env, &(st->pid)); + st->len = src.size; + st->si = 0; + st->ti = 0; + st->tgt = (ErlNifBinary*) enif_alloc(sizeof(ErlNifBinary)); + if(st->tgt == NULL) { + ret = make_error(env, priv, priv->atom_nomem); + goto error; + } + + // The target length is defined as 4 * ceil(src_len/3) but we + // don't use '=' padding for URLs so we only add exactly the + // extra bytes we need. + tlen = (src.size / 3) * 4; + rem = src.size % 3; + if(rem == 1) { + tlen += 2; + } else if(rem == 2) { + tlen += 3; + } + + if(!enif_alloc_binary(tlen, st->tgt)) { + ret = make_error(env, priv, priv->atom_nomem); + goto error; + } + + status = cseq_b64url_encode(env, &src, st); + + if(status == ST_OK) { + ret = enif_make_binary(env, st->tgt); + enif_free(st->tgt); + st->tgt = NULL; + enif_release_resource(st); + return make_ok(env, priv, ret); + } else { + assert(status == ST_PARTIAL && "invalid status"); + ret = enif_make_resource(env, st); + enif_release_resource(st); + return make_partial(env, priv, ret); + } + +error: + if(st != NULL) { + enif_release_resource(st); + } + + return ret; +} + + +static ENTERM +cseq_b64url_encode_cont(ErlNifEnv* env, int argc, const ENTERM argv[]) +{ + ErlNifBinary src; + cseq_priv* priv = (cseq_priv*) enif_priv_data(env); + cseq_st* st = NULL; + ENTERM ret; + int status; + + if(argc != 2) { + return enif_make_badarg(env); + } + + if(!enif_inspect_iolist_as_binary(env, argv[0], &src)) { + return enif_make_badarg(env); + } + + if(src.size <= 0) { + return enif_make_badarg(env); + } + + if(!enif_get_resource(env, argv[1], priv->res_st, (void**) &st)) { + return enif_make_badarg(env); + } + + if(!check_pid(env, st)) { + return enif_make_badarg(env); + } + + if(src.size != st->len) { + return enif_make_badarg(env); + } + + status = cseq_b64url_encode(env, &src, st); + + if(status == ST_OK) { + ret = enif_make_binary(env, st->tgt); + st->tgt = NULL; + enif_release_resource(st); + return make_ok(env, priv, ret); + } else { + assert(status == ST_PARTIAL && "invalid status"); + ret = enif_make_resource(env, st); + enif_release_resource(st); + return make_partial(env, priv, ret); + } + + assert(0 == 1 && "Invalid status from cseq_b64url_encode"); +} + + +static ENTERM +cseq_b64url_decode_init(ErlNifEnv* env, int argc, const ENTERM argv[]) +{ + return enif_make_badarg(env); +} + + +static ENTERM +cseq_b64url_decode_cont(ErlNifEnv* env, int argc, const ENTERM argv[]) +{ + return enif_make_badarg(env); +} + + +static ErlNifFunc funcs[] = { + {"encode_init", 1, cseq_b64url_encode_init}, + {"encode_cont", 2, cseq_b64url_encode_cont}, + {"decode_init", 1, cseq_b64url_decode_init}, + {"decode_cont", 2, cseq_b64url_decode_cont} +}; + + +ERL_NIF_INIT(couch_seqs_b64url, funcs, &load, &reload, &upgrade, &unload); + + diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..6f2ca46 --- /dev/null +++ b/rebar.config @@ -0,0 +1,12 @@ +{port_specs, [ + {"priv/couch_seqs_b64.so", ["c_src/*.c"]} +]}. + +{port_env, [ + % Development compilation + % {".*", "CFLAGS", "$CFLAGS -g -Wall -Werror -fPIC"} + + % Production compilation + {".*", "CFLAGS", "$CFLAGS -Wall -Werror -DNDEBUG -O3"} +]}. + diff --git a/src/couch_seqs.app.src b/src/couch_seqs.app.src new file mode 100644 index 0000000..44b6a3d --- /dev/null +++ b/src/couch_seqs.app.src @@ -0,0 +1,6 @@ +{application, couch_seqs, [ + {description, "A library for handling couch sequences"}, + {vsn, git}, + {registered, []}, + {applications, [kernel]} +]}. diff --git a/src/couch_seqs_b64url.erl b/src/couch_seqs_b64url.erl new file mode 100644 index 0000000..bc1830b --- /dev/null +++ b/src/couch_seqs_b64url.erl @@ -0,0 +1,72 @@ +-module(couch_seqs_b64url). +-on_load(init/0). + + +-export([ + encode/1, + decode/1 +]). + + +-define(NOT_LOADED, not_loaded(?LINE)). + + +-spec encode(iodata()) -> binary(). +encode(IoData) -> + case encode_init(IoData) of + {ok, Bin} -> + Bin; + {partial, St} -> + encode_loop(IoData, St) + end. + + +-spec decode(iodata()) -> binary() | {error, any()}. +decode(IoData) -> + case decode_init(IoData) of + {ok, Bin} -> + Bin; + {partial, St} -> + decode_loop(IoData, St) + end. + + +init() -> + PrivDir = case code:priv_dir(?MODULE) of + {error, _} -> + EbinDir = filename:dirname(code:which(?MODULE)), + AppPath = filename:dirname(EbinDir), + filename:join(AppPath, "priv"); + Path -> + Path + end, + erlang:load_nif(filename:join(PrivDir, "couch_seqs_b64"), 0). + + +encode_loop(IoData, St) -> + case encode_cont(IoData, St) of + {ok, Bin} -> + Bin; + {partial, St} -> + encode_loop(IoData, St) + end. + + +decode_loop(IoData, St) -> + case decode_cont(IoData, St) of + {ok, Bin} -> + Bin; + {partial, St} -> + decode_loop(IoData, St) + end. + + +encode_init(_) -> ?NOT_LOADED. +encode_cont(_, _) -> ?NOT_LOADED. +decode_init(_) -> ?NOT_LOADED. +decode_cont(_, _) -> ?NOT_LOADED. + + +not_loaded(Line) -> + erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}). + diff --git a/test/couch_seqs_b64url_tests.erl b/test/couch_seqs_b64url_tests.erl new file mode 100644 index 0000000..0e1089a --- /dev/null +++ b/test/couch_seqs_b64url_tests.erl @@ -0,0 +1,35 @@ +-module(couch_seqs_b64url_tests). +-compile(export_all). + +-include_lib("proper/include/proper.hrl"). +-include_lib("eunit/include/eunit.hrl"). + + +proper_test_() -> + PropErOpts = [ + {to_file, user}, + {max_size, 524288}, + {numtests, 1000} + ], + {timeout, 3600, ?_assertEqual([], proper:module(?MODULE, PropErOpts))}. + + +prop_encode_binary() -> + ?FORALL(Bin, binary(), + couch_seqs_b64url:encode(Bin) == couch_encode_base64url(Bin) + ). + + +couch_encode_base64url(Data) -> + Url1 = iolist_to_binary(re:replace(base64:encode(Url), "=+$", "")), + Url2 = iolist_to_binary(re:replace(Url1, "/", "_", [global])), + iolist_to_binary(re:replace(Url2, "\\+", "-", [global])). + + +couch_decode_base64url(Data) -> + Url1 = re:replace(iolist_to_binary(Url64), "-", "+", [global]), + Url2 = iolist_to_binary( + re:replace(iolist_to_binary(Url1), "_", "/", [global]) + ), + Padding = list_to_binary(lists:duplicate((4 - size(Url2) rem 4) rem 4, $=)), + base64:decode(<<Url2/binary, Padding/binary>>).