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