]>
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 | #ifndef __STDC_LIMIT_MACROS | |
28 | # define __STDC_LIMIT_MACROS 1 | |
29 | #endif | |
30 | ||
31 | #include "cbor.h" | |
32 | #include "cborinternal_p.h" | |
33 | #include "compilersupport_p.h" | |
34 | #include "utf8_p.h" | |
35 | ||
36 | #include <inttypes.h> | |
37 | #include <string.h> | |
38 | ||
39 | /** | |
40 | * \defgroup CborPretty Converting CBOR to text | |
41 | * \brief Group of functions used to convert CBOR to text form. | |
42 | * | |
43 | * This group contains two functions that can be used to convert a \ref | |
44 | * CborValue object to a text representation. This module attempts to follow | |
45 | * the recommendations from RFC 7049 section 6 "Diagnostic Notation", though it | |
46 | * has a few differences. They are noted below. | |
47 | * | |
48 | * TinyCBOR does not provide a way to convert from the text representation back | |
49 | * to encoded form. To produce a text form meant to be parsed, CborToJson is | |
50 | * recommended instead. | |
51 | * | |
52 | * Either of the functions in this section will attempt to convert exactly one | |
53 | * CborValue object to text. Those functions may return any error documented | |
54 | * for the functions for CborParsing. In addition, if the C standard library | |
55 | * stream functions return with error, the text conversion will return with | |
56 | * error CborErrorIO. | |
57 | * | |
58 | * These functions also perform UTF-8 validation in CBOR text strings. If they | |
59 | * encounter a sequence of bytes that is not permitted in UTF-8, they will return | |
60 | * CborErrorInvalidUtf8TextString. That includes encoding of surrogate points | |
61 | * in UTF-8. | |
62 | * | |
63 | * \warning The output type produced by these functions is not guaranteed to | |
64 | * remain stable. A future update of TinyCBOR may produce different output for | |
65 | * the same input and parsers may be unable to handle it. | |
66 | * | |
67 | * \sa CborParsing, CborToJson, cbor_parser_init() | |
68 | */ | |
69 | ||
70 | /** | |
71 | * \addtogroup CborPretty | |
72 | * @{ | |
73 | * <h2 class="groupheader">Text format</h2> | |
74 | * | |
75 | * As described in RFC 7049 section 6 "Diagnostic Notation", the format is | |
76 | * largely borrowed from JSON, but modified to suit CBOR's different data | |
77 | * types. TinyCBOR makes further modifications to distinguish different, but | |
78 | * similar values. | |
79 | * | |
80 | * CBOR values are currently encoded as follows: | |
81 | * \par Integrals (unsigned and negative) | |
82 | * Base-10 (decimal) text representation of the value | |
83 | * \par Byte strings: | |
84 | * <tt>"h'"</tt> followed by the Base16 (hex) representation of the binary data, followed by an ending quote (') | |
85 | * \par Text strings: | |
86 | * C-style escaped string in quotes, with C11/C++11 escaping of Unicode codepoints above U+007F. | |
87 | * \par Tags: | |
88 | * Tag value, with the tagged value in parentheses. No special encoding of the tagged value is performed. | |
89 | * \par Simple types: | |
90 | * <tt>"simple(nn)"</tt> where \c nn is the simple value | |
91 | * \par Null: | |
92 | * \c null | |
93 | * \par Undefined: | |
94 | * \c undefined | |
95 | * \par Booleans: | |
96 | * \c true or \c false | |
97 | * \par Floating point: | |
98 | * If NaN or infinite, the actual words \c NaN or \c infinite. | |
99 | * Otherwise, the decimal representation with as many digits as necessary to ensure no loss of information. | |
100 | * By default, float values are suffixed by "f" and half-float values suffixed by "f16" (doubles have no suffix). | |
101 | * If the CborPrettyNumericEncodingIndicators flag is active, the values instead are encoded following the | |
102 | * Section 6 recommended encoding indicators: float values are suffixed with "_2" and half-float with "_1". | |
103 | * A decimal point is always present. | |
104 | * \par Arrays: | |
105 | * Comma-separated list of elements, enclosed in square brackets ("[" and "]"). | |
106 | * \par Maps: | |
107 | * Comma-separated list of key-value pairs, with the key and value separated | |
108 | * by a colon (":"), enclosed in curly braces ("{" and "}"). | |
109 | * | |
110 | * The CborPrettyFlags enumerator contains flags to control some aspects of the | |
111 | * encoding: | |
112 | * \par String fragmentation | |
113 | * When the CborPrettyShowStringFragments option is active, text and byte | |
114 | * strings that are transmitted in fragments are shown instead inside | |
115 | * parentheses ("(" and ")") with no preceding number and each fragment is | |
116 | * displayed individually. If a tag precedes the string, then the output | |
117 | * will contain a double set of parentheses. If the option is not active, | |
118 | * the fragments are merged together and the display will not show any | |
119 | * difference from a string transmitted with determinate length. | |
120 | * \par Encoding indicators | |
121 | * Numbers and lengths in CBOR can be encoded in multiple representations. | |
122 | * If the CborPrettyIndicateOverlongNumbers option is active, numbers | |
123 | * and lengths that are transmitted in a longer encoding than necessary | |
124 | * will be indicated, by appending an underscore ("_") to either the | |
125 | * number or the opening bracket or brace, followed by a number | |
126 | * indicating the CBOR additional information: 0 for 1 byte, 1 for 2 | |
127 | * bytes, 2 for 4 bytes and 3 for 8 bytes. | |
128 | * If the CborPrettyIndicateIndeterminateLength option is active, maps, | |
129 | * arrays and strings encoded with indeterminate length will be marked by | |
130 | * an underscore after the opening bracket or brace or the string (if not | |
131 | * showing fragments), without a number after it. | |
132 | */ | |
133 | ||
134 | /** | |
135 | * \enum CborPrettyFlags | |
136 | * The CborPrettyFlags enum contains flags that control the conversion of CBOR to text format. | |
137 | * | |
138 | * \value CborPrettyNumericEncodingIndicators Use numeric encoding indicators instead of textual for float and half-float. | |
139 | * \value CborPrettyTextualEncodingIndicators Use textual encoding indicators for float ("f") and half-float ("f16"). | |
140 | * \value CborPrettyIndicateIndeterminateLength (default) Indicate when a map or array has indeterminate length. | |
141 | * \value CborPrettyIndicateOverlongNumbers Indicate when a number or length was encoded with more bytes than needed. | |
142 | * \value CborPrettyShowStringFragments If the byte or text string is transmitted in chunks, show each individually. | |
143 | * \value CborPrettyMergeStringFragment Merge all chunked byte or text strings and display them in a single entry. | |
144 | * \value CborPrettyDefaultFlags Default conversion flags. | |
145 | */ | |
146 | ||
147 | #ifndef CBOR_NO_FLOATING_POINT | |
148 | static inline bool convertToUint64(double v, uint64_t *absolute) | |
149 | { | |
150 | double supremum; | |
151 | v = fabs(v); | |
152 | ||
153 | /* C11 standard section 6.3.1.4 "Real floating and integer" says: | |
154 | * | |
155 | * 1 When a finite value of real floating type is converted to an integer | |
156 | * type other than _Bool, the fractional part is discarded (i.e., the | |
157 | * value is truncated toward zero). If the value of the integral part | |
158 | * cannot be represented by the integer type, the behavior is undefined. | |
159 | * | |
160 | * So we must perform a range check that v <= UINT64_MAX, but we can't use | |
161 | * UINT64_MAX + 1.0 because the standard continues: | |
162 | * | |
163 | * 2 When a value of integer type is converted to a real floating type, if | |
164 | * the value being converted can be represented exactly in the new type, | |
165 | * it is unchanged. If the value being converted is in the range of | |
166 | * values that can be represented but cannot be represented exactly, the | |
167 | * result is either the nearest higher or nearest lower representable | |
168 | * value, chosen in an implementation-defined manner. | |
169 | */ | |
170 | supremum = -2.0 * INT64_MIN; /* -2 * (- 2^63) == 2^64 */ | |
171 | if (v >= supremum) | |
172 | return false; | |
173 | ||
174 | /* Now we can convert, these two conversions cannot be UB */ | |
175 | *absolute = v; | |
176 | return *absolute == v; | |
177 | } | |
178 | #endif | |
179 | ||
180 | static void printRecursionLimit(CborStreamFunction stream, void *out) | |
181 | { | |
182 | stream(out, "<nesting too deep, recursion stopped>"); | |
183 | } | |
184 | ||
185 | static CborError hexDump(CborStreamFunction stream, void *out, const void *ptr, size_t n) | |
186 | { | |
187 | const uint8_t *buffer = (const uint8_t *)ptr; | |
188 | CborError err = CborNoError; | |
189 | while (n-- && !err) | |
190 | err = stream(out, "%02" PRIx8, *buffer++); | |
191 | ||
192 | return err; | |
193 | } | |
194 | ||
195 | /* This function decodes buffer as UTF-8 and prints as escaped UTF-16. | |
196 | * On UTF-8 decoding error, it returns CborErrorInvalidUtf8TextString */ | |
197 | static CborError utf8EscapedDump(CborStreamFunction stream, void *out, const void *ptr, size_t n) | |
198 | { | |
199 | const uint8_t *buffer = (const uint8_t *)ptr; | |
200 | const uint8_t * const end = buffer + n; | |
201 | CborError err = CborNoError; | |
202 | ||
203 | while (buffer < end && !err) { | |
204 | uint32_t uc = get_utf8(&buffer, end); | |
205 | if (uc == ~0U) | |
206 | return CborErrorInvalidUtf8TextString; | |
207 | ||
208 | if (uc < 0x80) { | |
209 | /* single-byte UTF-8 */ | |
210 | unsigned char escaped = (unsigned char)uc; | |
211 | if (uc < 0x7f && uc >= 0x20 && uc != '\\' && uc != '"') { | |
212 | err = stream(out, "%c", (char)uc); | |
213 | continue; | |
214 | } | |
215 | ||
216 | /* print as an escape sequence */ | |
217 | switch (uc) { | |
218 | case '"': | |
219 | case '\\': | |
220 | break; | |
221 | case '\b': | |
222 | escaped = 'b'; | |
223 | break; | |
224 | case '\f': | |
225 | escaped = 'f'; | |
226 | break; | |
227 | case '\n': | |
228 | escaped = 'n'; | |
229 | break; | |
230 | case '\r': | |
231 | escaped = 'r'; | |
232 | break; | |
233 | case '\t': | |
234 | escaped = 't'; | |
235 | break; | |
236 | default: | |
237 | goto print_utf16; | |
238 | } | |
239 | err = stream(out, "\\%c", escaped); | |
240 | continue; | |
241 | } | |
242 | ||
243 | /* now print the sequence */ | |
244 | if (uc > 0xffffU) { | |
245 | /* needs surrogate pairs */ | |
246 | err = stream(out, "\\u%04" PRIX32 "\\u%04" PRIX32, | |
247 | (uc >> 10) + 0xd7c0, /* high surrogate */ | |
248 | (uc % 0x0400) + 0xdc00); | |
249 | } else { | |
250 | print_utf16: | |
251 | /* no surrogate pair needed */ | |
252 | err = stream(out, "\\u%04" PRIX32, uc); | |
253 | } | |
254 | } | |
255 | return err; | |
256 | } | |
257 | ||
258 | static const char *resolve_indicator(const uint8_t *ptr, const uint8_t *end, int flags) | |
259 | { | |
260 | static const char indicators[8][3] = { | |
261 | "_0", "_1", "_2", "_3", | |
262 | "", "", "", /* these are not possible */ | |
263 | "_" | |
264 | }; | |
265 | const char *no_indicator = indicators[5]; /* empty string */ | |
266 | uint8_t additional_information; | |
267 | uint8_t expected_information; | |
268 | uint64_t value; | |
269 | CborError err; | |
270 | ||
271 | if (ptr == end) | |
272 | return NULL; /* CborErrorUnexpectedEOF */ | |
273 | ||
274 | additional_information = (*ptr & SmallValueMask); | |
275 | if (additional_information < Value8Bit) | |
276 | return no_indicator; | |
277 | ||
278 | /* determine whether to show anything */ | |
279 | if ((flags & CborPrettyIndicateIndeterminateLength) && | |
280 | additional_information == IndefiniteLength) | |
281 | return indicators[IndefiniteLength - Value8Bit]; | |
282 | if ((flags & CborPrettyIndicateOverlongNumbers) == 0) | |
283 | return no_indicator; | |
284 | ||
285 | err = _cbor_value_extract_number(&ptr, end, &value); | |
286 | if (err) | |
287 | return NULL; /* CborErrorUnexpectedEOF */ | |
288 | ||
289 | expected_information = Value8Bit - 1; | |
290 | if (value >= Value8Bit) | |
291 | ++expected_information; | |
292 | if (value > 0xffU) | |
293 | ++expected_information; | |
294 | if (value > 0xffffU) | |
295 | ++expected_information; | |
296 | if (value > 0xffffffffU) | |
297 | ++expected_information; | |
298 | return expected_information == additional_information ? | |
299 | no_indicator : | |
300 | indicators[additional_information - Value8Bit]; | |
301 | } | |
302 | ||
303 | static const char *get_indicator(const CborValue *it, int flags) | |
304 | { | |
305 | return resolve_indicator(it->ptr, it->parser->end, flags); | |
306 | } | |
307 | ||
308 | static CborError value_to_pretty(CborStreamFunction stream, void *out, CborValue *it, int flags, int recursionsLeft); | |
309 | static CborError container_to_pretty(CborStreamFunction stream, void *out, CborValue *it, CborType containerType, | |
310 | int flags, int recursionsLeft) | |
311 | { | |
312 | const char *comma = ""; | |
313 | CborError err = CborNoError; | |
314 | ||
315 | if (!recursionsLeft) { | |
316 | printRecursionLimit(stream, out); | |
317 | return err; /* do allow the dumping to continue */ | |
318 | } | |
319 | ||
320 | while (!cbor_value_at_end(it) && !err) { | |
321 | err = stream(out, "%s", comma); | |
322 | comma = ", "; | |
323 | ||
324 | if (!err) | |
325 | err = value_to_pretty(stream, out, it, flags, recursionsLeft); | |
326 | ||
327 | if (containerType == CborArrayType) | |
328 | continue; | |
329 | ||
330 | /* map: that was the key, so get the value */ | |
331 | if (!err) | |
332 | err = stream(out, ": "); | |
333 | if (!err) | |
334 | err = value_to_pretty(stream, out, it, flags, recursionsLeft); | |
335 | } | |
336 | return err; | |
337 | } | |
338 | ||
339 | static CborError value_to_pretty(CborStreamFunction stream, void *out, CborValue *it, int flags, int recursionsLeft) | |
340 | { | |
341 | CborError err = CborNoError; | |
342 | CborType type = cbor_value_get_type(it); | |
343 | switch (type) { | |
344 | case CborArrayType: | |
345 | case CborMapType: { | |
346 | /* recursive type */ | |
347 | CborValue recursed; | |
348 | const char *indicator = get_indicator(it, flags); | |
349 | const char *space = *indicator ? " " : indicator; | |
350 | ||
351 | err = stream(out, "%c%s%s", type == CborArrayType ? '[' : '{', indicator, space); | |
352 | if (err) | |
353 | return err; | |
354 | ||
355 | err = cbor_value_enter_container(it, &recursed); | |
356 | if (err) { | |
357 | it->ptr = recursed.ptr; | |
358 | return err; /* parse error */ | |
359 | } | |
360 | err = container_to_pretty(stream, out, &recursed, type, flags, recursionsLeft - 1); | |
361 | if (err) { | |
362 | it->ptr = recursed.ptr; | |
363 | return err; /* parse error */ | |
364 | } | |
365 | err = cbor_value_leave_container(it, &recursed); | |
366 | if (err) | |
367 | return err; /* parse error */ | |
368 | ||
369 | return stream(out, type == CborArrayType ? "]" : "}"); | |
370 | } | |
371 | ||
372 | case CborIntegerType: { | |
373 | uint64_t val; | |
374 | cbor_value_get_raw_integer(it, &val); /* can't fail */ | |
375 | ||
376 | if (cbor_value_is_unsigned_integer(it)) { | |
377 | err = stream(out, "%" PRIu64, val); | |
378 | } else { | |
379 | /* CBOR stores the negative number X as -1 - X | |
380 | * (that is, -1 is stored as 0, -2 as 1 and so forth) */ | |
381 | if (++val) { /* unsigned overflow may happen */ | |
382 | err = stream(out, "-%" PRIu64, val); | |
383 | } else { | |
384 | /* overflown | |
385 | * 0xffff`ffff`ffff`ffff + 1 = | |
386 | * 0x1`0000`0000`0000`0000 = 18446744073709551616 (2^64) */ | |
387 | err = stream(out, "-18446744073709551616"); | |
388 | } | |
389 | } | |
390 | if (!err) | |
391 | err = stream(out, "%s", get_indicator(it, flags)); | |
392 | break; | |
393 | } | |
394 | ||
395 | case CborByteStringType: | |
396 | case CborTextStringType: { | |
397 | size_t n = 0; | |
398 | const void *ptr; | |
399 | bool showingFragments = (flags & CborPrettyShowStringFragments) && !cbor_value_is_length_known(it); | |
400 | const char *separator = ""; | |
401 | char close = '\''; | |
402 | char open[3] = "h'"; | |
403 | const char *indicator = NULL; | |
404 | ||
405 | if (type == CborTextStringType) { | |
406 | close = open[0] = '"'; | |
407 | open[1] = '\0'; | |
408 | } | |
409 | ||
410 | if (showingFragments) { | |
411 | err = stream(out, "(_ "); | |
412 | if (!err) | |
413 | err = _cbor_value_prepare_string_iteration(it); | |
414 | } else { | |
415 | err = stream(out, "%s", open); | |
416 | } | |
417 | ||
418 | while (!err) { | |
419 | if (showingFragments || indicator == NULL) { | |
420 | /* any iteration, except the second for a non-chunked string */ | |
421 | indicator = resolve_indicator(it->ptr, it->parser->end, flags); | |
422 | } | |
423 | ||
424 | err = _cbor_value_get_string_chunk(it, &ptr, &n, it); | |
425 | if (!ptr) | |
426 | break; | |
427 | ||
428 | if (!err && showingFragments) | |
429 | err = stream(out, "%s%s", separator, open); | |
430 | if (!err) | |
431 | err = (type == CborByteStringType ? | |
432 | hexDump(stream, out, ptr, n) : | |
433 | utf8EscapedDump(stream, out, ptr, n)); | |
434 | if (!err && showingFragments) { | |
435 | err = stream(out, "%c%s", close, indicator); | |
436 | separator = ", "; | |
437 | } | |
438 | } | |
439 | ||
440 | if (!err) { | |
441 | if (showingFragments) | |
442 | err = stream(out, ")"); | |
443 | else | |
444 | err = stream(out, "%c%s", close, indicator); | |
445 | } | |
446 | return err; | |
447 | } | |
448 | ||
449 | case CborTagType: { | |
450 | CborTag tag; | |
451 | cbor_value_get_tag(it, &tag); /* can't fail */ | |
452 | err = stream(out, "%" PRIu64 "%s(", tag, get_indicator(it, flags)); | |
453 | if (!err) | |
454 | err = cbor_value_advance_fixed(it); | |
455 | if (!err && recursionsLeft) | |
456 | err = value_to_pretty(stream, out, it, flags, recursionsLeft - 1); | |
457 | else if (!err) | |
458 | printRecursionLimit(stream, out); | |
459 | if (!err) | |
460 | err = stream(out, ")"); | |
461 | return err; | |
462 | } | |
463 | ||
464 | case CborSimpleType: { | |
465 | /* simple types can't fail and can't have overlong encoding */ | |
466 | uint8_t simple_type; | |
467 | cbor_value_get_simple_type(it, &simple_type); | |
468 | err = stream(out, "simple(%" PRIu8 ")", simple_type); | |
469 | break; | |
470 | } | |
471 | ||
472 | case CborNullType: | |
473 | err = stream(out, "null"); | |
474 | break; | |
475 | ||
476 | case CborUndefinedType: | |
477 | err = stream(out, "undefined"); | |
478 | break; | |
479 | ||
480 | case CborBooleanType: { | |
481 | bool val; | |
482 | cbor_value_get_boolean(it, &val); /* can't fail */ | |
483 | err = stream(out, val ? "true" : "false"); | |
484 | break; | |
485 | } | |
486 | ||
487 | #ifndef CBOR_NO_FLOATING_POINT | |
488 | case CborDoubleType: { | |
489 | const char *suffix; | |
490 | double val; | |
491 | int r; | |
492 | uint64_t ival; | |
493 | ||
494 | if (false) { | |
495 | float f; | |
496 | case CborFloatType: | |
497 | cbor_value_get_float(it, &f); | |
498 | val = f; | |
499 | suffix = flags & CborPrettyNumericEncodingIndicators ? "_2" : "f"; | |
500 | } else if (false) { | |
501 | uint16_t f16; | |
502 | case CborHalfFloatType: | |
503 | #ifndef CBOR_NO_HALF_FLOAT_TYPE | |
504 | cbor_value_get_half_float(it, &f16); | |
505 | val = decode_half(f16); | |
506 | suffix = flags & CborPrettyNumericEncodingIndicators ? "_1" : "f16"; | |
507 | #else | |
508 | (void)f16; | |
509 | err = CborErrorUnsupportedType; | |
510 | break; | |
511 | #endif | |
512 | } else { | |
513 | cbor_value_get_double(it, &val); | |
514 | suffix = ""; | |
515 | } | |
516 | ||
517 | if ((flags & CborPrettyNumericEncodingIndicators) == 0) { | |
518 | r = fpclassify(val); | |
519 | if (r == FP_NAN || r == FP_INFINITE) | |
520 | suffix = ""; | |
521 | } | |
522 | ||
523 | if (convertToUint64(val, &ival)) { | |
524 | /* this double value fits in a 64-bit integer, so show it as such | |
525 | * (followed by a floating point suffix, to disambiguate) */ | |
526 | err = stream(out, "%s%" PRIu64 ".%s", val < 0 ? "-" : "", ival, suffix); | |
527 | } else { | |
528 | /* this number is definitely not a 64-bit integer */ | |
529 | err = stream(out, "%." DBL_DECIMAL_DIG_STR "g%s", val, suffix); | |
530 | } | |
531 | break; | |
532 | } | |
533 | #else | |
534 | case CborDoubleType: | |
535 | case CborFloatType: | |
536 | case CborHalfFloatType: | |
537 | err = CborErrorUnsupportedType; | |
538 | break; | |
539 | #endif /* !CBOR_NO_FLOATING_POINT */ | |
540 | ||
541 | case CborInvalidType: | |
542 | err = stream(out, "invalid"); | |
543 | if (err) | |
544 | return err; | |
545 | return CborErrorUnknownType; | |
546 | } | |
547 | ||
548 | if (!err) | |
549 | err = cbor_value_advance_fixed(it); | |
550 | return err; | |
551 | } | |
552 | ||
553 | /** | |
554 | * Converts the current CBOR type pointed by \a value to its textual | |
555 | * representation and writes it to the stream by calling the \a streamFunction. | |
556 | * If an error occurs, this function returns an error code similar to | |
557 | * \ref CborParsing. | |
558 | * | |
559 | * The textual representation can be controlled by the \a flags parameter (see | |
560 | * \ref CborPrettyFlags for more information). | |
561 | * | |
562 | * If no error ocurred, this function advances \a value to the next element. | |
563 | * Often, concatenating the text representation of multiple elements can be | |
564 | * done by appending a comma to the output stream in between calls to this | |
565 | * function. | |
566 | * | |
567 | * The \a streamFunction function will be called with the \a token value as the | |
568 | * first parameter and a printf-style format string as the second, with a variable | |
569 | * number of further parameters. | |
570 | * | |
571 | * \sa cbor_value_to_pretty(), cbor_value_to_json_advance() | |
572 | */ | |
573 | CborError cbor_value_to_pretty_stream(CborStreamFunction streamFunction, void *token, CborValue *value, int flags) | |
574 | { | |
575 | return value_to_pretty(streamFunction, token, value, flags, CBOR_PARSER_MAX_RECURSIONS); | |
576 | } | |
577 | ||
578 | /** @} */ |