1 /* wscbor.c
2  * Wireshark CBOR item decoding API.
3  * References:
4  *     RFC 8949: https://tools.ietf.org/html/rfc8949
5  *
6  * Copyright 2019-2021, Brian Sipos <brian.sipos@gmail.com>
7  *
8  * Wireshark - Network traffic analyzer
9  * By Gerald Combs <gerald@wireshark.org>
10  * Copyright 1998 Gerald Combs
11  *
12  * SPDX-License-Identifier: LGPL-2.1-or-later
13  */
14 #include "config.h"
15 
16 #include <epan/packet.h>
17 #include <epan/exceptions.h>
18 #include <epan/expert.h>
19 #include <stdio.h>
20 #include <inttypes.h>
21 #include "wscbor.h"
22 
23 /// Pseudo-protocol to register expert info
24 static int proto_wscbor = -1;
25 
26 static expert_field ei_cbor_invalid = EI_INIT;
27 static expert_field ei_cbor_overflow = EI_INIT;
28 static expert_field ei_cbor_wrong_type = EI_INIT;
29 static expert_field ei_cbor_array_wrong_size = EI_INIT;
30 static expert_field ei_cbor_indef_string = EI_INIT;
31 static ei_register_info expertitems[] = {
32     {&ei_cbor_invalid, {"_ws.wscbor.cbor_invalid", PI_MALFORMED, PI_ERROR, "CBOR cannot be decoded", EXPFILL}},
33     {&ei_cbor_overflow, {"_ws.wscbor.cbor_overflow", PI_UNDECODED, PI_ERROR, "CBOR overflow of Wireshark value", EXPFILL}},
34     {&ei_cbor_wrong_type, {"_ws.wscbor.cbor_wrong_type", PI_MALFORMED, PI_ERROR, "CBOR is wrong type", EXPFILL}},
35     {&ei_cbor_array_wrong_size, {"_ws.wscbor.array_wrong_size", PI_MALFORMED, PI_WARN, "CBOR array is the wrong size", EXPFILL}},
36     {&ei_cbor_indef_string, {"_ws.wscbor.indef_string", PI_COMMENTS_GROUP, PI_COMMENT, "String uses indefinite-length encoding", EXPFILL}},
37 };
38 
39 /// The basic header structure of CBOR encoding
40 typedef struct {
41     /// The start offset of this header
42     gint start;
43     /// The length of just this header
44     gint length;
45     /// The expert info object (if error)
46     expert_field *error;
47 
48     /// Major type of this item (cbor_type)
49     guint8 type_major;
50     /// Minor type of this item
51     guint8 type_minor;
52     /// Raw head "value" which may be from the @c type_minor
53     guint64 rawvalue;
54 } wscbor_head_t;
55 
56 /** Read the raw value from a CBOR head.
57  * @param[in,out] head The head to read into.
58  * @param tvb The buffer to read from.
59  */
60 static void wscbor_read_unsigned(wscbor_head_t *head, tvbuff_t *tvb) {
61     switch (head->type_minor) {
62         case 0x18:
63             head->rawvalue = tvb_get_guint8(tvb, head->start + head->length);
64             head->length += 1;
65             break;
66         case 0x19:
67             head->rawvalue = tvb_get_guint16(tvb, head->start + head->length, ENC_BIG_ENDIAN);
68             head->length += 2;
69             break;
70         case 0x1A:
71             head->rawvalue = tvb_get_guint32(tvb, head->start + head->length, ENC_BIG_ENDIAN);
72             head->length += 4;
73             break;
74         case 0x1B:
75             head->rawvalue = tvb_get_guint64(tvb, head->start + head->length, ENC_BIG_ENDIAN);
76             head->length += 8;
77             break;
78         default:
79             if (head->type_minor <= 0x17) {
80                 head->rawvalue = head->type_minor;
81             }
82             break;
83     }
84 }
85 
86 /** Read just the CBOR head octet.
87  * @param alloc The allocator to use.
88  * @param tvb The TVB to read from.
89  * @param[in,out] offset The offset with in @c tvb.
90  * This is updated with just the head length.
91  * @return The new head object.
92  * This never returns NULL.
93  * @post Will throw wireshark exception if read fails.
94  */
95 static wscbor_head_t * wscbor_head_read(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset) {
96     wscbor_head_t *head = wmem_new0(alloc, wscbor_head_t);
97 
98     head->start = *offset;
99     const guint8 first = tvb_get_guint8(tvb, head->start);
100     head->length += 1;
101 
102     // Match libcbor enums
103     head->type_major = (first & 0xe0) >> 5;
104     head->type_minor = (first & 0x1f);
105     switch ((cbor_type)(head->type_major)) {
106         case CBOR_TYPE_UINT:
107         case CBOR_TYPE_NEGINT:
108         case CBOR_TYPE_TAG:
109             wscbor_read_unsigned(head, tvb);
110             if (head->type_minor > 0x1B) {
111                 head->error = &ei_cbor_invalid;
112             }
113             break;
114         case CBOR_TYPE_BYTESTRING:
115         case CBOR_TYPE_STRING:
116         case CBOR_TYPE_ARRAY:
117         case CBOR_TYPE_MAP:
118         case CBOR_TYPE_FLOAT_CTRL:
119             wscbor_read_unsigned(head, tvb);
120             if ((head->type_minor > 0x1B) && (head->type_minor < 0x1F)) {
121                 head->error = &ei_cbor_invalid;
122             }
123             break;
124 
125         default:
126             head->error = &ei_cbor_invalid;
127             break;
128     }
129 
130     *offset += head->length;
131     return head;
132 }
133 
134 /** Force a head to be freed.
135  */
136 static void wscbor_head_free(wmem_allocator_t *alloc, wscbor_head_t *head) {
137     wmem_free(alloc, head);
138 }
139 
140 struct _wscbor_chunk_priv_t {
141     /// The allocator used for wscbor_chunk_t.errors and wscbor_chunk_t.tags
142     wmem_allocator_t *alloc;
143     /// For string types, including indefinite length, the item payload.
144     /// Otherwise NULL.
145     tvbuff_t *str_value;
146 
147 };
148 
149 /** Get a clamped string length suitable for tvb functions.
150  * @param[in,out] chunk The chunk to set errors on.
151  * @param head_value The value to clamp.
152  * @return The clamped length value.
153  */
154 static gint wscbor_get_length(wscbor_chunk_t *chunk, guint64 head_value) {
155     gint length;
156     if (head_value > G_MAXINT) {
157         wmem_list_append(chunk->errors, wscbor_error_new(
158                 chunk->_priv->alloc, &ei_cbor_overflow,
159                 NULL
160         ));
161         length = G_MAXINT;
162     }
163     else {
164         length = (gint) head_value;
165     }
166     return length;
167 }
168 
169 wscbor_error_t * wscbor_error_new(wmem_allocator_t *alloc, expert_field *ei, const char *format, ...) {
170     wscbor_error_t *err = wmem_new0(alloc, wscbor_error_t);
171     err->ei = ei;
172     if (format) {
173         wmem_strbuf_t *buf = wmem_strbuf_new(alloc, "");
174 
175         va_list ap;
176         va_start(ap, format);
177         wmem_strbuf_append_vprintf(buf, format, ap);
178         va_end(ap);
179 
180         err->msg = wmem_strbuf_finalize(buf);
181     }
182     return err;
183 }
184 
185 wscbor_chunk_t * wscbor_chunk_read(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset) {
186     DISSECTOR_ASSERT(alloc != NULL);
187     DISSECTOR_ASSERT(offset != NULL);
188     DISSECTOR_ASSERT(tvb != NULL);
189 
190     wscbor_chunk_t *chunk = wmem_new0(alloc, wscbor_chunk_t);
191     chunk->_priv = wmem_new0(alloc, struct _wscbor_chunk_priv_t);
192     chunk->_priv->alloc = alloc;
193     chunk->errors = wmem_list_new(alloc);
194     chunk->tags = wmem_list_new(alloc);
195     chunk->start = *offset;
196 
197     // Read a sequence of tags followed by an item header
198     while (TRUE) {
199         // This will break out of the loop if it runs out of buffer
200         wscbor_head_t *head = wscbor_head_read(alloc, tvb, offset);
201         chunk->head_length += head->length;
202         if (head->error) {
203             wmem_list_append(chunk->errors, wscbor_error_new(alloc, head->error, NULL));
204         }
205         if (head->type_major == CBOR_TYPE_TAG) {
206             wscbor_tag_t *tag = wmem_new(alloc, wscbor_tag_t);
207             tag->start = head->start;
208             tag->length = head->length;
209             tag->value = head->rawvalue;
210             wmem_list_append(chunk->tags, tag);
211             // same chunk, next part
212             wscbor_head_free(alloc, head);
213             continue;
214         }
215 
216         // An actual (non-tag) header
217         chunk->type_major = (cbor_type)head->type_major;
218         chunk->type_minor = head->type_minor;
219         chunk->head_value = head->rawvalue;
220 
221         wscbor_head_free(alloc, head);
222         break;
223     }
224 
225     // Data beyond the tags and item head
226     chunk->data_length = chunk->head_length;
227     switch (chunk->type_major) {
228         case CBOR_TYPE_BYTESTRING:
229         case CBOR_TYPE_STRING:
230             if (chunk->type_minor != 31) {
231                 const gint datalen = wscbor_get_length(chunk, chunk->head_value);
232                 // skip over definite data
233                 *offset += datalen;
234                 chunk->data_length += datalen;
235                 chunk->_priv->str_value = tvb_new_subset_length(tvb, chunk->start + chunk->head_length, datalen);
236             }
237             else {
238                 // indefinite length, sequence of definite items
239                 chunk->_priv->str_value = tvb_new_composite();
240 
241                 while (TRUE) {
242                     wscbor_head_t *head = wscbor_head_read(alloc, tvb, offset);
243                     chunk->data_length += head->length;
244                     if (head->error) {
245                         wmem_list_append(chunk->errors, wscbor_error_new(alloc, head->error, NULL));
246                     }
247                     const gboolean is_break = (
248                         (head->type_major == CBOR_TYPE_FLOAT_CTRL)
249                         && (head->type_minor == 31)
250                     );
251                     if (!is_break) {
252                         if (head->type_major != chunk->type_major) {
253                             wmem_list_append(chunk->errors, wscbor_error_new(
254                                     chunk->_priv->alloc, &ei_cbor_wrong_type,
255                                     "Indefinite sub-string item has major type %d, should be %d",
256                                     head->type_major, chunk->type_major
257                             ));
258                         }
259                         else {
260                             const gint datalen = wscbor_get_length(chunk, head->rawvalue);
261                             *offset += datalen;
262                             chunk->data_length += datalen;
263                             tvb_composite_append(
264                                 chunk->_priv->str_value,
265                                 tvb_new_subset_length(tvb, head->start + head->length, datalen)
266                             );
267                         }
268                     }
269 
270                     wscbor_head_free(alloc, head);
271                     if (is_break) {
272                         break;
273                     }
274                 }
275 
276                 wmem_list_append(chunk->errors, wscbor_error_new(
277                         chunk->_priv->alloc, &ei_cbor_indef_string,
278                         NULL
279                 ));
280                 tvb_composite_finalize(chunk->_priv->str_value);
281             }
282             break;
283         default:
284             break;
285     }
286 
287     return chunk;
288 }
289 
290 static void wscbor_subitem_free(gpointer data, gpointer userdata) {
291     wmem_allocator_t *alloc = (wmem_allocator_t *) userdata;
292     wmem_free(alloc, data);
293 }
294 
295 void wscbor_chunk_free(wscbor_chunk_t *chunk) {
296     DISSECTOR_ASSERT(chunk);
297     wmem_allocator_t *alloc = chunk->_priv->alloc;
298     wmem_list_foreach(chunk->errors, wscbor_subitem_free, alloc);
299     wmem_destroy_list(chunk->errors);
300     wmem_list_foreach(chunk->tags, wscbor_subitem_free, alloc);
301     wmem_destroy_list(chunk->tags);
302     wmem_free(alloc, chunk);
303 }
304 
305 guint64 wscbor_chunk_mark_errors(packet_info *pinfo, proto_item *item, const wscbor_chunk_t *chunk) {
306     for (wmem_list_frame_t *it = wmem_list_head(chunk->errors); it;
307             it = wmem_list_frame_next(it)) {
308         wscbor_error_t *err = (wscbor_error_t *) wmem_list_frame_data(it);
309         if (err->msg) {
310             expert_add_info_format(pinfo, item, err->ei, "%s", err->msg);
311         }
312         else {
313             expert_add_info(pinfo, item, err->ei);
314         }
315     }
316     return wmem_list_count(chunk->errors);
317 }
318 
319 guint wscbor_has_errors(const wscbor_chunk_t *chunk) {
320     return wmem_list_count(chunk->errors);
321 }
322 
323 gboolean wscbor_is_indefinite_break(const wscbor_chunk_t *chunk) {
324     return (
325         (chunk->type_major == CBOR_TYPE_FLOAT_CTRL)
326         && (chunk->type_minor == 31)
327     );
328 }
329 
330 gboolean wscbor_skip_next_item(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset) {
331     wscbor_chunk_t *chunk = wscbor_chunk_read(alloc, tvb, offset);
332     switch (chunk->type_major) {
333         case CBOR_TYPE_UINT:
334         case CBOR_TYPE_NEGINT:
335         case CBOR_TYPE_TAG:
336         case CBOR_TYPE_FLOAT_CTRL:
337             break;
338         case CBOR_TYPE_BYTESTRING:
339         case CBOR_TYPE_STRING:
340             // wscbor_read_chunk() sets offset past string value
341             break;
342         case CBOR_TYPE_ARRAY: {
343             if (chunk->type_minor == 31) {
344                 // wait for indefinite break
345                 while (!wscbor_skip_next_item(alloc, tvb, offset)) {}
346             }
347             else {
348                 const guint64 count = chunk->head_value;
349                 for (guint64 ix = 0; ix < count; ++ix) {
350                     wscbor_skip_next_item(alloc, tvb, offset);
351                 }
352             }
353             break;
354         }
355         case CBOR_TYPE_MAP: {
356             if (chunk->type_minor == 31) {
357                 // wait for indefinite break
358                 while (!wscbor_skip_next_item(alloc, tvb, offset)) {}
359             }
360             else {
361                 const guint64 count = chunk->head_value;
362                 for (guint64 ix = 0; ix < count; ++ix) {
363                     wscbor_skip_next_item(alloc, tvb, offset);
364                     wscbor_skip_next_item(alloc, tvb, offset);
365                 }
366             }
367             break;
368         }
369     }
370     const gboolean is_break = wscbor_is_indefinite_break(chunk);
371     wscbor_chunk_free(chunk);
372     return is_break;
373 }
374 
375 gboolean wscbor_skip_if_errors(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset, const wscbor_chunk_t *chunk) {
376     if (wscbor_has_errors(chunk) == 0) {
377         return FALSE;
378     }
379 
380     *offset = chunk->start;
381     wscbor_skip_next_item(alloc, tvb, offset);
382     return TRUE;
383 }
384 
385 void wscbor_init(void) {
386     proto_wscbor = proto_register_protocol(
387         "CBOR Item Decoder",
388         "CBOR Item Decoder",
389         "_ws.wscbor"
390     );
391 
392     expert_module_t *expert_wscbor = expert_register_protocol(proto_wscbor);
393     /* This isn't really a protocol, it's an error indication;
394        disabling them makes no sense. */
395     proto_set_cant_toggle(proto_wscbor);
396 
397     expert_register_field_array(expert_wscbor, expertitems, array_length(expertitems));
398 }
399 
400 const ei_register_info * wscbor_expert_items(int *size) {
401     if (size) {
402         *size = array_length(expertitems);
403     }
404     return expertitems;
405 }
406 
407 gboolean wscbor_require_major_type(wscbor_chunk_t *chunk, cbor_type major) {
408     if (chunk->type_major == major) {
409         return TRUE;
410     }
411     wmem_list_append(chunk->errors, wscbor_error_new(
412             chunk->_priv->alloc, &ei_cbor_wrong_type,
413             "Item has major type %d, should be %d",
414             chunk->type_major, major
415     ));
416     return FALSE;
417 }
418 
419 gboolean wscbor_require_array(wscbor_chunk_t *chunk) {
420     return wscbor_require_major_type(chunk, CBOR_TYPE_ARRAY);
421 }
422 
423 gboolean wscbor_require_array_size(wscbor_chunk_t *chunk, guint64 count_min, guint64 count_max) {
424     if (!wscbor_require_array(chunk)) {
425         return FALSE;
426     }
427     if ((chunk->head_value < count_min) || (chunk->head_value > count_max)) {
428         wmem_list_append(chunk->errors, wscbor_error_new(
429                 chunk->_priv->alloc, &ei_cbor_array_wrong_size,
430                 "Array has %" PRId64 " items, should be within [%"PRId64", %"PRId64"]",
431                 chunk->head_value, count_min, count_max
432         ));
433         return FALSE;
434     }
435     return TRUE;
436 }
437 
438 gboolean wscbor_require_map(wscbor_chunk_t *chunk) {
439     return wscbor_require_major_type(chunk, CBOR_TYPE_MAP);
440 }
441 
442 gboolean * wscbor_require_boolean(wmem_allocator_t *alloc, wscbor_chunk_t *chunk) {
443     if (!wscbor_require_major_type(chunk, CBOR_TYPE_FLOAT_CTRL)) {
444         return NULL;
445     }
446 
447     switch (chunk->type_minor) {
448         case CBOR_CTRL_TRUE:
449         case CBOR_CTRL_FALSE: {
450             gboolean *value = NULL;
451             value = wmem_new(alloc, gboolean);
452             *value = (chunk->type_minor == CBOR_CTRL_TRUE);
453             return value;
454         }
455         default:
456             wmem_list_append(chunk->errors, wscbor_error_new(
457                     chunk->_priv->alloc, &ei_cbor_wrong_type,
458                     "Item has minor type %d, should be %d or %d",
459                     chunk->type_minor, CBOR_CTRL_TRUE, CBOR_CTRL_FALSE
460             ));
461             break;
462     }
463     return NULL;
464 }
465 
466 guint64 * wscbor_require_uint64(wmem_allocator_t *alloc, wscbor_chunk_t *chunk) {
467     if (!wscbor_require_major_type(chunk, CBOR_TYPE_UINT)) {
468         return NULL;
469     }
470 
471     guint64 *result = wmem_new(alloc, guint64);
472     *result = chunk->head_value;
473     return result;
474 }
475 
476 gint64 * wscbor_require_int64(wmem_allocator_t *alloc, wscbor_chunk_t *chunk) {
477     gint64 *result = NULL;
478     switch (chunk->type_major) {
479         case CBOR_TYPE_UINT:
480         case CBOR_TYPE_NEGINT: {
481             gint64 clamped;
482             if (chunk->head_value > INT64_MAX) {
483                 clamped = INT64_MAX;
484                 wmem_list_append(chunk->errors, wscbor_error_new(
485                         chunk->_priv->alloc, &ei_cbor_overflow,
486                         NULL
487                 ));
488             }
489             else {
490                 clamped = chunk->head_value;
491             }
492 
493             result = wmem_new(alloc, gint64);
494             if (chunk->type_major == CBOR_TYPE_NEGINT) {
495                 *result = -clamped - 1;
496             }
497             else {
498                 *result = clamped;
499             }
500             break;
501         }
502         default:
503             wmem_list_append(chunk->errors, wscbor_error_new(
504                     chunk->_priv->alloc, &ei_cbor_wrong_type,
505                     "Item has major type %d, should be %d or %d",
506                     chunk->type_major, CBOR_TYPE_UINT, CBOR_TYPE_NEGINT
507             ));
508             break;
509     }
510     return result;
511 }
512 
513 char * wscbor_require_tstr(wmem_allocator_t *alloc, wscbor_chunk_t *chunk) {
514     if (!wscbor_require_major_type(chunk, CBOR_TYPE_STRING)) {
515         return NULL;
516     }
517 
518     return (char *)tvb_get_string_enc(alloc, chunk->_priv->str_value, 0, tvb_reported_length(chunk->_priv->str_value), ENC_UTF_8);
519 }
520 
521 tvbuff_t * wscbor_require_bstr(wmem_allocator_t *alloc _U_, wscbor_chunk_t *chunk) {
522     if (!wscbor_require_major_type(chunk, CBOR_TYPE_BYTESTRING)) {
523         return NULL;
524     }
525 
526     return chunk->_priv->str_value;
527 }
528 
529 proto_item * proto_tree_add_cbor_container(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk) {
530     const header_field_info *hfinfo = proto_registrar_get_nth(hfindex);
531     proto_item *item;
532     if (IS_FT_UINT(hfinfo->type)) {
533         item = proto_tree_add_uint64(tree, hfindex, tvb, chunk->start, chunk->head_length, chunk->head_value);
534     }
535     else if (IS_FT_INT(hfinfo->type)) {
536         item = proto_tree_add_int64(tree, hfindex, tvb, chunk->start, chunk->head_length, chunk->head_value);
537     }
538     else {
539         item = proto_tree_add_item(tree, hfindex, tvb, chunk->start, -1, 0);
540     }
541     wscbor_chunk_mark_errors(pinfo, item, chunk);
542     return item;
543 }
544 
545 proto_item * proto_tree_add_cbor_ctrl(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk) {
546     proto_item *item = proto_tree_add_item(tree, hfindex, tvb, chunk->start, chunk->head_length, 0);
547     wscbor_chunk_mark_errors(pinfo, item, chunk);
548     return item;
549 }
550 
551 proto_item * proto_tree_add_cbor_boolean(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const gboolean *value) {
552     proto_item *item = proto_tree_add_boolean(tree, hfindex, tvb, chunk->start, chunk->data_length, value ? *value : FALSE);
553     wscbor_chunk_mark_errors(pinfo, item, chunk);
554     return item;
555 }
556 
557 proto_item * proto_tree_add_cbor_uint64(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const guint64 *value) {
558     proto_item *item = proto_tree_add_uint64(tree, hfindex, tvb, chunk->start, chunk->head_length, value ? *value : 0);
559     wscbor_chunk_mark_errors(pinfo, item, chunk);
560     return item;
561 }
562 
563 proto_item * proto_tree_add_cbor_int64(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const gint64 *value) {
564     proto_item *item = proto_tree_add_int64(tree, hfindex, tvb, chunk->start, chunk->head_length, value ? *value : 0);
565     wscbor_chunk_mark_errors(pinfo, item, chunk);
566     return item;
567 }
568 
569 proto_item * proto_tree_add_cbor_bitmask(proto_tree *tree, int hfindex, const gint ett, int *const *fields, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk, const guint64 *value) {
570     header_field_info *field = proto_registrar_get_nth(hfindex);
571     gint flagsize = 0;
572     switch (field->type) {
573         case FT_UINT8:
574             flagsize = 1;
575             break;
576         case FT_UINT16:
577             flagsize = 2;
578             break;
579         case FT_UINT32:
580             flagsize = 4;
581             break;
582         case FT_UINT64:
583             flagsize = 8;
584             break;
585         default:
586             fprintf(stderr, "Unhandled bitmask size: %d", field->type);
587             return NULL;
588     }
589 
590     // Fake TVB data for these functions
591     guint8 *flags = (guint8 *) wmem_alloc0(pinfo->pool, flagsize);
592     { // Inject big-endian value directly
593         guint64 buf = (value ? *value : 0);
594         for (gint ix = flagsize - 1; ix >= 0; --ix) {
595             flags[ix] = buf & 0xFF;
596             buf >>= 8;
597         }
598     }
599     tvbuff_t *tvb_flags = tvb_new_child_real_data(tvb, flags, flagsize, flagsize);
600 
601     proto_item *item = proto_tree_add_bitmask_value(tree, tvb_flags, 0, hfindex, ett, fields, value ? *value : 0);
602     wscbor_chunk_mark_errors(pinfo, item, chunk);
603     return item;
604 }
605 
606 proto_item * proto_tree_add_cbor_tstr(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb _U_, const wscbor_chunk_t *chunk) {
607     proto_item *item = proto_tree_add_item(tree, hfindex, chunk->_priv->str_value, 0, tvb_reported_length(chunk->_priv->str_value), 0);
608     wscbor_chunk_mark_errors(pinfo, item, chunk);
609     return item;
610 }
611 
612 proto_item * proto_tree_add_cbor_bstr(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb _U_, const wscbor_chunk_t *chunk) {
613     proto_item *item = proto_tree_add_item(tree, hfindex, chunk->_priv->str_value, 0, tvb_reported_length(chunk->_priv->str_value), 0);
614     wscbor_chunk_mark_errors(pinfo, item, chunk);
615     return item;
616 }
617