]>
Commit | Line | Data |
---|---|---|
0bb51450 OM |
1 | /**************************************************************************** |
2 | ** | |
3 | ** Copyright (C) 2018 Intel Corporation | |
4 | ** | |
5 | ** Permission is hereby granted, free of charge, to any person obtaining a copy | |
6 | ** of this software and associated documentation files (the "Software"), to deal | |
7 | ** in the Software without restriction, including without limitation the rights | |
8 | ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
9 | ** copies of the Software, and to permit persons to whom the Software is | |
10 | ** furnished to do so, subject to the following conditions: | |
11 | ** | |
12 | ** The above copyright notice and this permission notice shall be included in | |
13 | ** all copies or substantial portions of the Software. | |
14 | ** | |
15 | ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
18 | ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
20 | ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
21 | ** THE SOFTWARE. | |
22 | ** | |
23 | ****************************************************************************/ | |
24 | ||
25 | #define _BSD_SOURCE 1 | |
26 | #define _DEFAULT_SOURCE 1 | |
27 | #define _GNU_SOURCE 1 | |
28 | #define _POSIX_C_SOURCE 200809L | |
29 | #ifndef __STDC_LIMIT_MACROS | |
30 | # define __STDC_LIMIT_MACROS 1 | |
31 | #endif | |
32 | ||
33 | #include "cbor.h" | |
34 | #include "cborjson.h" | |
35 | #include "cborinternal_p.h" | |
36 | #include "compilersupport_p.h" | |
37 | ||
38 | #include <inttypes.h> | |
39 | #include <stdio.h> | |
40 | #include <stdlib.h> | |
41 | #include <string.h> | |
42 | ||
43 | /** | |
44 | * \defgroup CborToJson Converting CBOR to JSON | |
45 | * \brief Group of functions used to convert CBOR to JSON. | |
46 | * | |
47 | * This group contains two functions that can be used to convert a \ref | |
48 | * CborValue object to an equivalent JSON representation. This module attempts | |
49 | * to follow the recommendations from RFC 7049 section 4.1 "Converting from | |
50 | * CBOR to JSON", though it has a few differences. They are noted below. | |
51 | * | |
52 | * These functions produce a "minified" JSON output, with no spacing, | |
53 | * indentation or line breaks. If those are necessary, they need to be applied | |
54 | * in a post-processing phase. | |
55 | * | |
56 | * Note that JSON cannot support all CBOR types with fidelity, so the | |
57 | * conversion is usually lossy. For that reason, TinyCBOR supports adding a set | |
58 | * of metadata JSON values that can be used by a JSON-to-CBOR converter to | |
59 | * restore the original data types. | |
60 | * | |
61 | * The TinyCBOR library does not provide a way to convert from JSON | |
62 | * representation back to encoded form. However, it provides a tool called | |
63 | * \c json2cbor which can be used for that purpose. That tool supports the | |
64 | * metadata format that these functions may produce. | |
65 | * | |
66 | * Either of the functions in this section will attempt to convert exactly one | |
67 | * CborValue object to JSON. Those functions may return any error documented | |
68 | * for the functions for CborParsing. In addition, if the C standard library | |
69 | * stream functions return with error, the text conversion will return with | |
70 | * error CborErrorIO. | |
71 | * | |
72 | * These functions also perform UTF-8 validation in CBOR text strings. If they | |
73 | * encounter a sequence of bytes that is not permitted in UTF-8, they will return | |
74 | * CborErrorInvalidUtf8TextString. That includes encoding of surrogate points | |
75 | * in UTF-8. | |
76 | * | |
77 | * \warning The metadata produced by these functions is not guaranteed to | |
78 | * remain stable. A future update of TinyCBOR may produce different output for | |
79 | * the same input and parsers may be unable to handle it. | |
80 | * | |
81 | * \sa CborParsing, CborPretty, cbor_parser_init() | |
82 | */ | |
83 | ||
84 | /** | |
85 | * \addtogroup CborToJson | |
86 | * @{ | |
87 | * <h2 class="groupheader">Conversion limitations</h2> | |
88 | * | |
89 | * When converting from CBOR to JSON, there may be information loss. This | |
90 | * section lists the possible scenarios. | |
91 | * | |
92 | * \par Number precision: | |
93 | * ALL JSON numbers, due to its JavaScript heritage, are IEEE 754 | |
94 | * double-precision floating point. This means JSON is not capable of | |
95 | * representing all integers numbers outside the range [-(2<sup>53</sup>)+1, | |
96 | * 2<sup>53</sup>-1] and is not capable of representing NaN or infinite. If the | |
97 | * CBOR data contains a number outside the valid range, the conversion will | |
98 | * lose precision. If the input was NaN or infinite, the result of the | |
99 | * conversion will be the JSON null value. In addition, the distinction between | |
100 | * half-, single- and double-precision is lost. | |
101 | * | |
102 | * \par | |
103 | * If enabled, the original value and original type are stored in the metadata. | |
104 | * | |
105 | * \par Non-native types: | |
106 | * CBOR's type system is richer than JSON's, which means some data values | |
107 | * cannot be represented when converted to JSON. The conversion silently turns | |
108 | * them into strings: CBOR simple types become "simple(nn)" where \c nn is the | |
109 | * simple type's value, with the exception of CBOR undefined, which becomes | |
110 | * "undefined", while CBOR byte strings are converted to an Base16, Base64, or | |
111 | * Base64url encoding | |
112 | * | |
113 | * \par | |
114 | * If enabled, the original type is stored in the metadata. | |
115 | * | |
116 | * \par Presence of tags: | |
117 | * JSON has no support for tagged values, so by default tags are dropped when | |
118 | * converting to JSON. However, if the CborConvertObeyByteStringTags option is | |
119 | * active (default), then certain known tags are honored and are used to format | |
120 | * the conversion of the tagged byte string to JSON. | |
121 | * | |
122 | * \par | |
123 | * If the CborConvertTagsToObjects option is active, then the tag and the | |
124 | * tagged value are converted to a JSON object. Otherwise, if enabled, the | |
125 | * last (innermost) tag is stored in the metadata. | |
126 | * | |
127 | * \par Non-string keys in maps: | |
128 | * JSON requires all Object keys to be strings, while CBOR does not. By | |
129 | * default, if a non-string key is found, the conversion fails with error | |
130 | * CborErrorJsonObjectKeyNotString. If the CborConvertStringifyMapKeys option | |
131 | * is active, then the conversion attempts to create a string representation | |
132 | * using CborPretty. Note that the \c json2cbor tool is not able to parse this | |
133 | * back to the original form. | |
134 | * | |
135 | * \par Duplicate keys in maps: | |
136 | * Neither JSON nor CBOR allow duplicated keys, but current TinyCBOR does not | |
137 | * validate that this is the case. If there are duplicated keys in the input, | |
138 | * they will be repeated in the output, which many JSON tools may flag as | |
139 | * invalid. In addition to that, if the CborConvertStringifyMapKeys option is | |
140 | * active, it is possible that a non-string key in a CBOR map will be converted | |
141 | * to a string form that is identical to another key. | |
142 | * | |
143 | * \par | |
144 | * When metadata support is active, the conversion will add extra key-value | |
145 | * pairs to the JSON output so it can store the metadata. It is possible that | |
146 | * the keys for the metadata clash with existing keys in the JSON map. | |
147 | */ | |
148 | ||
149 | extern FILE *open_memstream(char **bufptr, size_t *sizeptr); | |
150 | ||
151 | enum ConversionStatusFlags { | |
152 | TypeWasNotNative = 0x100, /* anything but strings, boolean, null, arrays and maps */ | |
153 | TypeWasTagged = 0x200, | |
154 | NumberPrecisionWasLost = 0x400, | |
155 | NumberWasNaN = 0x800, | |
156 | NumberWasInfinite = 0x1000, | |
157 | NumberWasNegative = 0x2000, /* only used with NumberWasInifite or NumberWasTooBig */ | |
158 | ||
159 | FinalTypeMask = 0xff | |
160 | }; | |
161 | ||
162 | typedef struct ConversionStatus { | |
163 | CborTag lastTag; | |
164 | uint64_t originalNumber; | |
165 | int flags; | |
166 | } ConversionStatus; | |
167 | ||
168 | static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, ConversionStatus *status); | |
169 | ||
170 | static CborError dump_bytestring_base16(char **result, CborValue *it) | |
171 | { | |
172 | static const char characters[] = "0123456789abcdef"; | |
173 | size_t i; | |
174 | size_t n = 0; | |
175 | uint8_t *buffer; | |
176 | CborError err = cbor_value_calculate_string_length(it, &n); | |
177 | if (err) | |
178 | return err; | |
179 | ||
180 | /* a Base16 (hex) output is twice as big as our buffer */ | |
181 | buffer = (uint8_t *)malloc(n * 2 + 1); | |
182 | *result = (char *)buffer; | |
183 | ||
184 | /* let cbor_value_copy_byte_string know we have an extra byte for the terminating NUL */ | |
185 | ++n; | |
186 | err = cbor_value_copy_byte_string(it, buffer + n - 1, &n, it); | |
187 | cbor_assert(err == CborNoError); | |
188 | ||
189 | for (i = 0; i < n; ++i) { | |
190 | uint8_t byte = buffer[n + i]; | |
191 | buffer[2*i] = characters[byte >> 4]; | |
192 | buffer[2*i + 1] = characters[byte & 0xf]; | |
193 | } | |
194 | return CborNoError; | |
195 | } | |
196 | ||
197 | static CborError generic_dump_base64(char **result, CborValue *it, const char alphabet[65]) | |
198 | { | |
199 | size_t n = 0, i; | |
200 | uint8_t *buffer, *out, *in; | |
201 | CborError err = cbor_value_calculate_string_length(it, &n); | |
202 | if (err) | |
203 | return err; | |
204 | ||
205 | /* a Base64 output (untruncated) has 4 bytes for every 3 in the input */ | |
206 | size_t len = (n + 5) / 3 * 4; | |
207 | out = buffer = (uint8_t *)malloc(len + 1); | |
208 | *result = (char *)buffer; | |
209 | ||
210 | /* we read our byte string at the tail end of the buffer | |
211 | * so we can do an in-place conversion while iterating forwards */ | |
212 | in = buffer + len - n; | |
213 | ||
214 | /* let cbor_value_copy_byte_string know we have an extra byte for the terminating NUL */ | |
215 | ++n; | |
216 | err = cbor_value_copy_byte_string(it, in, &n, it); | |
217 | cbor_assert(err == CborNoError); | |
218 | ||
219 | uint_least32_t val = 0; | |
220 | for (i = 0; n - i >= 3; i += 3) { | |
221 | /* read 3 bytes x 8 bits = 24 bits */ | |
222 | if (false) { | |
223 | #ifdef __GNUC__ | |
224 | } else if (i) { | |
225 | __builtin_memcpy(&val, in + i - 1, sizeof(val)); | |
226 | val = cbor_ntohl(val); | |
227 | #endif | |
228 | } else { | |
229 | val = (in[i] << 16) | (in[i + 1] << 8) | in[i + 2]; | |
230 | } | |
231 | ||
232 | /* write 4 chars x 6 bits = 24 bits */ | |
233 | *out++ = alphabet[(val >> 18) & 0x3f]; | |
234 | *out++ = alphabet[(val >> 12) & 0x3f]; | |
235 | *out++ = alphabet[(val >> 6) & 0x3f]; | |
236 | *out++ = alphabet[val & 0x3f]; | |
237 | } | |
238 | ||
239 | /* maybe 1 or 2 bytes left */ | |
240 | if (n - i) { | |
241 | /* we can read in[i + 1] even if it's past the end of the string because | |
242 | * we know (by construction) that it's a NUL byte */ | |
243 | #ifdef __GNUC__ | |
244 | uint16_t val16; | |
245 | __builtin_memcpy(&val16, in + i, sizeof(val16)); | |
246 | val = cbor_ntohs(val16); | |
247 | #else | |
248 | val = (in[i] << 8) | in[i + 1]; | |
249 | #endif | |
250 | val <<= 8; | |
251 | ||
252 | /* the 65th character in the alphabet is our filler: either '=' or '\0' */ | |
253 | out[4] = '\0'; | |
254 | out[3] = alphabet[64]; | |
255 | if (n - i == 2) { | |
256 | /* write the third char in 3 chars x 6 bits = 18 bits */ | |
257 | out[2] = alphabet[(val >> 6) & 0x3f]; | |
258 | } else { | |
259 | out[2] = alphabet[64]; /* filler */ | |
260 | } | |
261 | out[1] = alphabet[(val >> 12) & 0x3f]; | |
262 | out[0] = alphabet[(val >> 18) & 0x3f]; | |
263 | } else { | |
264 | out[0] = '\0'; | |
265 | } | |
266 | ||
267 | return CborNoError; | |
268 | } | |
269 | ||
270 | static CborError dump_bytestring_base64(char **result, CborValue *it) | |
271 | { | |
272 | static const char alphabet[] = "ABCDEFGH" "IJKLMNOP" "QRSTUVWX" "YZabcdef" | |
273 | "ghijklmn" "opqrstuv" "wxyz0123" "456789+/" "="; | |
274 | return generic_dump_base64(result, it, alphabet); | |
275 | } | |
276 | ||
277 | static CborError dump_bytestring_base64url(char **result, CborValue *it) | |
278 | { | |
279 | static const char alphabet[] = "ABCDEFGH" "IJKLMNOP" "QRSTUVWX" "YZabcdef" | |
280 | "ghijklmn" "opqrstuv" "wxyz0123" "456789-_"; | |
281 | return generic_dump_base64(result, it, alphabet); | |
282 | } | |
283 | ||
284 | static CborError add_value_metadata(FILE *out, CborType type, const ConversionStatus *status) | |
285 | { | |
286 | int flags = status->flags; | |
287 | if (flags & TypeWasTagged) { | |
288 | /* extract the tagged type, which may be JSON native */ | |
289 | type = flags & FinalTypeMask; | |
290 | flags &= ~(FinalTypeMask | TypeWasTagged); | |
291 | ||
292 | if (fprintf(out, "\"tag\":\"%" PRIu64 "\"%s", status->lastTag, | |
293 | flags & ~TypeWasTagged ? "," : "") < 0) | |
294 | return CborErrorIO; | |
295 | } | |
296 | ||
297 | if (!flags) | |
298 | return CborNoError; | |
299 | ||
300 | /* print at least the type */ | |
301 | if (fprintf(out, "\"t\":%d", type) < 0) | |
302 | return CborErrorIO; | |
303 | ||
304 | if (flags & NumberWasNaN) | |
305 | if (fprintf(out, ",\"v\":\"nan\"") < 0) | |
306 | return CborErrorIO; | |
307 | if (flags & NumberWasInfinite) | |
308 | if (fprintf(out, ",\"v\":\"%sinf\"", flags & NumberWasNegative ? "-" : "") < 0) | |
309 | return CborErrorIO; | |
310 | if (flags & NumberPrecisionWasLost) | |
311 | if (fprintf(out, ",\"v\":\"%c%" PRIx64 "\"", flags & NumberWasNegative ? '-' : '+', | |
312 | status->originalNumber) < 0) | |
313 | return CborErrorIO; | |
314 | if (type == CborSimpleType) | |
315 | if (fprintf(out, ",\"v\":%d", (int)status->originalNumber) < 0) | |
316 | return CborErrorIO; | |
317 | return CborNoError; | |
318 | } | |
319 | ||
320 | static CborError find_tagged_type(CborValue *it, CborTag *tag, CborType *type) | |
321 | { | |
322 | CborError err = CborNoError; | |
323 | *type = cbor_value_get_type(it); | |
324 | while (*type == CborTagType) { | |
325 | cbor_value_get_tag(it, tag); /* can't fail */ | |
326 | err = cbor_value_advance_fixed(it); | |
327 | if (err) | |
328 | return err; | |
329 | ||
330 | *type = cbor_value_get_type(it); | |
331 | } | |
332 | return err; | |
333 | } | |
334 | ||
335 | static CborError tagged_value_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) | |
336 | { | |
337 | CborTag tag; | |
338 | CborError err; | |
339 | ||
340 | if (flags & CborConvertTagsToObjects) { | |
341 | cbor_value_get_tag(it, &tag); /* can't fail */ | |
342 | err = cbor_value_advance_fixed(it); | |
343 | if (err) | |
344 | return err; | |
345 | ||
346 | if (fprintf(out, "{\"tag%" PRIu64 "\":", tag) < 0) | |
347 | return CborErrorIO; | |
348 | ||
349 | CborType type = cbor_value_get_type(it); | |
350 | err = value_to_json(out, it, flags, type, status); | |
351 | if (err) | |
352 | return err; | |
353 | if (flags & CborConvertAddMetadata && status->flags) { | |
354 | if (fprintf(out, ",\"tag%" PRIu64 "$cbor\":{", tag) < 0 || | |
355 | add_value_metadata(out, type, status) != CborNoError || | |
356 | fputc('}', out) < 0) | |
357 | return CborErrorIO; | |
358 | } | |
359 | if (fputc('}', out) < 0) | |
360 | return CborErrorIO; | |
361 | status->flags = TypeWasNotNative | CborTagType; | |
362 | return CborNoError; | |
363 | } | |
364 | ||
365 | CborType type; | |
366 | err = find_tagged_type(it, &status->lastTag, &type); | |
367 | if (err) | |
368 | return err; | |
369 | tag = status->lastTag; | |
370 | ||
371 | /* special handling of byte strings? */ | |
372 | if (type == CborByteStringType && (flags & CborConvertByteStringsToBase64Url) == 0 && | |
373 | (tag == CborNegativeBignumTag || tag == CborExpectedBase16Tag || tag == CborExpectedBase64Tag)) { | |
374 | char *str; | |
375 | char *pre = ""; | |
376 | ||
377 | if (tag == CborNegativeBignumTag) { | |
378 | pre = "~"; | |
379 | err = dump_bytestring_base64url(&str, it); | |
380 | } else if (tag == CborExpectedBase64Tag) { | |
381 | err = dump_bytestring_base64(&str, it); | |
382 | } else { /* tag == CborExpectedBase16Tag */ | |
383 | err = dump_bytestring_base16(&str, it); | |
384 | } | |
385 | if (err) | |
386 | return err; | |
387 | err = fprintf(out, "\"%s%s\"", pre, str) < 0 ? CborErrorIO : CborNoError; | |
388 | free(str); | |
389 | status->flags = TypeWasNotNative | TypeWasTagged | CborByteStringType; | |
390 | return err; | |
391 | } | |
392 | ||
393 | /* no special handling */ | |
394 | err = value_to_json(out, it, flags, type, status); | |
395 | status->flags |= TypeWasTagged | type; | |
396 | return err; | |
397 | } | |
398 | ||
399 | static CborError stringify_map_key(char **key, CborValue *it, int flags, CborType type) | |
400 | { | |
401 | (void)flags; /* unused */ | |
402 | (void)type; /* unused */ | |
403 | #ifdef WITHOUT_OPEN_MEMSTREAM | |
404 | (void)key; /* unused */ | |
405 | (void)it; /* unused */ | |
406 | return CborErrorJsonNotImplemented; | |
407 | #else | |
408 | size_t size; | |
409 | ||
410 | FILE *memstream = open_memstream(key, &size); | |
411 | if (memstream == NULL) | |
412 | return CborErrorOutOfMemory; /* could also be EMFILE, but it's unlikely */ | |
413 | CborError err = cbor_value_to_pretty_advance(memstream, it); | |
414 | ||
415 | if (unlikely(fclose(memstream) < 0 || *key == NULL)) | |
416 | return CborErrorInternalError; | |
417 | return err; | |
418 | #endif | |
419 | } | |
420 | ||
421 | static CborError array_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) | |
422 | { | |
423 | const char *comma = ""; | |
424 | while (!cbor_value_at_end(it)) { | |
425 | if (fprintf(out, "%s", comma) < 0) | |
426 | return CborErrorIO; | |
427 | comma = ","; | |
428 | ||
429 | CborError err = value_to_json(out, it, flags, cbor_value_get_type(it), status); | |
430 | if (err) | |
431 | return err; | |
432 | } | |
433 | return CborNoError; | |
434 | } | |
435 | ||
436 | static CborError map_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) | |
437 | { | |
438 | const char *comma = ""; | |
439 | CborError err; | |
440 | while (!cbor_value_at_end(it)) { | |
441 | char *key; | |
442 | if (fprintf(out, "%s", comma) < 0) | |
443 | return CborErrorIO; | |
444 | comma = ","; | |
445 | ||
446 | CborType keyType = cbor_value_get_type(it); | |
447 | if (likely(keyType == CborTextStringType)) { | |
448 | size_t n = 0; | |
449 | err = cbor_value_dup_text_string(it, &key, &n, it); | |
450 | } else if (flags & CborConvertStringifyMapKeys) { | |
451 | err = stringify_map_key(&key, it, flags, keyType); | |
452 | } else { | |
453 | return CborErrorJsonObjectKeyNotString; | |
454 | } | |
455 | if (err) | |
456 | return err; | |
457 | ||
458 | /* first, print the key */ | |
459 | if (fprintf(out, "\"%s\":", key) < 0) { | |
460 | free(key); | |
461 | return CborErrorIO; | |
462 | } | |
463 | ||
464 | /* then, print the value */ | |
465 | CborType valueType = cbor_value_get_type(it); | |
466 | err = value_to_json(out, it, flags, valueType, status); | |
467 | ||
468 | /* finally, print any metadata we may have */ | |
469 | if (flags & CborConvertAddMetadata) { | |
470 | if (!err && keyType != CborTextStringType) { | |
471 | if (fprintf(out, ",\"%s$keycbordump\":true", key) < 0) | |
472 | err = CborErrorIO; | |
473 | } | |
474 | if (!err && status->flags) { | |
475 | if (fprintf(out, ",\"%s$cbor\":{", key) < 0 || | |
476 | add_value_metadata(out, valueType, status) != CborNoError || | |
477 | fputc('}', out) < 0) | |
478 | err = CborErrorIO; | |
479 | } | |
480 | } | |
481 | ||
482 | free(key); | |
483 | if (err) | |
484 | return err; | |
485 | } | |
486 | return CborNoError; | |
487 | } | |
488 | ||
489 | static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, ConversionStatus *status) | |
490 | { | |
491 | CborError err; | |
492 | status->flags = 0; | |
493 | ||
494 | switch (type) { | |
495 | case CborArrayType: | |
496 | case CborMapType: { | |
497 | /* recursive type */ | |
498 | CborValue recursed; | |
499 | err = cbor_value_enter_container(it, &recursed); | |
500 | if (err) { | |
501 | it->ptr = recursed.ptr; | |
502 | return err; /* parse error */ | |
503 | } | |
504 | if (fputc(type == CborArrayType ? '[' : '{', out) < 0) | |
505 | return CborErrorIO; | |
506 | ||
507 | err = (type == CborArrayType) ? | |
508 | array_to_json(out, &recursed, flags, status) : | |
509 | map_to_json(out, &recursed, flags, status); | |
510 | if (err) { | |
511 | it->ptr = recursed.ptr; | |
512 | return err; /* parse error */ | |
513 | } | |
514 | ||
515 | if (fputc(type == CborArrayType ? ']' : '}', out) < 0) | |
516 | return CborErrorIO; | |
517 | err = cbor_value_leave_container(it, &recursed); | |
518 | if (err) | |
519 | return err; /* parse error */ | |
520 | ||
521 | status->flags = 0; /* reset, there are never conversion errors for us */ | |
522 | return CborNoError; | |
523 | } | |
524 | ||
525 | case CborIntegerType: { | |
526 | double num; /* JS numbers are IEEE double precision */ | |
527 | uint64_t val; | |
528 | cbor_value_get_raw_integer(it, &val); /* can't fail */ | |
529 | num = (double)val; | |
530 | ||
531 | if (cbor_value_is_negative_integer(it)) { | |
532 | num = -num - 1; /* convert to negative */ | |
533 | if ((uint64_t)(-num - 1) != val) { | |
534 | status->flags = NumberPrecisionWasLost | NumberWasNegative; | |
535 | status->originalNumber = val; | |
536 | } | |
537 | } else { | |
538 | if ((uint64_t)num != val) { | |
539 | status->flags = NumberPrecisionWasLost; | |
540 | status->originalNumber = val; | |
541 | } | |
542 | } | |
543 | if (fprintf(out, "%.0f", num) < 0) /* this number has no fraction, so no decimal points please */ | |
544 | return CborErrorIO; | |
545 | break; | |
546 | } | |
547 | ||
548 | case CborByteStringType: | |
549 | case CborTextStringType: { | |
550 | char *str; | |
551 | if (type == CborByteStringType) { | |
552 | err = dump_bytestring_base64url(&str, it); | |
553 | status->flags = TypeWasNotNative; | |
554 | } else { | |
555 | size_t n = 0; | |
556 | err = cbor_value_dup_text_string(it, &str, &n, it); | |
557 | } | |
558 | if (err) | |
559 | return err; | |
560 | err = (fprintf(out, "\"%s\"", str) < 0) ? CborErrorIO : CborNoError; | |
561 | free(str); | |
562 | return err; | |
563 | } | |
564 | ||
565 | case CborTagType: | |
566 | return tagged_value_to_json(out, it, flags, status); | |
567 | ||
568 | case CborSimpleType: { | |
569 | uint8_t simple_type; | |
570 | cbor_value_get_simple_type(it, &simple_type); /* can't fail */ | |
571 | status->flags = TypeWasNotNative; | |
572 | status->originalNumber = simple_type; | |
573 | if (fprintf(out, "\"simple(%" PRIu8 ")\"", simple_type) < 0) | |
574 | return CborErrorIO; | |
575 | break; | |
576 | } | |
577 | ||
578 | case CborNullType: | |
579 | if (fprintf(out, "null") < 0) | |
580 | return CborErrorIO; | |
581 | break; | |
582 | ||
583 | case CborUndefinedType: | |
584 | status->flags = TypeWasNotNative; | |
585 | if (fprintf(out, "\"undefined\"") < 0) | |
586 | return CborErrorIO; | |
587 | break; | |
588 | ||
589 | case CborBooleanType: { | |
590 | bool val; | |
591 | cbor_value_get_boolean(it, &val); /* can't fail */ | |
592 | if (fprintf(out, val ? "true" : "false") < 0) | |
593 | return CborErrorIO; | |
594 | break; | |
595 | } | |
596 | ||
597 | #ifndef CBOR_NO_FLOATING_POINT | |
598 | case CborDoubleType: { | |
599 | double val; | |
600 | if (false) { | |
601 | float f; | |
602 | case CborFloatType: | |
603 | status->flags = TypeWasNotNative; | |
604 | cbor_value_get_float(it, &f); | |
605 | val = f; | |
606 | } else if (false) { | |
607 | uint16_t f16; | |
608 | case CborHalfFloatType: | |
609 | # ifndef CBOR_NO_HALF_FLOAT_TYPE | |
610 | status->flags = TypeWasNotNative; | |
611 | cbor_value_get_half_float(it, &f16); | |
612 | val = decode_half(f16); | |
613 | # else | |
614 | (void)f16; | |
615 | err = CborErrorUnsupportedType; | |
616 | break; | |
617 | # endif | |
618 | } else { | |
619 | cbor_value_get_double(it, &val); | |
620 | } | |
621 | ||
622 | int r = fpclassify(val); | |
623 | if (r == FP_NAN || r == FP_INFINITE) { | |
624 | if (fprintf(out, "null") < 0) | |
625 | return CborErrorIO; | |
626 | status->flags |= r == FP_NAN ? NumberWasNaN : | |
627 | NumberWasInfinite | (val < 0 ? NumberWasNegative : 0); | |
628 | } else { | |
629 | uint64_t ival = (uint64_t)fabs(val); | |
630 | if ((double)ival == fabs(val)) { | |
631 | /* print as integer so we get the full precision */ | |
632 | r = fprintf(out, "%s%" PRIu64, val < 0 ? "-" : "", ival); | |
633 | status->flags |= TypeWasNotNative; /* mark this integer number as a double */ | |
634 | } else { | |
635 | /* this number is definitely not a 64-bit integer */ | |
636 | r = fprintf(out, "%." DBL_DECIMAL_DIG_STR "g", val); | |
637 | } | |
638 | if (r < 0) | |
639 | return CborErrorIO; | |
640 | } | |
641 | break; | |
642 | } | |
643 | #else | |
644 | case CborDoubleType: | |
645 | case CborFloatType: | |
646 | case CborHalfFloatType: | |
647 | err = CborErrorUnsupportedType; | |
648 | break; | |
649 | #endif /* !CBOR_NO_FLOATING_POINT */ | |
650 | ||
651 | case CborInvalidType: | |
652 | return CborErrorUnknownType; | |
653 | } | |
654 | ||
655 | return cbor_value_advance_fixed(it); | |
656 | } | |
657 | ||
658 | /** | |
659 | * \enum CborToJsonFlags | |
660 | * The CborToJsonFlags enum contains flags that control the conversion of CBOR to JSON. | |
661 | * | |
662 | * \value CborConvertAddMetadata Adds metadata to facilitate restoration of the original CBOR data. | |
663 | * \value CborConvertTagsToObjects Converts CBOR tags to JSON objects | |
664 | * \value CborConvertIgnoreTags (default) Ignore CBOR tags, except for byte strings | |
665 | * \value CborConvertObeyByteStringTags (default) Honor formatting of CBOR byte strings if so tagged | |
666 | * \value CborConvertByteStringsToBase64Url Force the conversion of all CBOR byte strings to Base64url encoding, despite any tags | |
667 | * \value CborConvertRequireMapStringKeys (default) Require CBOR map keys to be strings, failing the conversion if they are not | |
668 | * \value CborConvertStringifyMapKeys Convert non-string keys in CBOR maps to a string form | |
669 | * \value CborConvertDefaultFlags Default conversion flags. | |
670 | */ | |
671 | ||
672 | /** | |
673 | * \fn CborError cbor_value_to_json(FILE *out, const CborValue *value, int flags) | |
674 | * | |
675 | * Converts the current CBOR type pointed to by \a value to JSON and writes that | |
676 | * to the \a out stream. If an error occurs, this function returns an error | |
677 | * code similar to CborParsing. The \a flags parameter indicates one or more of | |
678 | * the flags from CborToJsonFlags that control the conversion. | |
679 | * | |
680 | * \sa cbor_value_to_json_advance(), cbor_value_to_pretty() | |
681 | */ | |
682 | ||
683 | /** | |
684 | * Converts the current CBOR type pointed to by \a value to JSON and writes that | |
685 | * to the \a out stream. If an error occurs, this function returns an error | |
686 | * code similar to CborParsing. The \a flags parameter indicates one or more of | |
687 | * the flags from CborToJsonFlags that control the conversion. | |
688 | * | |
689 | * If no error ocurred, this function advances \a value to the next element. | |
690 | * | |
691 | * \sa cbor_value_to_json(), cbor_value_to_pretty_advance() | |
692 | */ | |
693 | CborError cbor_value_to_json_advance(FILE *out, CborValue *value, int flags) | |
694 | { | |
695 | ConversionStatus status; | |
696 | return value_to_json(out, value, flags, cbor_value_get_type(value), &status); | |
697 | } | |
698 | ||
699 | /** @} */ |