/**************************************************************************** ** ** Copyright (C) 2015 Intel Corporation ** ** 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. ** ****************************************************************************/ #define _POSIX_C_SOURCE 200809L #define _GNU_SOURCE #include "cbor.h" #include "compilersupport_p.h" #include #include #include #include #include #include #include #include static const char meta_data_marker[] = "$cbor"; uint8_t *buffer; size_t buffersize; bool usingMetaData = false; struct MetaData { CborTag tag; union { const char *v; uint8_t simpleType; }; CborType t; bool tagged; }; uint8_t *decode_base64_generic(const char *string, size_t *len, const int8_t reverse_alphabet[256]) { *len = ((strlen(string) + 3) & ~3) * 3 / 4; uint8_t *buffer = malloc(*len); if (buffer == NULL) return NULL; uint8_t *out = buffer; const uint8_t *in = (const uint8_t *)string; bool done = false; while (!done) { if (reverse_alphabet[in[0]] < 0 || reverse_alphabet[in[1]] < 0) { if (in[0] == '\0') done = true; break; } uint32_t val = reverse_alphabet[in[0]] << 18; val |= reverse_alphabet[in[1]] << 12; if (in[2] == '=' || in[2] == '\0') { if (in[2] == '=' && (in[3] != '=' || in[4] != '\0')) break; val >>= 12; done = true; } else if (in[3] == '=' || in[3] == '\0') { if (in[3] == '=' && in[4] != '\0') break; val >>= 6; val |= reverse_alphabet[in[2]]; done = true; } else { val |= reverse_alphabet[in[2]] << 6; val |= reverse_alphabet[in[3]]; } *out++ = val >> 16; *out++ = val >> 8; *out++ = val; in += 4; } if (!done) { free(buffer); return NULL; } *len = out - buffer; return buffer; } uint8_t *decode_base64(const char *string, size_t *len) { static const int8_t reverse_alphabet[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; return decode_base64_generic(string, len, reverse_alphabet); } uint8_t *decode_base64url(const char *string, size_t *len) { static const int8_t reverse_alphabet[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; return decode_base64_generic(string, len, reverse_alphabet); } uint8_t *decode_base16(const char *string, size_t *len) { size_t i; *len = strlen(string) / 2; uint8_t *buffer = malloc(*len); if (buffer == NULL) return NULL; for (i = 0; i < *len; ++i) { char c = string[i * 2]; if (c >= '0' && c <= '9') { buffer[i] = (c - '0') << 4; } else if ((c | 0x20) >= 'a' && (c | 0x20) <= 'f') { buffer[i] = ((c | 0x20) - 'a' + 10) << 4; } else { free(buffer); return NULL; } c = string[i * 2 + 1]; if (c >= '0' && c <= '9') { buffer[i] |= (c - '0'); } else if ((c | 0x20) >= 'a' && (c | 0x20) <= 'f') { buffer[i] |= ((c | 0x20) - 'a' + 10); } else { free(buffer); return NULL; } } return buffer; } size_t get_cjson_size_limited(cJSON *container) { // cJSON_GetArraySize is O(n), so don't go too far unsigned s = 0; cJSON *item; for (item = container->child; item; item = item->next) { if (++s > 255) return CborIndefiniteLength; } return s; } cJSON *get_meta_data(cJSON *object, cJSON *item) { cJSON *meta; char *metadatakey; if (asprintf(&metadatakey, "%s%s", item->string, meta_data_marker) < 0 || metadatakey == NULL) return NULL; meta = cJSON_GetObjectItem(object, metadatakey); free(metadatakey); return meta; } struct MetaData parse_meta_data(cJSON *md) { struct MetaData result = { 0, {NULL}, CborInvalidType, false }; if (md == NULL || md->type != cJSON_Object) return result; for (md = md->child; md; md = md->next) { if (strcmp(md->string, "tag") == 0) { if (md->type != cJSON_String || sscanf(md->valuestring, "%" PRIu64, &result.tag) < 0) fprintf(stderr, "json2cbor: could not parse tag: %s\n", md->valuestring); else result.tagged = true; } else if (strcmp(md->string, "t") == 0) { result.t = md->valueint; } else if (strcmp(md->string, "v") == 0) { if (md->type == cJSON_Number) result.simpleType = md->valueint; else result.v = md->valuestring; } } return result; } CborError decode_json(cJSON *json, CborEncoder *encoder); CborError decode_json_with_metadata(cJSON *item, CborEncoder *encoder, struct MetaData md) { switch (md.t) { case CborIntegerType: { // integer that has more than 53 bits of precision uint64_t v; bool positive = *md.v++ == '+'; if (sscanf(md.v, "%" PRIx64, &v) < 0) { fprintf(stderr, "json2cbor: could not parse number: %s\n", md.v); break; } return positive ? cbor_encode_uint(encoder, v) : cbor_encode_negative_int(encoder, v); } case CborByteStringType: { uint8_t *data; size_t len; if (md.tag == CborExpectedBase64Tag) data = decode_base64(item->valuestring, &len); else if (md.tag == CborExpectedBase16Tag) data = decode_base16(item->valuestring, &len); else if (md.tag == CborNegativeBignumTag) data = decode_base64url(item->valuestring + 1, &len); else data = decode_base64url(item->valuestring, &len); if (data != NULL) { CborError err = cbor_encode_byte_string(encoder, data, len); free(data); return err; } fprintf(stderr, "json2cbor: could not decode encoded byte string: %s\n", item->valuestring); break; } case CborSimpleType: return cbor_encode_simple_value(encoder, md.simpleType); case CborUndefinedType: return cbor_encode_undefined(encoder); case CborHalfFloatType: case CborFloatType: case CborDoubleType: { unsigned short half; double v; if (!md.v) { v = item->valuedouble; } else if (strcmp(md.v, "nan") == 0) { v = NAN; } else if (strcmp(md.v, "-inf") == 0) { v = -INFINITY; } else if (strcmp(md.v, "inf") == 0) { v = INFINITY; } else { fprintf(stderr, "json2cbor: invalid floating-point value: %s\n", md.v); break; } // we can't get an OOM here because the metadata makes up for space // (the smallest metadata is "$cbor":{"t":250} (17 bytes) return (md.t == CborDoubleType) ? cbor_encode_double(encoder, v) : (md.t == CborFloatType) ? cbor_encode_float(encoder, v) : (half = encode_half(v), cbor_encode_half_float(encoder, &half)); } default: fprintf(stderr, "json2cbor: invalid CBOR type: %d\n", md.t); case CborInvalidType: break; } return decode_json(item, encoder); } CborError decode_json(cJSON *json, CborEncoder *encoder) { CborEncoder container; CborError err; cJSON *item; switch (json->type) { case cJSON_False: case cJSON_True: return cbor_encode_boolean(encoder, json->type == cJSON_True); case cJSON_NULL: return cbor_encode_null(encoder); case cJSON_Number: if ((double)json->valueint == json->valuedouble) return cbor_encode_int(encoder, json->valueint); encode_double: // the only exception that JSON is larger: floating point numbers container = *encoder; // save the state err = cbor_encode_double(encoder, json->valuedouble); if (err == CborErrorOutOfMemory) { buffersize += 1024; uint8_t *newbuffer = realloc(buffer, buffersize); if (newbuffer == NULL) return err; *encoder = container; // restore state encoder->data.ptr = newbuffer + (container.data.ptr - buffer); encoder->end = newbuffer + buffersize; buffer = newbuffer; goto encode_double; } return err; case cJSON_String: return cbor_encode_text_stringz(encoder, json->valuestring); default: return CborErrorUnknownType; case cJSON_Array: err = cbor_encoder_create_array(encoder, &container, get_cjson_size_limited(json)); if (err) return err; for (item = json->child; item; item = item->next) { err = decode_json(item, &container); if (err) return err; } return cbor_encoder_close_container_checked(encoder, &container); case cJSON_Object: err = cbor_encoder_create_map(encoder, &container, usingMetaData ? CborIndefiniteLength : get_cjson_size_limited(json)); if (err) return err; for (item = json->child ; item; item = item->next) { if (usingMetaData && strlen(item->string) > strlen(meta_data_marker) && strcmp(item->string + strlen(item->string) - 5, meta_data_marker) == 0) continue; err = cbor_encode_text_stringz(&container, item->string); if (err) return err; if (usingMetaData) { cJSON *meta = get_meta_data(json, item); struct MetaData md = parse_meta_data(meta); if (md.tagged) { err = cbor_encode_tag(&container, md.tag); if (err) return err; } err = decode_json_with_metadata(item, &container, md); } else { err = decode_json(item, &container); } if (err) return err; } return cbor_encoder_close_container_checked(encoder, &container); } } int main(int argc, char **argv) { int c; while ((c = getopt(argc, argv, "M")) != -1) { switch (c) { case 'M': usingMetaData = true; break; case '?': fprintf(stderr, "Unknown option -%c.\n", optopt); // fall through case 'h': puts("Usage: json2cbor [OPTION]... [FILE]...\n" "Reads JSON content from FILE and convert to CBOR.\n" "\n" "Options:\n" " -M Interpret metadata added by cbordump tool\n" ""); return c == '?' ? EXIT_FAILURE : EXIT_SUCCESS; } } FILE *in; const char *fname = argv[optind]; if (fname && strcmp(fname, "-") != 0) { in = fopen(fname, "r"); if (!in) { perror("open"); return EXIT_FAILURE; } } else { in = stdin; fname = "-"; } /* 1. read the file */ off_t fsize; if (fseeko(in, 0, SEEK_END) == 0 && (fsize = ftello(in)) >= 0) { buffersize = fsize + 1; buffer = malloc(buffersize); if (buffer == NULL) { perror("malloc"); return EXIT_FAILURE; } rewind(in); fsize = fread(buffer, 1, fsize, in); buffer[fsize] = '\0'; } else { const unsigned chunk = 16384; buffersize = 0; buffer = NULL; do { // it the hard way buffer = realloc(buffer, buffersize + chunk); if (buffer == NULL) perror("malloc"); buffersize += fread(buffer + buffersize, 1, chunk, in); } while (!feof(in) && !ferror(in)); buffer[buffersize] = '\0'; } if (ferror(in)) { perror("read"); return EXIT_FAILURE; } if (in != stdin) fclose(in); /* 2. parse as JSON */ cJSON *doc = cJSON_ParseWithOpts((char *)buffer, NULL, true); if (doc == NULL) { fprintf(stderr, "json2cbor: %s: could not parse.\n", fname); return EXIT_FAILURE; } /* 3. encode as CBOR */ // We're going to reuse the buffer, as CBOR is usually shorter than the equivalent JSON CborEncoder encoder; cbor_encoder_init(&encoder, buffer, buffersize, 0); CborError err = decode_json(doc, &encoder); cJSON_Delete(doc); if (err) { fprintf(stderr, "json2cbor: %s: error encoding to CBOR: %s\n", fname, cbor_error_string(err)); return EXIT_FAILURE; } fwrite(buffer, 1, cb.ptr - buffer, stdout); free(buffer); return EXIT_SUCCESS; }