/* ex: ts=4 sw=4 et * * Copyright (c) 2011-2016 Sergey Urbanovich * http://github.com/urbanserj/cbase64-erlang-nif * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "stdint.h" #include "erl_nif.h" #define min(X, Y) ((X) < (Y) ? (X) : (Y)) #define TIMESLICE 10 #define SPEEDUP 30 #define REDUCTIONS 2000 #define ITER (REDUCTIONS * SPEEDUP / TIMESLICE) static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; inline void encodeblock(const uint8_t in[3], uint8_t out[4], size_t len) { out[0] = cb64[ (in[0] >> 2) & 0x3f ]; out[1] = cb64[ ((in[0] << 4) + (--len ? in[1] >> 4 : 0)) & 0x3f ]; out[2] = len ? cb64[ ((in[1] << 2) + (--len ? in[2] >> 6 : 0)) & 0x3f ] : '='; out[3] = len ? cb64[ in[2] & 0x3f ] : '='; } static ERL_NIF_TERM encode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { ERL_NIF_TERM tdst, tsrc; ErlNifBinary src, dst; if ( enif_inspect_binary(env, argv[0], &src) ) { tsrc = argv[0]; } else if ( enif_inspect_iolist_as_binary(env, argv[0], &src) ) { tsrc = enif_make_binary(env, &src); } else { return enif_make_badarg(env); } if (src.size == 0) { return argv[0]; } uint8_t *data; size_t size = (src.size + 2) / 3 * 4; if (argc == 1) { data = enif_make_new_binary(env, size, &tdst); } else { tdst = argv[1]; if ( !enif_inspect_binary(env, argv[1], &dst) ) { return enif_make_badarg(env); } data = dst.data; } int i = size / 4 - 1; if (argc == 3) { if ( !enif_get_int(env, argv[2], &i) ) { return enif_make_badarg(env); } } else { /* last block */ encodeblock(src.data + i * 3, data + i * 4, src.size - i * 3); i--; } int reductions = 0; for (; i >= 0; i--) { encodeblock(src.data + i * 3, data + i * 4, 3); reductions += 3; if (reductions >= ITER) { if (enif_consume_timeslice(env, TIMESLICE)) { ERL_NIF_TERM schargv[3] = {tsrc, tdst, enif_make_int(env, i)}; return enif_schedule_nif(env, "encode", 0, &encode, 3, schargv); } reductions = 0; } } return tdst; } #define DECODE_ERROR 0xff #define DECODE_OK 0x00 #define B64(_) \ ( (_) >= 'A' && (_) <= 'Z' ? 25 - 'Z' + (_) : \ (_) >= 'a' && (_) <= 'z' ? 51 - 'z' + (_) : \ (_) >= '0' && (_) <= '9' ? 61 - '9' + (_) : \ (_) == '+' ? 62 : (_) ? 63 : DECODE_ERROR ) static const int cd64[256] = { B64( 0), B64( 1), B64( 2), B64( 3), B64( 4), B64( 5), B64( 6), B64( 7), B64( 8), B64( 9), B64( 10), B64( 11), B64( 12), B64( 13), B64( 14), B64( 15), B64( 16), B64( 17), B64( 18), B64( 19), B64( 20), B64( 21), B64( 22), B64( 23), B64( 24), B64( 25), B64( 26), B64( 27), B64( 28), B64( 29), B64( 30), B64( 31), B64( 32), B64( 33), B64( 34), B64( 35), B64( 36), B64( 37), B64( 38), B64( 39), B64( 40), B64( 41), B64( 42), B64( 43), B64( 44), B64( 45), B64( 46), B64( 47), B64( 48), B64( 49), B64( 50), B64( 51), B64( 52), B64( 53), B64( 54), B64( 55), B64( 56), B64( 57), B64( 58), B64( 59), B64( 60), B64( 61), B64( 62), B64( 63), B64( 64), B64( 65), B64( 66), B64( 67), B64( 68), B64( 69), B64( 70), B64( 71), B64( 72), B64( 73), B64( 74), B64( 75), B64( 76), B64( 77), B64( 78), B64( 79), B64( 80), B64( 81), B64( 82), B64( 83), B64( 84), B64( 85), B64( 86), B64( 87), B64( 88), B64( 89), B64( 90), B64( 91), B64( 92), B64( 93), B64( 94), B64( 95), B64( 96), B64( 97), B64( 98), B64( 99), B64(100), B64(101), B64(102), B64(103), B64(104), B64(105), B64(106), B64(107), B64(108), B64(109), B64(110), B64(111), B64(112), B64(113), B64(114), B64(115), B64(116), B64(117), B64(118), B64(119), B64(120), B64(121), B64(122), B64(123), B64(124), B64(125), B64(126), B64(127), B64(128), B64(129), B64(130), B64(131), B64(132), B64(133), B64(134), B64(135), B64(136), B64(137), B64(138), B64(139), B64(140), B64(141), B64(142), B64(143), B64(144), B64(145), B64(146), B64(147), B64(148), B64(149), B64(150), B64(151), B64(152), B64(153), B64(154), B64(155), B64(156), B64(157), B64(158), B64(159), B64(160), B64(161), B64(162), B64(163), B64(164), B64(165), B64(166), B64(167), B64(168), B64(169), B64(170), B64(171), B64(172), B64(173), B64(174), B64(175), B64(176), B64(177), B64(178), B64(179), B64(180), B64(181), B64(182), B64(183), B64(184), B64(185), B64(186), B64(187), B64(188), B64(189), B64(190), B64(191), B64(192), B64(193), B64(194), B64(195), B64(196), B64(197), B64(198), B64(199), B64(200), B64(201), B64(202), B64(203), B64(204), B64(205), B64(206), B64(207), B64(208), B64(209), B64(210), B64(211), B64(212), B64(213), B64(214), B64(215), B64(216), B64(217), B64(218), B64(219), B64(220), B64(221), B64(222), B64(223), B64(224), B64(225), B64(226), B64(227), B64(228), B64(229), B64(230), B64(231), B64(232), B64(233), B64(234), B64(235), B64(236), B64(237), B64(238), B64(239), B64(240), B64(241), B64(242), B64(243), B64(244), B64(245), B64(246), B64(247), B64(248), B64(249), B64(250), B64(251), B64(252), B64(253), B64(254), B64(255) }; inline uint8_t decodeblock(const uint8_t in[4], uint8_t out[3]) { int code; size_t it = 0; unsigned int val = 0; for (;it < 4; ++it) { if ( (code = cd64[in[it]]) == DECODE_ERROR ) return DECODE_ERROR; val = (val << 6) + code; } out[0] = (val >> 16) & 0xff; out[1] = (val >> 8) & 0xff; out[2] = val & 0xff; return DECODE_OK; } inline uint8_t decodeblock_tail(const uint8_t in[4], uint8_t out[3]) { int code; size_t it = 0; unsigned int val = 0; if ( in[0] == '=' || in[1] == '=' || (in[2] == '=' && in[3] != '=') ) return DECODE_ERROR; for (;it < 4; ++it) { val <<= 6; if ( in[it] == '=' ) continue; if ( (code = cd64[in[it]]) == DECODE_ERROR ) return DECODE_ERROR; val += code; } out[0] = (val >> 16) & 0xff; if ( in[2] != '=' ) out[1] = (val >> 8) & 0xff; if ( in[3] != '=' ) out[2] = val & 0xff; return DECODE_OK; } static ERL_NIF_TERM decode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { ERL_NIF_TERM tdst, tsrc; ErlNifBinary src, dst; if ( enif_inspect_binary(env, argv[0], &src) ) { tsrc = argv[0]; } else if ( enif_inspect_iolist_as_binary(env, argv[0], &src) ) { tsrc = enif_make_binary(env, &src); } else { return enif_make_badarg(env); } if (src.size == 0) { return argv[0]; } if (src.size % 4 != 0) { return enif_make_badarg(env); } uint8_t *data; size_t size = src.size/4*3 - (src.data[src.size - 1] == '=' ? 1 : 0) - (src.data[src.size - 2] == '=' ? 1 : 0); if (argc == 1) { data = enif_make_new_binary(env, size, &tdst); } else { tdst = argv[1]; if ( !enif_inspect_binary(env, argv[1], &dst) ) { return enif_make_badarg(env); } data = dst.data; } int i = src.size / 4 - 1; if (argc == 3) { if ( !enif_get_int(env, argv[2], &i) ) { return enif_make_badarg(env); } } else { /* last block */ if ( decodeblock_tail(src.data + i*4, data + i*3) ) { return enif_make_badarg(env); } i--; } int reductions = 0; for (; i >= 0; i--) { if ( decodeblock(src.data + i*4, data + i*3) ) { return enif_make_badarg(env); } reductions += 4; if (reductions >= ITER) { if (enif_consume_timeslice(env, TIMESLICE)) { ERL_NIF_TERM schargv[3] = {tsrc, tdst, enif_make_int(env, i)}; return enif_schedule_nif(env, "decode", 0, &decode, 3, schargv); } reductions = 0; } } return tdst; } static ErlNifFunc nif_funcs[] = { {"encode", 1, encode}, {"decode", 1, decode} }; ERL_NIF_INIT(cbase64, nif_funcs, NULL, NULL, NULL, NULL)