diff options
author | Christoph Burger-Scheidlin <mail@christoph.burger-scheidlin.name> | 2016-02-08 21:31:46 +0100 |
---|---|---|
committer | Alexis La Goutte <alexis.lagoutte@gmail.com> | 2016-02-29 21:07:09 +0000 |
commit | 53252df9204629b35ea9fef9b800bfc5bb1bdac6 (patch) | |
tree | 65f140cd51575d519c03a67632b20d20e7debf49 | |
parent | 8ff3d1bd75588b8829797d603df946545cb730e7 (diff) |
Support dissecting the TLV data format specified for Lwm2m
Lightweight M2M is a protocol on top of CoAP that is used for
device management. The specification contains a custom payload
format - a simple type, length, value binary encoding.
This patch adds support for dissecting this payload format.
While not yet officially registered, the main open source
implementation of the lwm2m protocol - eclipse's leshan - uses this
content type 1542 for its messages.
Bug: 12110
Change-Id: Ib022d1f485c706f1d69ceec7200790448d080965
Reviewed-on: https://code.wireshark.org/review/13835
Petri-Dish: Alexis La Goutte <alexis.lagoutte@gmail.com>
Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org>
Reviewed-by: Alexis La Goutte <alexis.lagoutte@gmail.com>
-rw-r--r-- | docbook/release-notes.asciidoc | 1 | ||||
-rw-r--r-- | epan/CMakeLists.txt | 1 | ||||
-rw-r--r-- | epan/dissectors/Makefile.common | 1 | ||||
-rw-r--r-- | epan/dissectors/packet-coap.c | 1 | ||||
-rw-r--r-- | epan/dissectors/packet-lwm2mtlv.c | 430 |
5 files changed, 434 insertions, 0 deletions
diff --git a/docbook/release-notes.asciidoc b/docbook/release-notes.asciidoc index 4434510beb..357b4d6c9f 100644 --- a/docbook/release-notes.asciidoc +++ b/docbook/release-notes.asciidoc @@ -61,6 +61,7 @@ UserLog Protocol FLEXRAY Protocol dissector added (automotive bus) USB3 Vision Protocol (USB machine vision cameras) USBIP Protocol +Open Mobile Alliance Lightweight Machine to Machine TLV payload Added (LwM2M TLV) // Items in --sort-and-group-- blocks will be sorted and comma-separated. --sort-and-group-- diff --git a/epan/CMakeLists.txt b/epan/CMakeLists.txt index e1e29b371d..e0335a2db1 100644 --- a/epan/CMakeLists.txt +++ b/epan/CMakeLists.txt @@ -957,6 +957,7 @@ set(DISSECTOR_SRC dissectors/packet-ltp.c dissectors/packet-lwapp.c dissectors/packet-lwm.c + dissectors/packet-lwm2mtlv.c dissectors/packet-lwres.c dissectors/packet-m2pa.c dissectors/packet-m2tp.c diff --git a/epan/dissectors/Makefile.common b/epan/dissectors/Makefile.common index de4cd27864..4ecf5ea954 100644 --- a/epan/dissectors/Makefile.common +++ b/epan/dissectors/Makefile.common @@ -877,6 +877,7 @@ DISSECTOR_SRC = \ packet-ltp.c \ packet-lwapp.c \ packet-lwm.c \ + packet-lwm2mtlv.c \ packet-lwres.c \ packet-m2pa.c \ packet-m2tp.c \ diff --git a/epan/dissectors/packet-coap.c b/epan/dissectors/packet-coap.c index fc7d3b1846..8f02858c4b 100644 --- a/epan/dissectors/packet-coap.c +++ b/epan/dissectors/packet-coap.c @@ -254,6 +254,7 @@ static const value_string vals_ctype[] = { { 47, "application/exi" }, { 50, "application/json" }, { 60, "application/cbor" }, + { 1542, "application/vnd.oma.lwm2m+tlv" }, { 0, NULL }, }; diff --git a/epan/dissectors/packet-lwm2mtlv.c b/epan/dissectors/packet-lwm2mtlv.c new file mode 100644 index 0000000000..8481f1ce25 --- /dev/null +++ b/epan/dissectors/packet-lwm2mtlv.c @@ -0,0 +1,430 @@ +/* packet-lwm2mtlv.c + * Routines for LWM2M TLV dissection + * References: + * OMA LWM2M Specification: OMA-TS-LightweightM2M-V1_0-20151214-C.pdf + * available from + * http://technical.openmobilealliance.org/Technical/technical-information/release-program/current-releases/oma-lightweightm2m-v1-0 + * + * Copyright 2016, Christoph Burger-Scheidlin + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + + +#include <epan/packet.h> + +void proto_register_lwm2mtlv(void); +void proto_reg_handoff_lwm2mtlv(void); + +static void parseArrayOfElements(tvbuff_t *tvb, proto_tree *tlv_tree); + +static int proto_lwm2mtlv = -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_float = -1; +static int hf_lwm2mtlv_value_double = -1; +static int hf_lwm2mtlv_value_boolean = -1; +static int hf_lwm2mtlv_value_timestamp = -1; + +static gint ett_lwm2mtlv = -1; +static gint ett_lwm2mtlv_header = -1; +static gint ett_lwm2mtlv_resource = -1; +static gint ett_lwm2mtlv_resourceInstance = -1; +static gint ett_lwm2mtlv_resourceArray = -1; +static gint ett_lwm2mtlv_objectInstance = -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; + + +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(tvbuff_t *tvb, proto_tree *tlv_tree, lwm2mElement_t *element) +{ + guint valueOffset = 1 + element->length_of_identifier + element->length_of_length; + guint8 *str; + + switch ( element->type ) + { + case OBJECT_INSTANCE: + return proto_tree_add_subtree_format(tlv_tree, tvb, 0, element->totalLength, ett_lwm2mtlv_objectInstance, NULL, + "Object Instance %02u (%u Bytes)", element->identifier, element->length_of_value); + + case RESOURCE_INSTANCE: + str = tvb_get_string_enc(wmem_packet_scope(), tvb, valueOffset, element->length_of_value, ENC_ASCII); + return proto_tree_add_subtree_format(tlv_tree, tvb, 0, element->totalLength, ett_lwm2mtlv_resourceInstance, NULL, + "%02u: %s", element->identifier, str); + + case RESOURCE_ARRAY: + return proto_tree_add_subtree_format(tlv_tree, tvb, 0, element->totalLength, ett_lwm2mtlv_resourceArray, NULL, + "%02u: (Array of %u Bytes)", element->identifier, element->length_of_value); + + case RESOURCE: + str = tvb_get_string_enc(wmem_packet_scope(), tvb, valueOffset, element->length_of_value, ENC_ASCII); + return proto_tree_add_subtree_format(tlv_tree, tvb, 0, element->totalLength, ett_lwm2mtlv_resource, NULL, + "%02u: %s", element->identifier, str); + } + return NULL; +} + +static void +addValueInterpretations(tvbuff_t *tvb, proto_tree *tlv_tree, lwm2mElement_t *element) +{ + guint valueOffset; + if ( element->length_of_value == 0 ) return; + + valueOffset = 1 + element->length_of_identifier + element->length_of_length; + + proto_tree_add_item(tlv_tree, hf_lwm2mtlv_value_string, tvb, valueOffset, element->length_of_value, ENC_ASCII|ENC_NA); + + 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_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); + 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_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_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(tvbuff_t *tvb, proto_tree *tlv_tree, lwm2mElement_t *element) +{ + guint valueOffset = 1 + element->length_of_identifier + element->length_of_length; + + 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(tvb, tlv_tree, element); + } else { + tvbuff_t* sub = tvb_new_subset_length(tvb, valueOffset, element->length_of_value); + parseArrayOfElements(sub, tlv_tree); + } +} + +static void +addTlvElement(tvbuff_t *tvb, proto_tree *tlv_tree, lwm2mElement_t *element) +{ + proto_tree *element_tree = NULL; + + element_tree = addElementTree(tvb, tlv_tree, element); + addTlvHeaderTree(tvb, element_tree, element); + addValueTree(tvb, element_tree, element); +} + +static guint64 +decodeVariableInt(tvbuff_t *tvb, const gint offset, const guint length) +{ + switch(length) + { + case 1: + return tvb_get_guint8(tvb, offset); + case 2: + return tvb_get_letohs(tvb, offset); + case 3: + return tvb_get_letoh24(tvb, offset); + case 4: + return tvb_get_letohl(tvb, offset); + case 5: + return tvb_get_letoh40(tvb, offset); + case 6: + return tvb_get_letoh48(tvb, offset); + case 7: + return tvb_get_letoh56(tvb, offset); + case 8: + return tvb_get_letoh64(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) decodeVariableInt(tvb, 1, element->length_of_identifier); + if ( element->length_of_length > 0 ) { + element->length_of_value = (guint) decodeVariableInt(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(tvbuff_t *tvb, proto_tree *tlv_tree) +{ + guint length; + guint offset = 0; + guint elementLength = 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); + addTlvElement(sub, tlv_tree, &element); + + length -= elementLength; + offset += elementLength; + if ( elementLength == 0 ) + { + break; + } + } +} + +static int +dissect_lwm2mtlv(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, void* data _U_) +{ + proto_tree* lwm2mtlv_tree; + proto_item* lwm2mtlv_item; + + 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); + + parseArrayOfElements(tvb, lwm2mtlv_tree); + } + return tvb_captured_length(tvb); +} + +void proto_register_lwm2mtlv(void) +{ + static hf_register_info hf[] = { + { &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_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 } + }, + }; + + static gint* ett[] = { + &ett_lwm2mtlv, + &ett_lwm2mtlv_header, + &ett_lwm2mtlv_resource, + &ett_lwm2mtlv_resourceInstance, + &ett_lwm2mtlv_resourceArray, + &ett_lwm2mtlv_objectInstance + }; + + /* 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); +} + +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 - http://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: + */ |