2021-05-11 03:50:41 +00:00
|
|
|
/* wscbor.c
|
|
|
|
* Wireshark CBOR item decoding API.
|
|
|
|
* References:
|
|
|
|
* RFC 8949: https://tools.ietf.org/html/rfc8949
|
|
|
|
*
|
|
|
|
* Copyright 2019-2021, Brian Sipos <brian.sipos@gmail.com>
|
|
|
|
*
|
|
|
|
* Wireshark - Network traffic analyzer
|
|
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
|
|
* Copyright 1998 Gerald Combs
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
*/
|
2021-11-12 17:23:04 +00:00
|
|
|
#include "config.h"
|
2021-05-11 03:50:41 +00:00
|
|
|
|
|
|
|
#include <epan/packet.h>
|
|
|
|
#include <epan/exceptions.h>
|
|
|
|
#include <epan/expert.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include "wscbor.h"
|
|
|
|
|
|
|
|
/// Pseudo-protocol to register expert info
|
|
|
|
static int proto_wscbor = -1;
|
|
|
|
|
|
|
|
static expert_field ei_cbor_invalid = EI_INIT;
|
|
|
|
static expert_field ei_cbor_overflow = EI_INIT;
|
|
|
|
static expert_field ei_cbor_wrong_type = EI_INIT;
|
|
|
|
static expert_field ei_cbor_array_wrong_size = EI_INIT;
|
2021-10-04 02:28:32 +00:00
|
|
|
static expert_field ei_cbor_indef_string = EI_INIT;
|
2021-05-11 03:50:41 +00:00
|
|
|
static ei_register_info expertitems[] = {
|
|
|
|
{&ei_cbor_invalid, {"_ws.wscbor.cbor_invalid", PI_MALFORMED, PI_ERROR, "CBOR cannot be decoded", EXPFILL}},
|
|
|
|
{&ei_cbor_overflow, {"_ws.wscbor.cbor_overflow", PI_UNDECODED, PI_ERROR, "CBOR overflow of Wireshark value", EXPFILL}},
|
|
|
|
{&ei_cbor_wrong_type, {"_ws.wscbor.cbor_wrong_type", PI_MALFORMED, PI_ERROR, "CBOR is wrong type", EXPFILL}},
|
|
|
|
{&ei_cbor_array_wrong_size, {"_ws.wscbor.array_wrong_size", PI_MALFORMED, PI_WARN, "CBOR array is the wrong size", EXPFILL}},
|
2021-10-04 02:28:32 +00:00
|
|
|
{&ei_cbor_indef_string, {"_ws.wscbor.indef_string", PI_COMMENTS_GROUP, PI_COMMENT, "String uses indefinite-length encoding", EXPFILL}},
|
2021-05-11 03:50:41 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/// The basic header structure of CBOR encoding
|
|
|
|
typedef struct {
|
|
|
|
/// The start offset of this header
|
|
|
|
gint start;
|
|
|
|
/// The length of just this header
|
|
|
|
gint length;
|
|
|
|
/// The expert info object (if error)
|
|
|
|
expert_field *error;
|
|
|
|
|
|
|
|
/// Major type of this item (cbor_type)
|
|
|
|
guint8 type_major;
|
|
|
|
/// Minor type of this item
|
|
|
|
guint8 type_minor;
|
|
|
|
/// Raw head "value" which may be from the @c type_minor
|
|
|
|
guint64 rawvalue;
|
|
|
|
} wscbor_head_t;
|
|
|
|
|
|
|
|
/** Read the raw value from a CBOR head.
|
|
|
|
* @param[in,out] head The head to read into.
|
|
|
|
* @param tvb The buffer to read from.
|
|
|
|
*/
|
|
|
|
static void wscbor_read_unsigned(wscbor_head_t *head, tvbuff_t *tvb) {
|
|
|
|
switch (head->type_minor) {
|
|
|
|
case 0x18:
|
|
|
|
head->rawvalue = tvb_get_guint8(tvb, head->start + head->length);
|
|
|
|
head->length += 1;
|
|
|
|
break;
|
|
|
|
case 0x19:
|
|
|
|
head->rawvalue = tvb_get_guint16(tvb, head->start + head->length, ENC_BIG_ENDIAN);
|
|
|
|
head->length += 2;
|
|
|
|
break;
|
|
|
|
case 0x1A:
|
|
|
|
head->rawvalue = tvb_get_guint32(tvb, head->start + head->length, ENC_BIG_ENDIAN);
|
|
|
|
head->length += 4;
|
|
|
|
break;
|
|
|
|
case 0x1B:
|
|
|
|
head->rawvalue = tvb_get_guint64(tvb, head->start + head->length, ENC_BIG_ENDIAN);
|
|
|
|
head->length += 8;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (head->type_minor <= 0x17) {
|
|
|
|
head->rawvalue = head->type_minor;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Read just the CBOR head octet.
|
2021-10-04 02:28:32 +00:00
|
|
|
* @param alloc The allocator to use.
|
|
|
|
* @param tvb The TVB to read from.
|
|
|
|
* @param[in,out] offset The offset with in @c tvb.
|
|
|
|
* This is updated with just the head length.
|
|
|
|
* @return The new head object.
|
|
|
|
* This never returns NULL.
|
2021-05-11 03:50:41 +00:00
|
|
|
* @post Will throw wireshark exception if read fails.
|
|
|
|
*/
|
2021-10-04 02:28:32 +00:00
|
|
|
static wscbor_head_t * wscbor_head_read(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset) {
|
2021-05-11 03:50:41 +00:00
|
|
|
wscbor_head_t *head = wmem_new0(alloc, wscbor_head_t);
|
|
|
|
|
2021-10-04 02:28:32 +00:00
|
|
|
head->start = *offset;
|
2021-05-11 03:50:41 +00:00
|
|
|
const guint8 first = tvb_get_guint8(tvb, head->start);
|
|
|
|
head->length += 1;
|
|
|
|
|
|
|
|
// Match libcbor enums
|
|
|
|
head->type_major = (first & 0xe0) >> 5;
|
|
|
|
head->type_minor = (first & 0x1f);
|
|
|
|
switch ((cbor_type)(head->type_major)) {
|
|
|
|
case CBOR_TYPE_UINT:
|
|
|
|
case CBOR_TYPE_NEGINT:
|
|
|
|
case CBOR_TYPE_TAG:
|
|
|
|
wscbor_read_unsigned(head, tvb);
|
|
|
|
if (head->type_minor > 0x1B) {
|
|
|
|
head->error = &ei_cbor_invalid;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case CBOR_TYPE_BYTESTRING:
|
|
|
|
case CBOR_TYPE_STRING:
|
|
|
|
case CBOR_TYPE_ARRAY:
|
|
|
|
case CBOR_TYPE_MAP:
|
|
|
|
case CBOR_TYPE_FLOAT_CTRL:
|
|
|
|
wscbor_read_unsigned(head, tvb);
|
|
|
|
if ((head->type_minor > 0x1B) && (head->type_minor < 0x1F)) {
|
|
|
|
head->error = &ei_cbor_invalid;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
head->error = &ei_cbor_invalid;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-10-04 02:28:32 +00:00
|
|
|
*offset += head->length;
|
2021-05-11 03:50:41 +00:00
|
|
|
return head;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Force a head to be freed.
|
|
|
|
*/
|
|
|
|
static void wscbor_head_free(wmem_allocator_t *alloc, wscbor_head_t *head) {
|
|
|
|
wmem_free(alloc, head);
|
|
|
|
}
|
|
|
|
|
2021-10-04 02:28:32 +00:00
|
|
|
struct _wscbor_chunk_priv_t {
|
|
|
|
/// The allocator used for wscbor_chunk_t.errors and wscbor_chunk_t.tags
|
|
|
|
wmem_allocator_t *alloc;
|
2021-10-25 04:31:30 +00:00
|
|
|
/// Non-error expert info on this chunk (type wscbor_error_t*)
|
|
|
|
wmem_list_t *infos;
|
2021-10-04 02:28:32 +00:00
|
|
|
/// For string types, including indefinite length, the item payload.
|
|
|
|
/// Otherwise NULL.
|
|
|
|
tvbuff_t *str_value;
|
|
|
|
};
|
|
|
|
|
2021-05-11 03:50:41 +00:00
|
|
|
/** Get a clamped string length suitable for tvb functions.
|
2021-10-04 02:28:32 +00:00
|
|
|
* @param[in,out] chunk The chunk to set errors on.
|
|
|
|
* @param head_value The value to clamp.
|
2021-05-11 03:50:41 +00:00
|
|
|
* @return The clamped length value.
|
|
|
|
*/
|
2021-10-04 02:28:32 +00:00
|
|
|
static gint wscbor_get_length(wscbor_chunk_t *chunk, guint64 head_value) {
|
2021-05-11 03:50:41 +00:00
|
|
|
gint length;
|
2021-10-04 02:28:32 +00:00
|
|
|
if (head_value > G_MAXINT) {
|
2021-05-11 03:50:41 +00:00
|
|
|
wmem_list_append(chunk->errors, wscbor_error_new(
|
2021-10-04 02:28:32 +00:00
|
|
|
chunk->_priv->alloc, &ei_cbor_overflow,
|
2021-05-11 03:50:41 +00:00
|
|
|
NULL
|
|
|
|
));
|
|
|
|
length = G_MAXINT;
|
|
|
|
}
|
|
|
|
else {
|
2021-10-04 02:28:32 +00:00
|
|
|
length = (gint) head_value;
|
2021-05-11 03:50:41 +00:00
|
|
|
}
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
|
|
|
wscbor_error_t * wscbor_error_new(wmem_allocator_t *alloc, expert_field *ei, const char *format, ...) {
|
|
|
|
wscbor_error_t *err = wmem_new0(alloc, wscbor_error_t);
|
|
|
|
err->ei = ei;
|
|
|
|
if (format) {
|
|
|
|
wmem_strbuf_t *buf = wmem_strbuf_new(alloc, "");
|
|
|
|
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
|
|
|
wmem_strbuf_append_vprintf(buf, format, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
err->msg = wmem_strbuf_finalize(buf);
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
wscbor_chunk_t * wscbor_chunk_read(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset) {
|
|
|
|
DISSECTOR_ASSERT(alloc != NULL);
|
|
|
|
DISSECTOR_ASSERT(offset != NULL);
|
|
|
|
DISSECTOR_ASSERT(tvb != NULL);
|
|
|
|
|
|
|
|
wscbor_chunk_t *chunk = wmem_new0(alloc, wscbor_chunk_t);
|
2021-10-04 02:28:32 +00:00
|
|
|
chunk->_priv = wmem_new0(alloc, struct _wscbor_chunk_priv_t);
|
|
|
|
chunk->_priv->alloc = alloc;
|
2021-10-25 04:31:30 +00:00
|
|
|
chunk->_priv->infos = wmem_list_new(alloc);
|
2021-05-11 03:50:41 +00:00
|
|
|
chunk->errors = wmem_list_new(alloc);
|
|
|
|
chunk->tags = wmem_list_new(alloc);
|
|
|
|
chunk->start = *offset;
|
|
|
|
|
|
|
|
// Read a sequence of tags followed by an item header
|
|
|
|
while (TRUE) {
|
|
|
|
// This will break out of the loop if it runs out of buffer
|
2021-10-04 02:28:32 +00:00
|
|
|
wscbor_head_t *head = wscbor_head_read(alloc, tvb, offset);
|
2021-05-11 03:50:41 +00:00
|
|
|
chunk->head_length += head->length;
|
|
|
|
if (head->error) {
|
|
|
|
wmem_list_append(chunk->errors, wscbor_error_new(alloc, head->error, NULL));
|
|
|
|
}
|
|
|
|
if (head->type_major == CBOR_TYPE_TAG) {
|
|
|
|
wscbor_tag_t *tag = wmem_new(alloc, wscbor_tag_t);
|
|
|
|
tag->start = head->start;
|
|
|
|
tag->length = head->length;
|
|
|
|
tag->value = head->rawvalue;
|
|
|
|
wmem_list_append(chunk->tags, tag);
|
|
|
|
// same chunk, next part
|
|
|
|
wscbor_head_free(alloc, head);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// An actual (non-tag) header
|
|
|
|
chunk->type_major = (cbor_type)head->type_major;
|
|
|
|
chunk->type_minor = head->type_minor;
|
|
|
|
chunk->head_value = head->rawvalue;
|
|
|
|
|
|
|
|
wscbor_head_free(alloc, head);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-10-04 02:28:32 +00:00
|
|
|
// Data beyond the tags and item head
|
|
|
|
chunk->data_length = chunk->head_length;
|
|
|
|
switch (chunk->type_major) {
|
|
|
|
case CBOR_TYPE_BYTESTRING:
|
|
|
|
case CBOR_TYPE_STRING:
|
|
|
|
if (chunk->type_minor != 31) {
|
|
|
|
const gint datalen = wscbor_get_length(chunk, chunk->head_value);
|
|
|
|
// skip over definite data
|
|
|
|
*offset += datalen;
|
|
|
|
chunk->data_length += datalen;
|
2022-08-23 21:51:39 +00:00
|
|
|
// allow even zero-length strings
|
|
|
|
chunk->_priv->str_value = tvb_new_subset_length(tvb, chunk->start + chunk->head_length, datalen);
|
2021-10-04 02:28:32 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// indefinite length, sequence of definite items
|
|
|
|
chunk->_priv->str_value = tvb_new_composite();
|
|
|
|
|
|
|
|
while (TRUE) {
|
|
|
|
wscbor_head_t *head = wscbor_head_read(alloc, tvb, offset);
|
|
|
|
chunk->data_length += head->length;
|
|
|
|
if (head->error) {
|
|
|
|
wmem_list_append(chunk->errors, wscbor_error_new(alloc, head->error, NULL));
|
|
|
|
}
|
|
|
|
const gboolean is_break = (
|
|
|
|
(head->type_major == CBOR_TYPE_FLOAT_CTRL)
|
|
|
|
&& (head->type_minor == 31)
|
|
|
|
);
|
|
|
|
if (!is_break) {
|
|
|
|
if (head->type_major != chunk->type_major) {
|
|
|
|
wmem_list_append(chunk->errors, wscbor_error_new(
|
|
|
|
chunk->_priv->alloc, &ei_cbor_wrong_type,
|
|
|
|
"Indefinite sub-string item has major type %d, should be %d",
|
|
|
|
head->type_major, chunk->type_major
|
|
|
|
));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
const gint datalen = wscbor_get_length(chunk, head->rawvalue);
|
|
|
|
*offset += datalen;
|
|
|
|
chunk->data_length += datalen;
|
2022-02-07 08:20:53 +00:00
|
|
|
if(datalen) {
|
|
|
|
tvb_composite_append(
|
|
|
|
chunk->_priv->str_value,
|
|
|
|
tvb_new_subset_length(tvb, head->start + head->length, datalen)
|
|
|
|
);
|
|
|
|
}
|
2021-10-04 02:28:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wscbor_head_free(alloc, head);
|
|
|
|
if (is_break) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-25 04:31:30 +00:00
|
|
|
wmem_list_append(chunk->_priv->infos, wscbor_error_new(
|
2021-10-04 02:28:32 +00:00
|
|
|
chunk->_priv->alloc, &ei_cbor_indef_string,
|
|
|
|
NULL
|
|
|
|
));
|
|
|
|
tvb_composite_finalize(chunk->_priv->str_value);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-05-11 03:50:41 +00:00
|
|
|
return chunk;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void wscbor_subitem_free(gpointer data, gpointer userdata) {
|
|
|
|
wmem_allocator_t *alloc = (wmem_allocator_t *) userdata;
|
|
|
|
wmem_free(alloc, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
void wscbor_chunk_free(wscbor_chunk_t *chunk) {
|
|
|
|
DISSECTOR_ASSERT(chunk);
|
2021-10-04 02:28:32 +00:00
|
|
|
wmem_allocator_t *alloc = chunk->_priv->alloc;
|
2021-10-25 04:31:30 +00:00
|
|
|
wmem_list_foreach(chunk->_priv->infos, wscbor_subitem_free, alloc);
|
|
|
|
wmem_destroy_list(chunk->_priv->infos);
|
2021-05-11 03:50:41 +00:00
|
|
|
wmem_list_foreach(chunk->errors, wscbor_subitem_free, alloc);
|
|
|
|
wmem_destroy_list(chunk->errors);
|
|
|
|
wmem_list_foreach(chunk->tags, wscbor_subitem_free, alloc);
|
|
|
|
wmem_destroy_list(chunk->tags);
|
|
|
|
wmem_free(alloc, chunk);
|
|
|
|
}
|
|
|
|
|
2021-10-25 04:31:30 +00:00
|
|
|
/// User data for wscbor_expert_add()
|
|
|
|
typedef struct {
|
|
|
|
packet_info *pinfo;
|
|
|
|
proto_item *item;
|
|
|
|
} wscbor_expert_add_t;
|
|
|
|
|
|
|
|
/// A callback for wmem_list_foreach() to add the info
|
|
|
|
static void wscbor_expert_add(gpointer data, gpointer userdata) {
|
|
|
|
const wscbor_error_t *err = (const wscbor_error_t *)data;
|
|
|
|
wscbor_expert_add_t *ctx = (wscbor_expert_add_t *)userdata;
|
|
|
|
|
|
|
|
if (err->msg) {
|
|
|
|
expert_add_info_format(ctx->pinfo, ctx->item, err->ei, "%s", err->msg);
|
2021-05-11 03:50:41 +00:00
|
|
|
}
|
2021-10-25 04:31:30 +00:00
|
|
|
else {
|
|
|
|
expert_add_info(ctx->pinfo, ctx->item, err->ei);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
guint64 wscbor_chunk_mark_errors(packet_info *pinfo, proto_item *item, const wscbor_chunk_t *chunk) {
|
|
|
|
wscbor_expert_add_t ctx = {pinfo, item};
|
|
|
|
wmem_list_foreach(chunk->errors, wscbor_expert_add, &ctx);
|
|
|
|
wmem_list_foreach(chunk->_priv->infos, wscbor_expert_add, &ctx);
|
|
|
|
|
2021-05-11 03:50:41 +00:00
|
|
|
return wmem_list_count(chunk->errors);
|
|
|
|
}
|
|
|
|
|
|
|
|
guint wscbor_has_errors(const wscbor_chunk_t *chunk) {
|
|
|
|
return wmem_list_count(chunk->errors);
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean wscbor_is_indefinite_break(const wscbor_chunk_t *chunk) {
|
|
|
|
return (
|
|
|
|
(chunk->type_major == CBOR_TYPE_FLOAT_CTRL)
|
|
|
|
&& (chunk->type_minor == 31)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-23 21:51:39 +00:00
|
|
|
/** Add output parameter to indicate internal state.
|
|
|
|
* @param alloc The allocator to use.
|
|
|
|
* @param tvb The data buffer.
|
|
|
|
* @param[in,out] offset The initial offset to read and skip over.
|
|
|
|
* @param[out] is_break If non-null, set to true only when the item was
|
|
|
|
* an indefinite break.
|
|
|
|
* @return True if the skipped item was fully valid.
|
|
|
|
*/
|
|
|
|
static gboolean wscbor_skip_next_item_internal(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset, gboolean *is_break) {
|
2021-05-11 03:50:41 +00:00
|
|
|
wscbor_chunk_t *chunk = wscbor_chunk_read(alloc, tvb, offset);
|
2022-08-23 21:51:39 +00:00
|
|
|
if (wscbor_has_errors(chunk)) {
|
|
|
|
*offset = chunk->start;
|
|
|
|
wscbor_chunk_free(chunk);
|
|
|
|
return FALSE;
|
|
|
|
}
|
2021-05-11 03:50:41 +00:00
|
|
|
switch (chunk->type_major) {
|
|
|
|
case CBOR_TYPE_UINT:
|
|
|
|
case CBOR_TYPE_NEGINT:
|
|
|
|
case CBOR_TYPE_TAG:
|
|
|
|
case CBOR_TYPE_FLOAT_CTRL:
|
|
|
|
break;
|
|
|
|
case CBOR_TYPE_BYTESTRING:
|
|
|
|
case CBOR_TYPE_STRING:
|
2021-10-04 02:28:32 +00:00
|
|
|
// wscbor_read_chunk() sets offset past string value
|
2021-05-11 03:50:41 +00:00
|
|
|
break;
|
|
|
|
case CBOR_TYPE_ARRAY: {
|
|
|
|
if (chunk->type_minor == 31) {
|
|
|
|
// wait for indefinite break
|
2022-08-23 21:51:39 +00:00
|
|
|
gboolean was_break = FALSE;
|
|
|
|
do {
|
|
|
|
if (!wscbor_skip_next_item_internal(alloc, tvb, offset, &was_break)) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (!was_break);
|
2021-05-11 03:50:41 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
const guint64 count = chunk->head_value;
|
|
|
|
for (guint64 ix = 0; ix < count; ++ix) {
|
2022-08-23 21:51:39 +00:00
|
|
|
if (!wscbor_skip_next_item_internal(alloc, tvb, offset, NULL)) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
2021-05-11 03:50:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case CBOR_TYPE_MAP: {
|
|
|
|
if (chunk->type_minor == 31) {
|
|
|
|
// wait for indefinite break
|
2022-08-23 21:51:39 +00:00
|
|
|
gboolean was_break = FALSE;
|
|
|
|
do {
|
|
|
|
if (!wscbor_skip_next_item_internal(alloc, tvb, offset, &was_break)) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (!was_break);
|
2021-05-11 03:50:41 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
const guint64 count = chunk->head_value;
|
|
|
|
for (guint64 ix = 0; ix < count; ++ix) {
|
2022-08-23 21:51:39 +00:00
|
|
|
if (!wscbor_skip_next_item_internal(alloc, tvb, offset, NULL)) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
if (!wscbor_skip_next_item_internal(alloc, tvb, offset, NULL)) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
2021-05-11 03:50:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-08-23 21:51:39 +00:00
|
|
|
const gboolean got_break = wscbor_is_indefinite_break(chunk);
|
|
|
|
if (is_break) {
|
|
|
|
*is_break = got_break;
|
|
|
|
}
|
2021-05-11 03:50:41 +00:00
|
|
|
wscbor_chunk_free(chunk);
|
2022-08-23 21:51:39 +00:00
|
|
|
// RFC 8949 Sec 3.2.1: a break code outside of an indefinite container is
|
|
|
|
// not valid, and is_break is non-null only in indefinite container.
|
|
|
|
return is_break || !got_break;
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean wscbor_skip_next_item(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset) {
|
|
|
|
return wscbor_skip_next_item_internal(alloc, tvb, offset, NULL);
|
2021-05-11 03:50:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
gboolean wscbor_skip_if_errors(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset, const wscbor_chunk_t *chunk) {
|
|
|
|
if (wscbor_has_errors(chunk) == 0) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
*offset = chunk->start;
|
|
|
|
wscbor_skip_next_item(alloc, tvb, offset);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void wscbor_init(void) {
|
|
|
|
proto_wscbor = proto_register_protocol(
|
|
|
|
"CBOR Item Decoder",
|
|
|
|
"CBOR Item Decoder",
|
|
|
|
"_ws.wscbor"
|
|
|
|
);
|
|
|
|
|
|
|
|
expert_module_t *expert_wscbor = expert_register_protocol(proto_wscbor);
|
|
|
|
/* This isn't really a protocol, it's an error indication;
|
|
|
|
disabling them makes no sense. */
|
|
|
|
proto_set_cant_toggle(proto_wscbor);
|
|
|
|
|
|
|
|
expert_register_field_array(expert_wscbor, expertitems, array_length(expertitems));
|
|
|
|
}
|
|
|
|
|
2021-10-04 02:28:32 +00:00
|
|
|
const ei_register_info * wscbor_expert_items(int *size) {
|
|
|
|
if (size) {
|
|
|
|
*size = array_length(expertitems);
|
|
|
|
}
|
|
|
|
return expertitems;
|
|
|
|
}
|
|
|
|
|
2021-05-11 03:50:41 +00:00
|
|
|
gboolean wscbor_require_major_type(wscbor_chunk_t *chunk, cbor_type major) {
|
|
|
|
if (chunk->type_major == major) {
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
wmem_list_append(chunk->errors, wscbor_error_new(
|
2021-10-04 02:28:32 +00:00
|
|
|
chunk->_priv->alloc, &ei_cbor_wrong_type,
|
2021-05-11 03:50:41 +00:00
|
|
|
"Item has major type %d, should be %d",
|
|
|
|
chunk->type_major, major
|
|
|
|
));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean wscbor_require_array(wscbor_chunk_t *chunk) {
|
|
|
|
return wscbor_require_major_type(chunk, CBOR_TYPE_ARRAY);
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean wscbor_require_array_size(wscbor_chunk_t *chunk, guint64 count_min, guint64 count_max) {
|
|
|
|
if (!wscbor_require_array(chunk)) {
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
if ((chunk->head_value < count_min) || (chunk->head_value > count_max)) {
|
|
|
|
wmem_list_append(chunk->errors, wscbor_error_new(
|
2021-10-04 02:28:32 +00:00
|
|
|
chunk->_priv->alloc, &ei_cbor_array_wrong_size,
|
2021-05-11 03:50:41 +00:00
|
|
|
"Array has %" PRId64 " items, should be within [%"PRId64", %"PRId64"]",
|
|
|
|
chunk->head_value, count_min, count_max
|
|
|
|
));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean wscbor_require_map(wscbor_chunk_t *chunk) {
|
|
|
|
return wscbor_require_major_type(chunk, CBOR_TYPE_MAP);
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean * wscbor_require_boolean(wmem_allocator_t *alloc, wscbor_chunk_t *chunk) {
|
|
|
|
if (!wscbor_require_major_type(chunk, CBOR_TYPE_FLOAT_CTRL)) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (chunk->type_minor) {
|
|
|
|
case CBOR_CTRL_TRUE:
|
|
|
|
case CBOR_CTRL_FALSE: {
|
|
|
|
gboolean *value = NULL;
|
|
|
|
value = wmem_new(alloc, gboolean);
|
|
|
|
*value = (chunk->type_minor == CBOR_CTRL_TRUE);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
wmem_list_append(chunk->errors, wscbor_error_new(
|
2021-10-04 02:28:32 +00:00
|
|
|
chunk->_priv->alloc, &ei_cbor_wrong_type,
|
2021-05-11 03:50:41 +00:00
|
|
|
"Item has minor type %d, should be %d or %d",
|
|
|
|
chunk->type_minor, CBOR_CTRL_TRUE, CBOR_CTRL_FALSE
|
|
|
|
));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
guint64 * wscbor_require_uint64(wmem_allocator_t *alloc, wscbor_chunk_t *chunk) {
|
|
|
|
if (!wscbor_require_major_type(chunk, CBOR_TYPE_UINT)) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
guint64 *result = wmem_new(alloc, guint64);
|
|
|
|
*result = chunk->head_value;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
gint64 * wscbor_require_int64(wmem_allocator_t *alloc, wscbor_chunk_t *chunk) {
|
|
|
|
gint64 *result = NULL;
|
|
|
|
switch (chunk->type_major) {
|
|
|
|
case CBOR_TYPE_UINT:
|
|
|
|
case CBOR_TYPE_NEGINT: {
|
|
|
|
gint64 clamped;
|
|
|
|
if (chunk->head_value > INT64_MAX) {
|
|
|
|
clamped = INT64_MAX;
|
|
|
|
wmem_list_append(chunk->errors, wscbor_error_new(
|
2021-10-04 02:28:32 +00:00
|
|
|
chunk->_priv->alloc, &ei_cbor_overflow,
|
2021-05-11 03:50:41 +00:00
|
|
|
NULL
|
|
|
|
));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
clamped = chunk->head_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
result = wmem_new(alloc, gint64);
|
|
|
|
if (chunk->type_major == CBOR_TYPE_NEGINT) {
|
|
|
|
*result = -clamped - 1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
*result = clamped;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
wmem_list_append(chunk->errors, wscbor_error_new(
|
2021-10-04 02:28:32 +00:00
|
|
|
chunk->_priv->alloc, &ei_cbor_wrong_type,
|
2021-05-11 03:50:41 +00:00
|
|
|
"Item has major type %d, should be %d or %d",
|
|
|
|
chunk->type_major, CBOR_TYPE_UINT, CBOR_TYPE_NEGINT
|
|
|
|
));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-10-04 02:28:32 +00:00
|
|
|
char * wscbor_require_tstr(wmem_allocator_t *alloc, wscbor_chunk_t *chunk) {
|
2021-05-11 03:50:41 +00:00
|
|
|
if (!wscbor_require_major_type(chunk, CBOR_TYPE_STRING)) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2021-10-04 02:28:32 +00:00
|
|
|
return (char *)tvb_get_string_enc(alloc, chunk->_priv->str_value, 0, tvb_reported_length(chunk->_priv->str_value), ENC_UTF_8);
|
2021-05-11 03:50:41 +00:00
|
|
|
}
|
|
|
|
|
2021-10-04 02:28:32 +00:00
|
|
|
tvbuff_t * wscbor_require_bstr(wmem_allocator_t *alloc _U_, wscbor_chunk_t *chunk) {
|
2021-05-11 03:50:41 +00:00
|
|
|
if (!wscbor_require_major_type(chunk, CBOR_TYPE_BYTESTRING)) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2021-10-04 02:28:32 +00:00
|
|
|
return chunk->_priv->str_value;
|
2021-05-11 03:50:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
proto_item * proto_tree_add_cbor_container(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk) {
|
|
|
|
const header_field_info *hfinfo = proto_registrar_get_nth(hfindex);
|
|
|
|
proto_item *item;
|
|
|
|
if (IS_FT_UINT(hfinfo->type)) {
|
|
|
|
item = proto_tree_add_uint64(tree, hfindex, tvb, chunk->start, chunk->head_length, chunk->head_value);
|
|
|
|
}
|
|
|
|
else if (IS_FT_INT(hfinfo->type)) {
|
|
|
|
item = proto_tree_add_int64(tree, hfindex, tvb, chunk->start, chunk->head_length, chunk->head_value);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
item = proto_tree_add_item(tree, hfindex, tvb, chunk->start, -1, 0);
|
|
|
|
}
|
|
|
|
wscbor_chunk_mark_errors(pinfo, item, chunk);
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
|
|
|
proto_item * proto_tree_add_cbor_ctrl(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk) {
|
|
|
|
proto_item *item = proto_tree_add_item(tree, hfindex, tvb, chunk->start, chunk->head_length, 0);
|
|
|
|
wscbor_chunk_mark_errors(pinfo, item, chunk);
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
proto_item *item = proto_tree_add_boolean(tree, hfindex, tvb, chunk->start, chunk->data_length, value ? *value : FALSE);
|
|
|
|
wscbor_chunk_mark_errors(pinfo, item, chunk);
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
proto_item *item = proto_tree_add_uint64(tree, hfindex, tvb, chunk->start, chunk->head_length, value ? *value : 0);
|
|
|
|
wscbor_chunk_mark_errors(pinfo, item, chunk);
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
proto_item *item = proto_tree_add_int64(tree, hfindex, tvb, chunk->start, chunk->head_length, value ? *value : 0);
|
|
|
|
wscbor_chunk_mark_errors(pinfo, item, chunk);
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
header_field_info *field = proto_registrar_get_nth(hfindex);
|
|
|
|
gint flagsize = 0;
|
|
|
|
switch (field->type) {
|
|
|
|
case FT_UINT8:
|
|
|
|
flagsize = 1;
|
|
|
|
break;
|
|
|
|
case FT_UINT16:
|
|
|
|
flagsize = 2;
|
|
|
|
break;
|
|
|
|
case FT_UINT32:
|
|
|
|
flagsize = 4;
|
|
|
|
break;
|
|
|
|
case FT_UINT64:
|
|
|
|
flagsize = 8;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "Unhandled bitmask size: %d", field->type);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fake TVB data for these functions
|
2021-09-29 15:56:02 +00:00
|
|
|
guint8 *flags = (guint8 *) wmem_alloc0(pinfo->pool, flagsize);
|
2021-05-11 03:50:41 +00:00
|
|
|
{ // Inject big-endian value directly
|
|
|
|
guint64 buf = (value ? *value : 0);
|
|
|
|
for (gint ix = flagsize - 1; ix >= 0; --ix) {
|
|
|
|
flags[ix] = buf & 0xFF;
|
|
|
|
buf >>= 8;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tvbuff_t *tvb_flags = tvb_new_child_real_data(tvb, flags, flagsize, flagsize);
|
|
|
|
|
|
|
|
proto_item *item = proto_tree_add_bitmask_value(tree, tvb_flags, 0, hfindex, ett, fields, value ? *value : 0);
|
|
|
|
wscbor_chunk_mark_errors(pinfo, item, chunk);
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
2022-08-23 21:51:39 +00:00
|
|
|
proto_item * proto_tree_add_cbor_tstr(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk) {
|
|
|
|
proto_item *item;
|
2022-02-07 08:20:53 +00:00
|
|
|
if (chunk->_priv->str_value) {
|
2022-08-23 21:51:39 +00:00
|
|
|
item = proto_tree_add_item(tree, hfindex, chunk->_priv->str_value, 0, tvb_reported_length(chunk->_priv->str_value), 0);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// still show an empty item with errors
|
|
|
|
item = proto_tree_add_item(tree, hfindex, tvb, chunk->start, 0, 0);
|
2022-02-07 08:20:53 +00:00
|
|
|
}
|
2022-08-23 21:51:39 +00:00
|
|
|
wscbor_chunk_mark_errors(pinfo, item, chunk);
|
|
|
|
return item;
|
2021-05-11 03:50:41 +00:00
|
|
|
}
|
|
|
|
|
2022-08-23 21:51:39 +00:00
|
|
|
proto_item * proto_tree_add_cbor_bstr(proto_tree *tree, int hfindex, packet_info *pinfo, tvbuff_t *tvb, const wscbor_chunk_t *chunk) {
|
|
|
|
proto_item *item;
|
2022-02-07 08:20:53 +00:00
|
|
|
if (chunk->_priv->str_value) {
|
2022-08-23 21:51:39 +00:00
|
|
|
item = proto_tree_add_item(tree, hfindex, chunk->_priv->str_value, 0, tvb_reported_length(chunk->_priv->str_value), 0);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// still show an empty item with errors
|
|
|
|
item = proto_tree_add_item(tree, hfindex, tvb, chunk->start, 0, 0);
|
2022-02-07 08:20:53 +00:00
|
|
|
}
|
2022-08-23 21:51:39 +00:00
|
|
|
wscbor_chunk_mark_errors(pinfo, item, chunk);
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
|
|
|
proto_item * proto_tree_add_cbor_strlen(proto_tree *tree, int hfindex, packet_info *pinfo _U_, tvbuff_t *tvb, const wscbor_chunk_t *chunk) {
|
|
|
|
const guint str_len = (chunk->_priv->str_value ? tvb_reported_length(chunk->_priv->str_value) : 0);
|
|
|
|
proto_item *item = proto_tree_add_uint64(tree, hfindex, tvb, chunk->start, chunk->head_length, str_len);
|
|
|
|
return item;
|
2021-05-11 03:50:41 +00:00
|
|
|
}
|