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