aboutsummaryrefslogtreecommitdiffstats
path: root/epan/wscbor.c
diff options
context:
space:
mode:
authorBrian Sipos <brian.sipos@gmail.com>2021-05-10 23:50:41 -0400
committerWireshark GitLab Utility <gerald+gitlab-utility@wireshark.org>2021-09-29 08:51:13 +0000
commitabd0f1183f4dcc8d2fe6b3fb6750328fc3f6036c (patch)
tree56f52b25d9002149663a30312c7a74f0a1ab0c54 /epan/wscbor.c
parent4010502a8251ac21e829ecab8ccc1ae2a2b96219 (diff)
COSE dissector from dtn-wireshark project
Diffstat (limited to 'epan/wscbor.c')
-rw-r--r--epan/wscbor.c546
1 files changed, 546 insertions, 0 deletions
diff --git a/epan/wscbor.c b/epan/wscbor.c
new file mode 100644
index 0000000000..e303071e36
--- /dev/null
+++ b/epan/wscbor.c
@@ -0,0 +1,546 @@
+/* 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
+ */
+
+#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;
+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}},
+};
+
+/// 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.
+ * @post Will throw wireshark exception if read fails.
+ */
+static wscbor_head_t * wscbor_head_read(wmem_allocator_t *alloc, tvbuff_t *tvb, gint start) {
+ wscbor_head_t *head = wmem_new0(alloc, wscbor_head_t);
+
+ head->start = start;
+ 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;
+ }
+
+ 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);
+}
+
+/** Get a clamped string length suitable for tvb functions.
+ * @param[in,out] chunk The chunk to read and set errors on.
+ * @return The clamped length value.
+ */
+static gint wscbor_get_length(const wscbor_chunk_t *chunk) {
+ gint length;
+ if (chunk->head_value > G_MAXINT) {
+ wmem_list_append(chunk->errors, wscbor_error_new(
+ chunk->_alloc, &ei_cbor_overflow,
+ NULL
+ ));
+ length = G_MAXINT;
+ }
+ else {
+ length = (gint) chunk->head_value;
+ }
+ 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);
+ chunk->_alloc = alloc;
+ 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
+ wscbor_head_t *head = wscbor_head_read(alloc, tvb, *offset);
+ *offset += head->length;
+ 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;
+
+ chunk->data_length = chunk->head_length;
+ switch ((cbor_type)(head->type_major)) {
+ case CBOR_TYPE_BYTESTRING:
+ case CBOR_TYPE_STRING:
+ if (chunk->type_minor != 31) {
+ const gint datalen = wscbor_get_length(chunk);
+ // skip over definite data
+ *offset += datalen;
+ chunk->data_length += datalen;
+ }
+ break;
+ default:
+ break;
+ }
+
+ wscbor_head_free(alloc, head);
+ break;
+ }
+
+ 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);
+ wmem_allocator_t *alloc = chunk->_alloc;
+ 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);
+}
+
+guint64 wscbor_chunk_mark_errors(packet_info *pinfo, proto_item *item, const wscbor_chunk_t *chunk) {
+ for (wmem_list_frame_t *it = wmem_list_head(chunk->errors); it;
+ it = wmem_list_frame_next(it)) {
+ wscbor_error_t *err = (wscbor_error_t *) wmem_list_frame_data(it);
+ if (err->msg) {
+ expert_add_info_format(pinfo, item, err->ei, "%s", err->msg);
+ }
+ else {
+ expert_add_info(pinfo, item, err->ei);
+ }
+ }
+ 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)
+ );
+}
+
+gboolean wscbor_skip_next_item(wmem_allocator_t *alloc, tvbuff_t *tvb, gint *offset) {
+ wscbor_chunk_t *chunk = wscbor_chunk_read(alloc, tvb, offset);
+ 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:
+ if (chunk->type_minor == 31) {
+ // wait for indefinite break
+ while (!wscbor_skip_next_item(alloc, tvb, offset)) {}
+ }
+ // wscbor_read_chunk() sets offset past definite value
+ break;
+ case CBOR_TYPE_ARRAY: {
+ if (chunk->type_minor == 31) {
+ // wait for indefinite break
+ while (!wscbor_skip_next_item(alloc, tvb, offset)) {}
+ }
+ else {
+ const guint64 count = chunk->head_value;
+ for (guint64 ix = 0; ix < count; ++ix) {
+ wscbor_skip_next_item(alloc, tvb, offset);
+ }
+ }
+ break;
+ }
+ case CBOR_TYPE_MAP: {
+ if (chunk->type_minor == 31) {
+ // wait for indefinite break
+ while (!wscbor_skip_next_item(alloc, tvb, offset)) {}
+ }
+ else {
+ const guint64 count = chunk->head_value;
+ for (guint64 ix = 0; ix < count; ++ix) {
+ wscbor_skip_next_item(alloc, tvb, offset);
+ wscbor_skip_next_item(alloc, tvb, offset);
+ }
+ }
+ break;
+ }
+ }
+ const gboolean is_break = wscbor_is_indefinite_break(chunk);
+ wscbor_chunk_free(chunk);
+ return is_break;
+}
+
+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));
+}
+
+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(
+ chunk->_alloc, &ei_cbor_wrong_type,
+ "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(
+ chunk->_alloc, &ei_cbor_array_wrong_size,
+ "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(
+ chunk->_alloc, &ei_cbor_wrong_type,
+ "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(
+ chunk->_alloc, &ei_cbor_overflow,
+ 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(
+ chunk->_alloc, &ei_cbor_wrong_type,
+ "Item has major type %d, should be %d or %d",
+ chunk->type_major, CBOR_TYPE_UINT, CBOR_TYPE_NEGINT
+ ));
+ break;
+ }
+ return result;
+}
+
+char * wscbor_require_tstr(wmem_allocator_t *alloc, tvbuff_t *parent, wscbor_chunk_t *chunk) {
+ if (!wscbor_require_major_type(chunk, CBOR_TYPE_STRING)) {
+ return NULL;
+ }
+
+ return (char *)tvb_get_string_enc(alloc, parent, chunk->start + chunk->head_length, wscbor_get_length(chunk), ENC_UTF_8);
+}
+
+tvbuff_t * wscbor_require_bstr(tvbuff_t *parent, wscbor_chunk_t *chunk) {
+ if (!wscbor_require_major_type(chunk, CBOR_TYPE_BYTESTRING)) {
+ return NULL;
+ }
+
+ return tvb_new_subset_length(parent, chunk->start + chunk->head_length, wscbor_get_length(chunk));
+}
+
+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
+ guint8 *flags = (guint8 *) wmem_alloc0(wmem_packet_scope(), flagsize);
+ { // 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;
+}
+
+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 = proto_tree_add_item(tree, hfindex, tvb, chunk->start + chunk->head_length, wscbor_get_length(chunk), 0);
+ wscbor_chunk_mark_errors(pinfo, item, chunk);
+ return item;
+}
+
+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 = proto_tree_add_item(tree, hfindex, tvb, chunk->start + chunk->head_length, wscbor_get_length(chunk), 0);
+ wscbor_chunk_mark_errors(pinfo, item, chunk);
+ return item;
+}