1// Licensed under the Apache License, Version 2.0 (the "License"); you may not
2// use this file except in compliance with the License. You may obtain a copy of
3// the License at
4//
5//   http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10// License for the specific language governing permissions and limitations under
11// the License.
12
13#include <stdio.h>
14#include <string.h>
15#include <math.h>
16
17#include "erl_nif.h"
18#include "erl_nif_compat.h"
19#include "yajl/yajl_encode.h"
20#include "ejson.h"
21
22#if defined(_WIN32) || defined(WIN32) || defined(__WIN32__)
23#include <float.h>
24#define isnan _isnan
25#define isinf !_finite
26#define snprintf _snprintf
27#endif
28
29#define SUCCESS 0
30#define NOMEM 1
31#define BADARG 2
32
33
34typedef struct {
35    ErlNifEnv* env;
36    ErlNifBinary bin;
37    size_t fill_offset;
38    int error;
39} encode_ctx;
40
41
42static int
43ensure_buffer(void* vctx, unsigned int len) {
44    encode_ctx* ctx = (encode_ctx*)vctx;
45    if ((ctx->bin.size - ctx->fill_offset) < len) {
46        if(!enif_realloc_binary_compat(ctx->env, &(ctx->bin), (ctx->bin.size * 2) + len)) {
47            return NOMEM;
48        }
49    }
50    return SUCCESS;
51}
52
53static void
54fill_buffer(void* vctx, const char* str, unsigned int len)
55{
56    encode_ctx* ctx = (encode_ctx*)vctx;
57
58    if (ctx->error || (ctx->error = ensure_buffer(vctx, len))) {
59        return;
60    }
61    memcpy(ctx->bin.data + ctx->fill_offset, str, len);
62    ctx->fill_offset += len;
63}
64
65/* Json encode the string binary into the ctx.bin,
66  with surrounding quotes and all */
67static int
68encode_string(void* vctx, ERL_NIF_TERM binary)
69{
70    encode_ctx* ctx = (encode_ctx*)vctx;
71    ErlNifBinary bin;
72
73    if(!enif_inspect_binary(ctx->env, binary, &bin)) {
74        return NOMEM;
75    }
76    fill_buffer(ctx, "\"", 1);
77    if (ctx->error) {
78        return ctx->error;
79    }
80    yajl_string_encode2(fill_buffer, ctx, bin.data, bin.size);
81    fill_buffer(ctx, "\"", 1);
82
83    return ctx->error;
84}
85
86static ERL_NIF_TERM
87no_mem_error(ErlNifEnv* env)
88{
89    return enif_make_tuple(env, 2,
90            enif_make_atom(env, "error"),
91            enif_make_atom(env, "insufficient_memory"));
92}
93
94ERL_NIF_TERM
95final_encode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
96{
97    ERL_NIF_TERM head = argv[0];
98    ERL_NIF_TERM term;
99    double number;
100    encode_ctx ctx;
101
102    ctx.env = env;
103    ctx.fill_offset = 0;
104    ctx.error = 0;
105
106    if (!enif_alloc_binary_compat(env, 100, &ctx.bin)) {
107            return no_mem_error(env);
108    }
109
110    while(enif_get_list_cell(env, head, &term, &head)) {
111        ErlNifBinary termbin;
112        const ERL_NIF_TERM* array;
113        int arity;
114        int code;
115
116        // We scan the list, looking for things to write into the binary, or
117        // encode and then write into the binary. We encode values that are
118        // tuples tagged with a type and a value: {Type, Value} where Type
119        // is a an Integer and Value is what is to be encoded
120
121        if (enif_get_tuple(env, term, &arity, &array)) {
122            // It's a tuple to encode and copy
123            if (arity != 2 || !enif_get_int(env, array[0], &code)) {
124                // not arity 2 or the first element isn't an int
125                ctx.error = BADARG;
126                goto done;
127            }
128            if (code == 0) {
129                // {0, String}
130                if (encode_string(&ctx, array[1]) != SUCCESS) {
131                    goto done;
132                }
133            }
134            else {
135                // {1, Double}
136                if(!enif_get_double(env, array[1], &number)) {
137                    ctx.error = BADARG;
138                    goto done;
139                }
140                // We can't encode these.
141                if (isnan(number) || isinf(number)) {
142                    ctx.error = BADARG;
143                    goto done;
144                }
145                if ((ctx.error = ensure_buffer(&ctx, 32)) != SUCCESS) {
146                    goto done;
147                }
148                // write the string into the buffer
149                snprintf((char*)ctx.bin.data+ctx.fill_offset, 32,
150                        "%.16g", number);
151                // increment the length
152                ctx.fill_offset += strlen((char*)ctx.bin.data+ctx.fill_offset);
153            }
154        } else if (enif_inspect_binary(env, term, &termbin)) {
155            // this is a regular binary, copy the contents into the buffer
156            fill_buffer(&ctx, (char*)termbin.data, termbin.size);
157            if (ctx.error) {
158                goto done;
159            }
160        }
161        else {
162            //not a binary, not a tuple, wtf!
163            ctx.error = BADARG;
164            goto done;
165        }
166    }
167done:
168    if (ctx.error == NOMEM) {
169        enif_release_binary_compat(env, &ctx.bin);
170        return no_mem_error(env);
171    } else if (ctx.error == BADARG) {
172        enif_release_binary_compat(env, &ctx.bin);
173        return enif_make_badarg(env);
174    }
175
176    // Resize the binary to our exact final size
177    if(!enif_realloc_binary_compat(env, &(ctx.bin), ctx.fill_offset)) {
178        enif_release_binary_compat(env, &ctx.bin);
179        return no_mem_error(env);
180    }
181    // make the binary term which transfers ownership
182    return enif_make_binary(env, &ctx.bin);
183}
184