/* packet-lwm2mtlv.c * Routines for LWM2M TLV dissection * References: * OMA LWM2M Specification: OMA-TS-LightweightM2M_Core-V1_1-20180710-A.pdf * available from * http://openmobilealliance.org/release/LightweightM2M/V1_1-20180710-A/ * * Copyright 2016, Christoph Burger-Scheidlin * Copyright 2018, Stig Bjorlykke * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" #include #include #include #include #include #include "packet-gsm_a_common.h" #include "packet-http.h" void proto_register_lwm2mtlv(void); void proto_reg_handoff_lwm2mtlv(void); static int proto_lwm2mtlv = -1; static int hf_lwm2mtlv_object_name = -1; static int hf_lwm2mtlv_resource_name = -1; static int hf_lwm2mtlv_header = -1; static int hf_lwm2mtlv_type_type = -1; static int hf_lwm2mtlv_type_length_of_identifier = -1; static int hf_lwm2mtlv_type_length_of_length = -1; static int hf_lwm2mtlv_type_length = -1; static int hf_lwm2mtlv_type_ignored = -1; static int hf_lwm2mtlv_identifier = -1; static int hf_lwm2mtlv_length = -1; static int hf_lwm2mtlv_value = -1; static int hf_lwm2mtlv_value_string = -1; static int hf_lwm2mtlv_value_integer = -1; static int hf_lwm2mtlv_value_unsigned_integer = -1; static int hf_lwm2mtlv_value_float = -1; static int hf_lwm2mtlv_value_double = -1; static int hf_lwm2mtlv_value_boolean = -1; static int hf_lwm2mtlv_value_timestamp = -1; static int hf_lwm2mtlv_object_instance = -1; static int hf_lwm2mtlv_resource_instance = -1; static int hf_lwm2mtlv_resource_array = -1; static int hf_lwm2mtlv_resource = -1; static gint ett_lwm2mtlv = -1; static gint ett_lwm2mtlv_header = -1; static gint ett_lwm2mtlv_resource = -1; static gint ett_lwm2mtlv_resource_instance = -1; static gint ett_lwm2mtlv_resource_array = -1; static gint ett_lwm2mtlv_object_instance = -1; static gint ett_lwm2mtlv_location_velocity = -1; typedef enum { OBJECT_INSTANCE = 0, RESOURCE_INSTANCE = 1, RESOURCE_ARRAY = 2, RESOURCE = 3 } lwm2m_identifier_t; static const value_string identifiers[] = { { OBJECT_INSTANCE, "Object Instance" }, { RESOURCE_INSTANCE, "Resource Instance" }, { RESOURCE_ARRAY, "Multiple Resources" }, { RESOURCE, "Resource with value" }, { 0, NULL } }; static const value_string length_identifier[] = { { 0x00, "1 byte identifier" }, { 0x01, "2 bytes identifier" }, { 0, NULL } }; static const value_string length_type[] = { { 0x00, "No length field" }, { 0x01, "1 byte length field" }, { 0x02, "2 bytes length field" }, { 0x03, "3 bytes length field" }, { 0, NULL } }; typedef struct { guint type; guint length_of_identifier; guint length_of_length; guint length_of_value; guint identifier; guint length; guint totalLength; } lwm2mElement_t; typedef struct _lwm2m_object_name_t { guint object_id; char *name; } lwm2m_object_name_t; typedef struct _lwm2m_resource_t { guint object_id; guint resource_id; char *name; guint data_type; gint *hf_id; gint ett_id; char *field_name; } lwm2m_resource_t; static void parseArrayOfElements(packet_info *pinfo, tvbuff_t *tvb, proto_tree *tlv_tree, gint object_id, gint resource_id); /* RESOURCE_FILL initializes all the dynamic fields in a lwm2m_resource_t. */ #define RESOURCE_FILL NULL, -1, NULL #define DATA_TYPE_NONE 0 #define DATA_TYPE_STRING 1 #define DATA_TYPE_INTEGER 2 #define DATA_TYPE_UNSIGNED_INTEGER 3 #define DATA_TYPE_FLOAT 4 #define DATA_TYPE_BOOLEAN 5 #define DATA_TYPE_OPAQUE 6 #define DATA_TYPE_TIME 7 #define DATA_TYPE_OBJLNK 8 #define DATA_TYPE_CORELNK 9 static const value_string data_types[] = { { DATA_TYPE_NONE, "None" }, { DATA_TYPE_STRING, "String" }, { DATA_TYPE_INTEGER, "Integer" }, { DATA_TYPE_UNSIGNED_INTEGER, "Unsigned Integer" }, { DATA_TYPE_FLOAT, "Float" }, { DATA_TYPE_BOOLEAN, "Boolean" }, { DATA_TYPE_OPAQUE, "Opaque" }, { DATA_TYPE_TIME, "Time" }, { DATA_TYPE_OBJLNK, "Objlnk" }, { DATA_TYPE_CORELNK, "Corelnk" }, { 0, NULL } }; /* LwM2M Objects defined by OMA (Normative) */ static const value_string lwm2m_oma_objects[] = { { 0, "LwM2M Security" }, { 1, "LwM2M Server" }, { 2, "Access Control" }, { 3, "Device" }, { 4, "Connectivity Monitoring" }, { 5, "Firmware Update" }, { 6, "Location" }, { 7, "Connectivity Statistics" }, { 21, "OSCORE" }, { 0, NULL } }; static lwm2m_resource_t lwm2m_oma_resources[] = { /* LwM2M Security (0) */ { 0, 0, "LwM2M Server URI", DATA_TYPE_STRING, RESOURCE_FILL }, { 0, 1, "Bootstrap-Server", DATA_TYPE_BOOLEAN, RESOURCE_FILL }, { 0, 2, "Security Mode", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 0, 3, "Public Key or Identity", DATA_TYPE_OPAQUE, RESOURCE_FILL }, { 0, 4, "Server Public Key", DATA_TYPE_OPAQUE, RESOURCE_FILL }, { 0, 5, "Secret Key", DATA_TYPE_OPAQUE, RESOURCE_FILL }, { 0, 6, "SMS Security Mode", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 0, 7, "SMS Binding Key Parameters", DATA_TYPE_OPAQUE, RESOURCE_FILL }, { 0, 8, "SMS Binding Secret Keys", DATA_TYPE_OPAQUE, RESOURCE_FILL }, { 0, 9, "LwM2M Server SMS Number", DATA_TYPE_STRING, RESOURCE_FILL }, { 0, 10, "Short Server ID", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 0, 11, "Client Hold Off Time", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 0, 12, "Bootstrap-Server Account Timeout", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 0, 13, "Matching Type", DATA_TYPE_UNSIGNED_INTEGER, RESOURCE_FILL }, { 0, 14, "SNI", DATA_TYPE_STRING, RESOURCE_FILL }, { 0, 15, "Certificate Usage", DATA_TYPE_UNSIGNED_INTEGER, RESOURCE_FILL }, { 0, 16, "TLS DTLS Ciphersuite", DATA_TYPE_UNSIGNED_INTEGER, RESOURCE_FILL }, { 0, 17, "OSCORE Security Mode", DATA_TYPE_OBJLNK, RESOURCE_FILL }, /* LwM2M Server (1) */ { 1, 0, "Short Server ID", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 1, 1, "Lifetime", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 1, 2, "Default Minimum Period", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 1, 3, "Default Maximum Period", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 1, 4, "Disable", DATA_TYPE_NONE, RESOURCE_FILL }, { 1, 5, "Disable Timeout", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 1, 6, "Notification Storing When Disabled or Offline", DATA_TYPE_BOOLEAN, RESOURCE_FILL }, { 1, 7, "Binding", DATA_TYPE_STRING, RESOURCE_FILL }, { 1, 8, "Registration Update Trigger", DATA_TYPE_NONE, RESOURCE_FILL }, { 1, 9, "Bootstrap Request Trigger", DATA_TYPE_NONE, RESOURCE_FILL }, { 1, 10, "APN Link", DATA_TYPE_OBJLNK, RESOURCE_FILL }, { 1, 11, "TLS DTLS Alert Code", DATA_TYPE_UNSIGNED_INTEGER, RESOURCE_FILL }, { 1, 12, "Last Bootstrapped", DATA_TYPE_TIME, RESOURCE_FILL }, { 1, 13, "Registration Priority Order", DATA_TYPE_UNSIGNED_INTEGER, RESOURCE_FILL }, { 1, 14, "Initial Registration Delay Timer", DATA_TYPE_UNSIGNED_INTEGER, RESOURCE_FILL }, { 1, 15, "Registration Failure Block", DATA_TYPE_BOOLEAN, RESOURCE_FILL }, { 1, 16, "Bootstrap on Registration Failure", DATA_TYPE_BOOLEAN, RESOURCE_FILL }, { 1, 17, "Communication Retry Count", DATA_TYPE_UNSIGNED_INTEGER, RESOURCE_FILL }, { 1, 18, "Communication Retry Timer", DATA_TYPE_UNSIGNED_INTEGER, RESOURCE_FILL }, { 1, 19, "Communication Sequence Delay Timer", DATA_TYPE_UNSIGNED_INTEGER, RESOURCE_FILL }, { 1, 20, "Communication Sequence Retry Count", DATA_TYPE_UNSIGNED_INTEGER, RESOURCE_FILL }, { 1, 21, "Trigger", DATA_TYPE_BOOLEAN, RESOURCE_FILL }, { 1, 22, "Preferred Transport", DATA_TYPE_STRING, RESOURCE_FILL }, { 1, 23, "Mute Send", DATA_TYPE_BOOLEAN, RESOURCE_FILL }, /* Access Control (2) */ { 2, 0, "Object ID", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 2, 1, "Object Instance ID", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 2, 2, "ACL", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 2, 3, "Access Control Owner", DATA_TYPE_INTEGER, RESOURCE_FILL }, /* Device (3) */ { 3, 0, "Manufacturer", DATA_TYPE_STRING, RESOURCE_FILL }, { 3, 1, "Model Number", DATA_TYPE_STRING, RESOURCE_FILL }, { 3, 2, "Serial Number", DATA_TYPE_STRING, RESOURCE_FILL }, { 3, 3, "Firmware Version", DATA_TYPE_STRING, RESOURCE_FILL }, { 3, 4, "Reboot", DATA_TYPE_NONE, RESOURCE_FILL }, { 3, 5, "Factory Reset", DATA_TYPE_NONE, RESOURCE_FILL }, { 3, 6, "Available Power Sources", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 3, 7, "Power Source Voltage", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 3, 8, "Power Source Current", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 3, 9, "Battery Level", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 3, 10, "Memory Free", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 3, 11, "Error Code", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 3, 12, "Reset Error Code", DATA_TYPE_NONE, RESOURCE_FILL }, { 3, 13, "Current Time", DATA_TYPE_TIME, RESOURCE_FILL }, { 3, 14, "UTC Offset", DATA_TYPE_STRING, RESOURCE_FILL }, { 3, 15, "Timezone", DATA_TYPE_STRING, RESOURCE_FILL }, { 3, 16, "Supported Binding and Modes", DATA_TYPE_STRING, RESOURCE_FILL }, { 3, 17, "Device Type", DATA_TYPE_STRING, RESOURCE_FILL }, { 3, 18, "Hardware Version", DATA_TYPE_STRING, RESOURCE_FILL }, { 3, 19, "Software Version", DATA_TYPE_STRING, RESOURCE_FILL }, { 3, 20, "Battery Status", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 3, 21, "Memory Total", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 3, 22, "ExtDevInfo", DATA_TYPE_OBJLNK, RESOURCE_FILL }, /* Connectivity Monitoring (4) */ { 4, 0, "Network Bearer", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 4, 1, "Available Network Bearer", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 4, 2, "Radio Signal Strength", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 4, 3, "Link Quality", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 4, 4, "IP Addresses", DATA_TYPE_STRING, RESOURCE_FILL }, { 4, 5, "Router IP Addresses", DATA_TYPE_STRING, RESOURCE_FILL }, { 4, 6, "Link Utilization", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 4, 7, "APN", DATA_TYPE_STRING, RESOURCE_FILL }, { 4, 8, "Cell ID", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 4, 9, "SMNC", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 4, 10, "SMCC", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 4, 11, "SignalSNR", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 5, 12, "LAC", DATA_TYPE_INTEGER, RESOURCE_FILL }, /* Firmware Update (5) */ { 5, 0, "Package", DATA_TYPE_OPAQUE, RESOURCE_FILL }, { 5, 1, "Package URI", DATA_TYPE_STRING, RESOURCE_FILL }, { 5, 2, "Update", DATA_TYPE_NONE, RESOURCE_FILL }, { 5, 3, "State", DATA_TYPE_INTEGER, RESOURCE_FILL }, /* { 5, 4, "", DATA_TYPE_NONE, RESOURCE_FILL }, */ { 5, 5, "Update Result", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 5, 6, "PkgName", DATA_TYPE_STRING, RESOURCE_FILL }, { 5, 7, "PkgVersion", DATA_TYPE_STRING, RESOURCE_FILL }, { 5, 8, "Firmware Update Protocol Support", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 5, 9, "Firmware Update Delivery Method", DATA_TYPE_INTEGER, RESOURCE_FILL }, /* Location (6) */ { 6, 0, "Latitude", DATA_TYPE_FLOAT, RESOURCE_FILL }, { 6, 1, "Longitude", DATA_TYPE_FLOAT, RESOURCE_FILL }, { 6, 2, "Altitude", DATA_TYPE_FLOAT, RESOURCE_FILL }, { 6, 3, "Radius", DATA_TYPE_FLOAT, RESOURCE_FILL }, { 6, 4, "Velocity", DATA_TYPE_OPAQUE, RESOURCE_FILL }, { 6, 5, "Timestamp", DATA_TYPE_TIME, RESOURCE_FILL }, { 6, 6, "Speed", DATA_TYPE_FLOAT, RESOURCE_FILL }, /* Connectivity Statistics (7) */ { 7, 0, "SMS Tx Counter", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 7, 1, "SMS Rx Counter", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 7, 2, "Tx Data", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 7, 3, "Rx Data", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 7, 4, "Max Message Size", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 7, 5, "Average Message Size", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 7, 6, "Start", DATA_TYPE_NONE, RESOURCE_FILL }, { 7, 7, "Stop", DATA_TYPE_NONE, RESOURCE_FILL }, { 7, 8, "Collection Period", DATA_TYPE_INTEGER, RESOURCE_FILL }, /* OSCORE (21) */ { 21, 0, "Master Secret", DATA_TYPE_STRING, RESOURCE_FILL }, { 21, 1, "Sender ID", DATA_TYPE_STRING, RESOURCE_FILL }, { 21, 2, "Recipient ID", DATA_TYPE_STRING, RESOURCE_FILL }, { 21, 3, "AEAD Algorithm", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 21, 4, "HMAC Algorithm", DATA_TYPE_INTEGER, RESOURCE_FILL }, { 21, 5, "Master Salt", DATA_TYPE_STRING, RESOURCE_FILL }, }; static const guint num_lwm2m_oma_resources = array_length(lwm2m_oma_resources); typedef struct _lwm2m_allocated_fields_t { hf_register_info *hf; guint hf_size; GArray *ett; lwm2m_resource_t *float_resources; guint num_float_resources; } lwm2m_allocated_fields_t; static lwm2m_allocated_fields_t oma_allocated_fields; static lwm2m_allocated_fields_t uat_allocated_fields; /* LwM2M Objects defined by User */ static lwm2m_object_name_t *lwm2m_uat_object_names; static guint num_lwm2m_uat_object_names; static lwm2m_resource_t *lwm2m_uat_resources; static guint num_lwm2m_uat_resources; static gboolean lwm2m_object_name_update_cb(void *record, char **error) { lwm2m_object_name_t *rec = (lwm2m_object_name_t *)record; if (rec->name == NULL) { *error = g_strdup("Object Name can't be empty"); return FALSE; } g_strstrip(rec->name); if (rec->name[0] == 0) { *error = g_strdup("Object Name can't be empty"); return FALSE; } *error = NULL; return TRUE; } static void *lwm2m_object_name_copy_cb(void *dest, const void *source, size_t len _U_) { const lwm2m_object_name_t *s = (const lwm2m_object_name_t *)source; lwm2m_object_name_t *d = (lwm2m_object_name_t *)dest; d->object_id = s->object_id; d->name = g_strdup(s->name); return d; } static void lwm2m_object_name_free_cb(void *record) { lwm2m_object_name_t *rec = (lwm2m_object_name_t *)record; g_free(rec->name); } UAT_DEC_CB_DEF(object_name, object_id, lwm2m_object_name_t) UAT_CSTRING_CB_DEF(object_name, name, lwm2m_object_name_t) static gboolean lwm2m_resource_update_cb(void *record, char **error) { lwm2m_resource_t *rec = (lwm2m_resource_t *)record; char c; if (rec->name == NULL) { *error = g_strdup("Resource Name can't be empty"); return FALSE; } g_strstrip(rec->name); if (rec->name[0] == 0) { *error = g_strdup("Resource Name can't be empty"); return FALSE; } g_free(rec->field_name); rec->field_name = g_ascii_strdown(rec->name, -1); for (size_t i = 0; i < strlen(rec->field_name); i++) { if (rec->field_name[i] == ' ' || rec->field_name[i] == '.') { rec->field_name[i] = '_'; } } /* Check for invalid characters (to avoid asserting out when registering the field). */ c = proto_check_field_name(rec->field_name); if (c) { *error = ws_strdup_printf("Resource Name can't contain '%c'", c); return FALSE; } *error = NULL; return TRUE; } static void *lwm2m_resource_copy_cb(void *dest, const void *source, size_t len _U_) { const lwm2m_resource_t *s = (const lwm2m_resource_t *)source; lwm2m_resource_t *d = (lwm2m_resource_t *)dest; d->object_id = s->object_id; d->resource_id = s->resource_id; d->name = g_strdup(s->name); d->field_name = g_strdup(s->field_name); d->data_type = s->data_type; return d; } static void lwm2m_resource_free_cb(void *record) { lwm2m_resource_t *rec = (lwm2m_resource_t *)record; g_free(rec->name); g_free(rec->field_name); } static void lwm2m_add_resource(lwm2m_resource_t *resource, hf_register_info *hf, gboolean float_as_double) { gchar *resource_abbrev; gint *hf_id; hf_id = g_new(gint,1); *hf_id = -1; if (resource->field_name) { resource_abbrev = g_strdup(resource->field_name); } else { resource_abbrev = g_ascii_strdown(resource->name, -1); for (size_t i = 0; i < strlen(resource_abbrev); i++) { if (resource_abbrev[i] == ' ' || resource_abbrev[i] == '.') { resource_abbrev[i] = '_'; } } } resource->hf_id = hf_id; resource->ett_id = -1; hf->p_id = hf_id; hf->hfinfo.name = g_strdup(resource->name); hf->hfinfo.abbrev = ws_strdup_printf("lwm2mtlv.resource.%s", resource_abbrev); g_free (resource_abbrev); switch (resource->data_type) { case DATA_TYPE_STRING: case DATA_TYPE_CORELNK: hf->hfinfo.display = BASE_NONE; hf->hfinfo.type = FT_STRING; break; case DATA_TYPE_INTEGER: hf->hfinfo.display = BASE_DEC; hf->hfinfo.type = FT_INT64; break; case DATA_TYPE_UNSIGNED_INTEGER: hf->hfinfo.display = BASE_DEC; hf->hfinfo.type = FT_UINT64; break; case DATA_TYPE_FLOAT: hf->hfinfo.display = BASE_NONE; hf->hfinfo.type = (float_as_double ? FT_DOUBLE : FT_FLOAT); break; case DATA_TYPE_BOOLEAN: hf->hfinfo.display = BASE_DEC; hf->hfinfo.type = FT_BOOLEAN; break; case DATA_TYPE_TIME: hf->hfinfo.display = ABSOLUTE_TIME_LOCAL; hf->hfinfo.type = FT_ABSOLUTE_TIME; break; case DATA_TYPE_OPAQUE: case DATA_TYPE_OBJLNK: default: hf->hfinfo.display = BASE_NONE; hf->hfinfo.type = FT_BYTES; break; } hf->hfinfo.strings = NULL; hf->hfinfo.bitmask = 0; hf->hfinfo.blurb = NULL; HFILL_INIT(*hf); } static void lwm2m_allocate_fields(lwm2m_allocated_fields_t *fields, lwm2m_resource_t *lwm2m_resources, guint num_lwm2m_resources) { guint resource_index; fields->num_float_resources = 0; for (guint i = 0; i < num_lwm2m_resources; i++) { if (lwm2m_resources[i].data_type == DATA_TYPE_FLOAT) { fields->num_float_resources++; } } fields->hf_size = num_lwm2m_resources + fields->num_float_resources; fields->hf = g_new0(hf_register_info, fields->hf_size); fields->ett = g_array_new(TRUE, TRUE, sizeof(gint*)); fields->float_resources = g_new0(lwm2m_resource_t, fields->num_float_resources); resource_index = 0; for (guint i = 0; i < num_lwm2m_resources; i++) { gint *ettp = &(lwm2m_resources[i].ett_id); lwm2m_add_resource(&lwm2m_resources[i], &fields->hf[i], FALSE); g_array_append_val(fields->ett, ettp); /* 8 bytes Float is handled as Double, allocate a separate resource for FT_DOUBLE */ if (lwm2m_resources[i].data_type == DATA_TYPE_FLOAT) { guint hf_index = num_lwm2m_resources + resource_index; memcpy(&fields->float_resources[resource_index], &lwm2m_resources[i], sizeof(lwm2m_resource_t)); lwm2m_add_resource(&fields->float_resources[resource_index++], &fields->hf[hf_index], TRUE); } } proto_register_field_array(proto_lwm2mtlv, fields->hf, fields->hf_size); proto_register_subtree_array((gint**)(void*)fields->ett->data, fields->ett->len); resource_index = 0; for (guint i = 0; i < num_lwm2m_resources; i++) { /* Reuse the same ETT for Float and Double resources */ if (lwm2m_resources[i].data_type == DATA_TYPE_FLOAT) { fields->float_resources[resource_index++].ett_id = lwm2m_resources[i].ett_id; } } } static const lwm2m_resource_t *lwm2m_search_float_resources(guint object_id, guint resource_id, const lwm2m_allocated_fields_t *fields) { const lwm2m_resource_t *resource = NULL; for (guint i = 0; i < fields->num_float_resources; i++) { if ((object_id == fields->float_resources[i].object_id) && (resource_id == fields->float_resources[i].resource_id)) { resource = &fields->float_resources[i]; break; } } return resource; } static const lwm2m_resource_t *lwm2m_search_fields(guint object_id, guint resource_id, guint length_of_value, const lwm2m_allocated_fields_t *fields, const lwm2m_resource_t *lwm2m_resources, guint num_lwm2m_resources) { const lwm2m_resource_t *resource = NULL; for (guint i = 0; i < num_lwm2m_resources; i++) { if ((object_id == lwm2m_resources[i].object_id) && (resource_id == lwm2m_resources[i].resource_id)) { /* 8 bytes Float is handled as Double, lookup the FT_DOUBLE resource */ if (length_of_value == 8 && lwm2m_resources[i].data_type == DATA_TYPE_FLOAT) { resource = lwm2m_search_float_resources(object_id, resource_id, fields); } else { resource = &lwm2m_resources[i]; } break; } } return resource; } static void lwm2m_free_fields(lwm2m_allocated_fields_t *fields) { if (fields->hf) { /* Deregister all fields */ for (guint i = 0; i < fields->hf_size; i++) { proto_deregister_field(proto_lwm2mtlv, *(fields->hf[i].p_id)); g_free (fields->hf[i].p_id); } proto_add_deregistered_data(fields->hf); fields->hf = NULL; fields->hf_size = 0; } if (fields->ett) { g_array_free(fields->ett, TRUE); fields->ett = NULL; } if (fields->float_resources) { g_free(fields->float_resources); fields->float_resources = NULL; fields->num_float_resources = 0; } } static void lwm2m_resource_post_update_cb(void) { lwm2m_free_fields(&uat_allocated_fields); if (num_lwm2m_uat_resources) { lwm2m_allocate_fields(&uat_allocated_fields, lwm2m_uat_resources, num_lwm2m_uat_resources); } } static void lwm2m_resource_reset_cb(void) { lwm2m_free_fields(&uat_allocated_fields); } static gint64 decodeVariableInt(tvbuff_t *tvb, const gint offset, const guint length) { switch(length) { case 1: return tvb_get_gint8(tvb, offset); case 2: return tvb_get_ntohis(tvb, offset); case 3: return tvb_get_ntohi24(tvb, offset); case 4: return tvb_get_ntohil(tvb, offset); case 5: return tvb_get_ntohi40(tvb, offset); case 6: return tvb_get_ntohi48(tvb, offset); case 7: return tvb_get_ntohi56(tvb, offset); case 8: return tvb_get_ntohi64(tvb, offset); default: return 0; } } UAT_DEC_CB_DEF(resource, object_id, lwm2m_resource_t) UAT_DEC_CB_DEF(resource, resource_id, lwm2m_resource_t) UAT_CSTRING_CB_DEF(resource, name, lwm2m_resource_t) UAT_VS_DEF(resource, data_type, lwm2m_resource_t, guint, DATA_TYPE_NONE, "None") static void addTlvHeaderElements(tvbuff_t *tvb, proto_tree *tlv_tree, lwm2mElement_t *element) { proto_tree_add_item(tlv_tree, hf_lwm2mtlv_type_type, tvb, 0, 1, ENC_BIG_ENDIAN); proto_tree_add_item(tlv_tree, hf_lwm2mtlv_type_length_of_identifier, tvb, 0, 1, ENC_BIG_ENDIAN); proto_tree_add_item(tlv_tree, hf_lwm2mtlv_type_length_of_length, tvb, 0, 1, ENC_BIG_ENDIAN); if ( element->length_of_length == 0 ) { proto_tree_add_item(tlv_tree, hf_lwm2mtlv_type_length, tvb, 0, 1, ENC_BIG_ENDIAN); } else { proto_tree_add_item(tlv_tree, hf_lwm2mtlv_type_ignored, tvb, 0, 1, ENC_BIG_ENDIAN); } proto_tree_add_item(tlv_tree, hf_lwm2mtlv_identifier, tvb, 1, element->length_of_identifier, ENC_BIG_ENDIAN); if ( element->length_of_length > 0 ) { proto_tree_add_item(tlv_tree, hf_lwm2mtlv_length, tvb, 1+element->length_of_identifier, element->length_of_length, ENC_BIG_ENDIAN); } } static void addTlvHeaderTree(tvbuff_t *tvb, proto_tree *tlv_tree, lwm2mElement_t *element) { proto_item *item = NULL; proto_tree *header_tree = NULL; guint valueOffset = 1 + element->length_of_identifier + element->length_of_length; item = proto_tree_add_item(tlv_tree, hf_lwm2mtlv_header, tvb, 0, valueOffset, ENC_NA); header_tree = proto_item_add_subtree(item, ett_lwm2mtlv_header); addTlvHeaderElements(tvb, header_tree, element); } static proto_tree* addElementTree(packet_info *pinfo, tvbuff_t *tvb, proto_tree *tlv_tree, lwm2mElement_t *element, const lwm2m_resource_t *resource) { proto_item *item = NULL; gchar *identifier = NULL; gint ett_id; if (resource) { identifier = wmem_strdup_printf(pinfo->pool, "[%02u] %s", element->identifier, resource->name); } else { identifier = wmem_strdup_printf(pinfo->pool, "[%02u]", element->identifier); } switch ( element->type ) { case OBJECT_INSTANCE: item = proto_tree_add_item(tlv_tree, hf_lwm2mtlv_object_instance, tvb, 0, element->totalLength, ENC_NA); proto_item_append_text(item, " %02u", element->identifier); return proto_item_add_subtree(item, ett_lwm2mtlv_object_instance); case RESOURCE_INSTANCE: item = proto_tree_add_item(tlv_tree, hf_lwm2mtlv_resource_instance, tvb, 0, element->totalLength, ENC_NA); proto_item_set_text(item, "%02u", element->identifier); return proto_item_add_subtree(item, ett_lwm2mtlv_resource_instance); case RESOURCE_ARRAY: ett_id = resource ? resource->ett_id : ett_lwm2mtlv_resource_array; item = proto_tree_add_item(tlv_tree, hf_lwm2mtlv_resource_array, tvb, 0, element->totalLength, ENC_NA); proto_item_set_text(item, "%s", identifier); return proto_item_add_subtree(item, ett_id); case RESOURCE: ett_id = resource ? resource->ett_id : ett_lwm2mtlv_resource; item = proto_tree_add_item(tlv_tree, hf_lwm2mtlv_resource, tvb, 0, element->totalLength, ENC_NA); proto_item_set_text(item, "%s", identifier); return proto_item_add_subtree(item, ett_id); } return NULL; } static void addValueInterpretations(packet_info *pinfo, tvbuff_t *tvb, proto_tree *tlv_tree, lwm2mElement_t *element, const lwm2m_resource_t *resource) { guint valueOffset; if ( element->length_of_value == 0 ) return; valueOffset = 1 + element->length_of_identifier + element->length_of_length; if (resource && resource->data_type != DATA_TYPE_NONE) { switch (resource->data_type) { case DATA_TYPE_STRING: case DATA_TYPE_CORELNK: { const guint8 *strval; proto_tree_add_item_ret_string(tlv_tree, *resource->hf_id, tvb, valueOffset, element->length_of_value, ENC_UTF_8, pinfo->pool, &strval); proto_item_append_text(tlv_tree, ": %s", format_text(pinfo->pool, strval, strlen(strval))); break; } case DATA_TYPE_INTEGER: proto_tree_add_item(tlv_tree, *resource->hf_id, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); proto_item_append_text(tlv_tree, ": %" PRId64, decodeVariableInt(tvb, valueOffset, element->length_of_value)); break; case DATA_TYPE_UNSIGNED_INTEGER: { guint64 value; proto_tree_add_item_ret_uint64(tlv_tree, *resource->hf_id, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN, &value); proto_item_append_text(tlv_tree, ": %" PRIu64, value); break; } case DATA_TYPE_FLOAT: proto_tree_add_item(tlv_tree, *resource->hf_id, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); if (element->length_of_value == 4) { proto_item_append_text(tlv_tree, ": %." G_STRINGIFY(FLT_DIG) "g", tvb_get_ieee_float(tvb, valueOffset, ENC_BIG_ENDIAN)); } else { proto_item_append_text(tlv_tree, ": %." G_STRINGIFY(DBL_DIG) "g", tvb_get_ieee_double(tvb, valueOffset, ENC_BIG_ENDIAN)); } break; case DATA_TYPE_BOOLEAN: { gboolean boolval; proto_tree_add_item_ret_boolean(tlv_tree, *resource->hf_id, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN, &boolval); proto_item_append_text(tlv_tree, ": %s", boolval ? "True" : "False"); break; } case DATA_TYPE_TIME: { nstime_t ts; ts.secs = (time_t)decodeVariableInt(tvb, valueOffset, element->length_of_value); ts.nsecs = 0; proto_tree_add_time(tlv_tree, *resource->hf_id, tvb, valueOffset, element->length_of_value, &ts); proto_item_append_text(tlv_tree, ": %s", abs_time_to_str(pinfo->pool, &ts, ABSOLUTE_TIME_LOCAL, FALSE)); break; } case DATA_TYPE_OBJLNK: { guint16 lnk1 = tvb_get_guint16(tvb, valueOffset, ENC_BIG_ENDIAN); guint16 lnk2 = tvb_get_guint16(tvb, valueOffset + 2, ENC_BIG_ENDIAN); proto_tree_add_bytes_format(tlv_tree, *resource->hf_id, tvb, valueOffset, element->length_of_value, NULL, "%u:%u", lnk1, lnk2); proto_item_append_text(tlv_tree, ": %u:%u", lnk1, lnk2); break; } case DATA_TYPE_OPAQUE: default: { proto_item *ti = proto_tree_add_item(tlv_tree, *resource->hf_id, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); proto_item_append_text(tlv_tree, ": %s", tvb_bytes_to_str(pinfo->pool, tvb, valueOffset, element->length_of_value)); if (resource->object_id == 6 && resource->resource_id == 4) { proto_tree *pt = proto_item_add_subtree(ti, ett_lwm2mtlv_location_velocity); dissect_description_of_velocity(tvb, pt, pinfo, valueOffset, element->length_of_value, NULL, 0); } break; } } } else { guint8 *str = tvb_get_string_enc(pinfo->pool, tvb, valueOffset, element->length_of_value, ENC_UTF_8); if (isprint_utf8_string(str, element->length_of_value)) { proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_string, tvb, valueOffset, element->length_of_value, ENC_UTF_8); } else { str = tvb_bytes_to_str(pinfo->pool, tvb, valueOffset, element->length_of_value); } proto_item_append_text(tlv_tree, ": %s", str); switch(element->length_of_value) { case 0x01: proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_integer, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_unsigned_integer, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); if (tvb_get_guint8(tvb, valueOffset) < 2) { proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_boolean, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); } break; case 0x02: proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_integer, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_unsigned_integer, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); break; case 0x04: proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_integer, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_unsigned_integer, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_float, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_timestamp, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); break; case 0x08: proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_integer, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_unsigned_integer, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_double, tvb, valueOffset, element->length_of_value, ENC_BIG_ENDIAN); /* apparently, wireshark does not deal well with 8 bytes. */ proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_timestamp, tvb, valueOffset+4, element->length_of_value-4, ENC_BIG_ENDIAN); break; } } } static void addValueTree(packet_info *pinfo, tvbuff_t *tvb, proto_tree *tlv_tree, lwm2mElement_t *element, gint object_id, gint resource_id, const lwm2m_resource_t *resource) { guint valueOffset = 1 + element->length_of_identifier + element->length_of_length; if (resource && (element->type == RESOURCE || element->type == RESOURCE_ARRAY)) { proto_item *ti = proto_tree_add_string(tlv_tree, hf_lwm2mtlv_resource_name, tvb, 0, 0, resource->name); proto_item_set_generated(ti); } if ( element->type == RESOURCE || element->type == RESOURCE_INSTANCE ) { proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value, tvb, valueOffset, element->length_of_value, ENC_NA); addValueInterpretations(pinfo, tvb, tlv_tree, element, resource); } else { tvbuff_t* sub = tvb_new_subset_length(tvb, valueOffset, element->length_of_value); parseArrayOfElements(pinfo, sub, tlv_tree, object_id, resource_id); } } static void addTlvElement(packet_info *pinfo, tvbuff_t *tvb, proto_tree *tlv_tree, lwm2mElement_t *element, gint object_id, gint resource_id) { proto_tree *element_tree = NULL; const lwm2m_resource_t *resource = NULL; if (object_id != -1 && resource_id != -1) { /* First search user configured objects */ resource = lwm2m_search_fields(object_id, resource_id, element->length_of_value, &uat_allocated_fields, lwm2m_uat_resources, num_lwm2m_uat_resources); if (resource == NULL) { /* Then search OMA objects */ resource = lwm2m_search_fields(object_id, resource_id, element->length_of_value, &oma_allocated_fields, lwm2m_oma_resources, num_lwm2m_oma_resources); } } element_tree = addElementTree(pinfo, tvb, tlv_tree, element, resource); addTlvHeaderTree(tvb, element_tree, element); addValueTree(pinfo, tvb, element_tree, element, object_id, resource_id, resource); } static guint64 decodeVariableUInt(tvbuff_t *tvb, const gint offset, const guint length) { switch(length) { case 1: return tvb_get_guint8(tvb, offset); case 2: return tvb_get_ntohs(tvb, offset); case 3: return tvb_get_ntoh24(tvb, offset); case 4: return tvb_get_ntohl(tvb, offset); case 5: return tvb_get_ntoh40(tvb, offset); case 6: return tvb_get_ntoh48(tvb, offset); case 7: return tvb_get_ntoh56(tvb, offset); case 8: return tvb_get_ntoh64(tvb, offset); default: return 0; } } static guint parseTLVHeader(tvbuff_t *tvb, lwm2mElement_t *element) { guint type_field = tvb_get_guint8(tvb, 0); element->type = (( type_field >> 6 ) & 0x03 ); element->length_of_identifier = (( type_field >> 5 ) & 0x01 ) + 1; element->length_of_length = (( type_field >> 3 ) & 0x03 ); element->length_of_value = (( type_field >> 0 ) & 0x07 ); /* It is ok to shorten identifier and length_of_value, they are never more than 24 bits long */ element->identifier = (guint) decodeVariableUInt(tvb, 1, element->length_of_identifier); if ( element->length_of_length > 0 ) { element->length_of_value = (guint) decodeVariableUInt(tvb, 1 + element->length_of_identifier, element->length_of_length); } element->totalLength = 1 + element->length_of_identifier + element->length_of_length + element->length_of_value; return element->totalLength; } static void parseArrayOfElements(packet_info *pinfo, tvbuff_t *tvb, proto_tree *tlv_tree, gint object_id, gint resource_id) { guint length; guint offset = 0; guint elementLength = 0; guint element_count = 0; lwm2mElement_t element; length = tvb_reported_length(tvb); while ( length > 0 ) { tvbuff_t* sub = tvb_new_subset_length(tvb, offset, length); elementLength = parseTLVHeader(sub, &element); if (element.type == RESOURCE || element.type == RESOURCE_ARRAY) { resource_id = (gint)element.identifier; } addTlvElement(pinfo, sub, tlv_tree, &element, object_id, resource_id); element_count++; length -= elementLength; offset += elementLength; if ( elementLength == 0 ) { break; } } proto_item_append_text(tlv_tree, " (%u element%s)", element_count, plurality(element_count, "", "s")); } static int dissect_lwm2mtlv(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) { proto_tree* lwm2mtlv_tree; proto_item* lwm2mtlv_item; http_message_info_t *message_info = (http_message_info_t *) data; gint object_id = -1; gint resource_id = -1; if (message_info && message_info->media_str && message_info->media_str[0]) { gchar **ids = wmem_strsplit(pinfo->pool, message_info->media_str, "/", 5); /* URI path is defined as: * ids[1] = Object ID * ids[2] = Object Instance * ids[3] = Resource ID * ids[4] = Resource Instance */ if (ids && ids[0] && ids[1]) { object_id = (gint)strtol(ids[1], NULL, 10); if (ids[2] && ids[3]) { resource_id = (gint)strtol(ids[1], NULL, 10); } } } if (tree) { /* we are being asked for details */ lwm2mtlv_item = proto_tree_add_item(tree, proto_lwm2mtlv, tvb, 0, -1, ENC_NA); lwm2mtlv_tree = proto_item_add_subtree(lwm2mtlv_item, ett_lwm2mtlv); if (object_id != -1) { const gchar *object_name = NULL; for (guint i = 0; i < num_lwm2m_uat_object_names; i++) { if ((guint)object_id == lwm2m_uat_object_names[i].object_id) { object_name = lwm2m_uat_object_names[i].name; break; } } if (!object_name) { object_name = val_to_str_const(object_id, lwm2m_oma_objects, ""); } if (object_name && object_name[0]) { proto_item *ti = proto_tree_add_string(lwm2mtlv_tree, hf_lwm2mtlv_object_name, tvb, 0, 0, object_name); proto_item_set_generated(ti); proto_item_append_text(lwm2mtlv_item, ", %s", object_name); } } parseArrayOfElements(pinfo, tvb, lwm2mtlv_tree, object_id, resource_id); } return tvb_captured_length(tvb); } static void lwm2m_shutdown_routine(void) { lwm2m_free_fields(&oma_allocated_fields); } void proto_register_lwm2mtlv(void) { static hf_register_info hf[] = { { &hf_lwm2mtlv_object_name, { "Object Name", "lwm2mtlv.object_name", FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_resource_name, { "Resource Name", "lwm2mtlv.resource_name", FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_header, { "TLV header", "lwm2mtlv.header", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_lwm2mtlv_type_type, { "Type of Identifier", "lwm2mtlv.type.type", FT_UINT8, BASE_DEC, VALS(identifiers), 0xC0, NULL, HFILL } }, { &hf_lwm2mtlv_type_length_of_identifier, { "Length of Identifier", "lwm2mtlv.type.loi", FT_UINT8, BASE_DEC, VALS(length_identifier), 0x20, NULL, HFILL } }, { &hf_lwm2mtlv_type_length_of_length, { "Length of Length", "lwm2mtlv.type.lol", FT_UINT8, BASE_DEC, VALS(length_type), 0x18, NULL, HFILL } }, { &hf_lwm2mtlv_type_length, { "Length", "lwm2mtlv.type.length", FT_UINT8, BASE_DEC, NULL, 0x07, NULL, HFILL } }, { &hf_lwm2mtlv_type_ignored, { "Ignored", "lwm2mtlv.type.ignored", FT_UINT8, BASE_DEC, NULL, 0x07, NULL, HFILL } }, { &hf_lwm2mtlv_identifier, { "Identifier", "lwm2mtlv.identifier", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_length, { "Length", "lwm2mtlv.length", FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_value, { "Value", "lwm2mtlv.value", FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_value_string, { "As String", "lwm2mtlv.value.string", FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_value_integer, { "As Integer", "lwm2mtlv.value.integer", FT_INT64, BASE_DEC, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_value_unsigned_integer, { "As Unsigned Integer", "lwm2mtlv.value.unsigned_integer", FT_UINT64, BASE_DEC, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_value_float, { "As Float", "lwm2mtlv.value.float", FT_FLOAT, BASE_NONE, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_value_double, { "As Double", "lwm2mtlv.value.double", FT_DOUBLE, BASE_NONE, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_value_boolean, { "As Boolean", "lwm2mtlv.value.boolean", FT_BOOLEAN, BASE_NONE, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_value_timestamp, { "As Timestamp", "lwm2mtlv.value.timestamp", FT_ABSOLUTE_TIME, ABSOLUTE_TIME_LOCAL, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_object_instance, { "Object Instance", "lwm2mtlv.object_instance", FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_resource_instance, { "Resource Instance", "lwm2mtlv.resource_instance", FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_resource_array, { "Resource Array", "lwm2mtlv.resource_array", FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } }, { &hf_lwm2mtlv_resource, { "Resource", "lwm2mtlv.resource", FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } }, }; static gint* ett[] = { &ett_lwm2mtlv, &ett_lwm2mtlv_header, &ett_lwm2mtlv_resource, &ett_lwm2mtlv_resource_instance, &ett_lwm2mtlv_resource_array, &ett_lwm2mtlv_object_instance, &ett_lwm2mtlv_location_velocity }; static uat_field_t lwm2m_object_name_flds[] = { UAT_FLD_DEC(object_name, object_id, "Object ID", "Object ID"), UAT_FLD_CSTRING(object_name, name, "Object Name", "Object Name"), UAT_END_FIELDS }; static uat_field_t lwm2m_resource_flds[] = { UAT_FLD_DEC(resource, object_id, "Object ID", "Object ID"), UAT_FLD_DEC(resource, resource_id, "Resource ID", "Resource ID"), UAT_FLD_CSTRING(resource, name, "Resource Name", "Resource Name"), UAT_FLD_VS(resource, data_type, "Data Type", data_types, "Data Type"), UAT_END_FIELDS }; uat_t *object_name_uat = uat_new("User Object Names", sizeof(lwm2m_object_name_t), "lwm2m_object_names", TRUE, &lwm2m_uat_object_names, &num_lwm2m_uat_object_names, UAT_AFFECTS_DISSECTION, "ChLwM2MResourceNames", lwm2m_object_name_copy_cb, lwm2m_object_name_update_cb, lwm2m_object_name_free_cb, NULL, NULL, lwm2m_object_name_flds); uat_t *resource_uat = uat_new("User Resource Names", sizeof(lwm2m_resource_t), "lwm2m_resource_names", TRUE, &lwm2m_uat_resources, &num_lwm2m_uat_resources, UAT_AFFECTS_DISSECTION|UAT_AFFECTS_FIELDS, "ChLwM2MResourceNames", lwm2m_resource_copy_cb, lwm2m_resource_update_cb, lwm2m_resource_free_cb, lwm2m_resource_post_update_cb, lwm2m_resource_reset_cb, lwm2m_resource_flds); module_t *lwm2mtlv_module; /* Register our configuration options */ proto_lwm2mtlv = proto_register_protocol ( "Lightweight M2M TLV", "LwM2M-TLV", "lwm2mtlv" ); proto_register_field_array(proto_lwm2mtlv, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); register_dissector("lwm2mtlv", dissect_lwm2mtlv, proto_lwm2mtlv); /* Register the dissector shutdown function */ register_shutdown_routine(lwm2m_shutdown_routine); lwm2mtlv_module = prefs_register_protocol(proto_lwm2mtlv, NULL); prefs_register_uat_preference(lwm2mtlv_module, "object_table", "Object Names", "User Object Names", object_name_uat); prefs_register_uat_preference(lwm2mtlv_module, "resource_table", "Resource Names", "User Resource Names", resource_uat); lwm2m_allocate_fields(&oma_allocated_fields, lwm2m_oma_resources, num_lwm2m_oma_resources); } void proto_reg_handoff_lwm2mtlv(void) { static dissector_handle_t lwm2mtlv_handle; lwm2mtlv_handle = create_dissector_handle(dissect_lwm2mtlv, proto_lwm2mtlv); dissector_add_string("media_type", "application/vnd.oma.lwm2m+tlv", lwm2mtlv_handle); } /* * Editor modelines - https://www.wireshark.org/tools/modelines.html * * Local variables: * c-basic-offset: 8 * tab-width: 8 * indent-tabs-mode: t * End: * * vi: set shiftwidth=8 tabstop=8 noexpandtab: * :indentSize=8:tabSize=8:noTabs=false: */