diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c4fd38cf6..04f3b03b5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2274,6 +2274,23 @@ add_custom_command( WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" ) +file(GLOB _protobuf_src_files RELATIVE "${CMAKE_SOURCE_DIR}" protobuf/*.proto) +set (_protobuf_data_files) +foreach(_data_file ${_protobuf_src_files}) + list(APPEND _protobuf_data_files "${DATAFILE_DIR}/${_data_file}") +endforeach() + +add_custom_command( + OUTPUT ${_protobuf_data_files} + COMMAND ${CMAKE_COMMAND} -E make_directory "${DATAFILE_DIR}/protobuf" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${_protobuf_src_files}" + "${DATAFILE_DIR}/protobuf" + VERBATIM + DEPENDS ${_protobuf_src_files} + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" +) + file(GLOB _profiles_src_files RELATIVE "${CMAKE_SOURCE_DIR}" profiles/*/*) set (_profiles_data_files) foreach(_data_file ${_profiles_src_files}) @@ -2290,6 +2307,7 @@ list(APPEND copy_data_files_depends ${_dtds_data_files} ${_diameter_data_files} ${_radius_data_files} + ${_protobuf_data_files} ${_profiles_data_files} ) diff --git a/NEWS b/NEWS index d77903c1c4..8eaa1bf868 100644 --- a/NEWS +++ b/NEWS @@ -86,8 +86,8 @@ Wireshark 3.5.0 Release Notes New Protocol Support Kerberos SPAKE, O-RAN fronthaul UC-plane (O-RAN), PDU Transport - Protocol, R09.x (R09), State Synchronization Protocol (SSyncP), and - UAVCAN\CAN + Protocol, R09.x (R09), SparkplugB, State Synchronization Protocol (SSyncP), + and UAVCAN\CAN. Updated Protocol Support diff --git a/docbook/release-notes.adoc b/docbook/release-notes.adoc index b5e122fbc6..23d29f70e6 100644 --- a/docbook/release-notes.adoc +++ b/docbook/release-notes.adoc @@ -88,6 +88,7 @@ They previously shipped with Npcap 1.20. Kerberos SPAKE O-RAN fronthaul UC-plane (O-RAN) PDU Transport Protocol +SparkplugB State Synchronization Protocol (SSyncP) UAVCAN\CAN R09.x (R09) diff --git a/epan/dissectors/CMakeLists.txt b/epan/dissectors/CMakeLists.txt index 48e607c198..d430db1959 100644 --- a/epan/dissectors/CMakeLists.txt +++ b/epan/dissectors/CMakeLists.txt @@ -1773,6 +1773,7 @@ set(DISSECTOR_SRC ${CMAKE_CURRENT_SOURCE_DIR}/packet-someip.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-someip-sd.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-soupbintcp.c + ${CMAKE_CURRENT_SOURCE_DIR}/packet-sparkplug.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-spdy.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-spice.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-spp.c diff --git a/epan/dissectors/packet-mqtt.c b/epan/dissectors/packet-mqtt.c index c8503e3780..79fbbc5e39 100644 --- a/epan/dissectors/packet-mqtt.c +++ b/epan/dissectors/packet-mqtt.c @@ -752,7 +752,7 @@ static gboolean mqtt_user_decode_message(proto_tree *tree, proto_tree *mqtt_tree message_decode_entry->payload_proto_name); proto_item_set_generated(ti); - call_dissector(message_decode_entry->payload_proto, msg_tvb, pinfo, tree); + call_dissector_with_data(message_decode_entry->payload_proto, msg_tvb, pinfo, tree, (void*)topic_str); } } diff --git a/epan/dissectors/packet-sparkplug.c b/epan/dissectors/packet-sparkplug.c new file mode 100644 index 0000000000..86d888acfe --- /dev/null +++ b/epan/dissectors/packet-sparkplug.c @@ -0,0 +1,169 @@ +/* packet-sparkplug.c + * Routines for Sparkplug dissection + * Copyright 2021 Graham Bloice trihedral.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#include +#include + +/* + * See + * + * https://cirrus-link.com/mqtt-sparkplug-tahu/ + * + * and the specification is at + * + * https://www.eclipse.org/tahu/spec/Sparkplug%20Topic%20Namespace%20and%20State%20ManagementV2.2-with%20appendix%20B%20format%20-%20Eclipse.pdf + * + */ + +/* Prototypes */ +/* (Required to prevent [-Wmissing-prototypes] warnings */ +void proto_reg_handoff_sparkplug(void); + +/* Initialize the protocol field */ +static int proto_sparkplugb = -1; + +/* Initialize the subtree pointers */ +static gint ett_sparkplugb = -1; +static gint ett_sparkplugb_namespace = -1; + +/* The handle to the protobuf dissector */ +dissector_handle_t protobuf_handle = NULL; + +/* The hf items */ +static int hf_sparkplugb_namespace = -1; +static int hf_sparkplugb_groupid = -1; +static int hf_sparkplugb_messagetype = -1; +static int hf_sparkplugb_edgenodeid = -1; +static int hf_sparkplugb_deviceid = -1; + +/* The expert info items */ +static expert_field ei_sparkplugb_missing_groupid = EI_INIT; +static expert_field ei_sparkplugb_missing_messagetype = EI_INIT; +static expert_field ei_sparkplugb_missing_edgenodeid = EI_INIT; + +static gboolean +dissect_sparkplugb(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) +{ + proto_item *ti; + proto_tree *sparkplugb_tree, *namespace_tree; + gchar **topic_elements, **current_element; + char *topic = (char *)data; + + /* Parse the topic into the elements */ + topic_elements = g_strsplit(topic, "/", 5); + + /* Heuristic check that the first element of the topic is the SparkplugB namespace */ + if (!topic_elements || (strcmp("spBv1.0", topic_elements[0]) != 0)) { + return FALSE; + } + + /* Make entries in Protocol column */ + col_set_str(pinfo->cinfo, COL_PROTOCOL, "SparkplugB"); + + /* Adjust the info column */ + col_clear(pinfo->cinfo, COL_INFO); + col_set_str(pinfo->cinfo, COL_INFO, "SparkplugB"); + + /* create display subtree for the protocol */ + ti = proto_tree_add_item(tree, proto_sparkplugb, tvb, 0, -1, ENC_NA); + sparkplugb_tree = proto_item_add_subtree(ti, ett_sparkplugb); + + /* Add the elements parsed out from the topic string */ + namespace_tree = proto_tree_add_subtree(sparkplugb_tree, tvb, 0, 0, ett_sparkplugb_namespace, &ti, "Topic Namespace"); + proto_item_set_generated(ti); + + current_element = topic_elements; + ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_namespace, tvb, 0, 0, current_element[0]); + proto_item_set_generated(ti); + + current_element += 1; + ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_groupid, tvb, 0, 0, current_element[0]); + proto_item_set_generated(ti); + if (!current_element[0]) { + expert_add_info(pinfo, ti, &ei_sparkplugb_missing_groupid); + } + + /* Adjust the info colum text with the message type */ + current_element += 1; + if (current_element[0]) { + col_append_sep_str(pinfo->cinfo, COL_INFO, NULL, current_element[0]); + col_set_fence(pinfo->cinfo, COL_INFO); + } + + ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_messagetype, tvb, 0, 0, current_element[0]); + proto_item_set_generated(ti); + if (!current_element[0]) { + expert_add_info(pinfo, ti, &ei_sparkplugb_missing_messagetype); + } + + current_element += 1; + ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_edgenodeid, tvb, 0, 0, current_element[0]); + proto_item_set_generated(ti); + if (!current_element[0]) { + expert_add_info(pinfo, ti, &ei_sparkplugb_missing_edgenodeid); + } + + /* Device ID is optional */ + current_element += 1; + ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_deviceid, tvb, 0, 0, current_element[0]); + proto_item_set_generated(ti); + + g_strfreev(topic_elements); + + /* Now handoff the Payload message to the protobuf dissector */ + call_dissector_with_data(protobuf_handle, tvb, pinfo, sparkplugb_tree, "message,com.cirruslink.sparkplug.protobuf.Payload"); + + return TRUE; +} + +void proto_register_sparkplug(void) +{ + expert_module_t* expert_sparkplugb; + + static gint *ett[] = { + &ett_sparkplugb, + &ett_sparkplugb_namespace + }; + + static hf_register_info hf[] = { + {&hf_sparkplugb_namespace, {"Namespace", "sparkplugb.namespace", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_sparkplugb_groupid, {"Group ID", "sparkplugb.groupid", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_sparkplugb_messagetype, {"Message Type", "sparkplugb.messagetype", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_sparkplugb_edgenodeid, {"Edge Node ID", "sparkplugb.edgenodeid", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_sparkplugb_deviceid, {"Device ID", "sparkplugb.deviceid", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + }; + + static ei_register_info ei[] = { + { &ei_sparkplugb_missing_groupid, { "sparkplugb.missing_groupid", PI_MALFORMED, PI_ERROR, "Missing Group ID", EXPFILL }}, + { &ei_sparkplugb_missing_messagetype, { "sparkplugb.missing_messagetype", PI_MALFORMED, PI_ERROR, "Missing Message Type", EXPFILL }}, + { &ei_sparkplugb_missing_edgenodeid, { "sparkplugb.missing_edgenodeid", PI_MALFORMED, PI_ERROR, "Missing Edge Node ID", EXPFILL }}, + }; + + /* Register the protocol name and description, fields and trees */ + proto_sparkplugb = proto_register_protocol("SparkplugB", "SparkplugB", "sparkplugb"); + register_dissector("sparkplugb", dissect_sparkplugb, proto_sparkplugb); + + proto_register_field_array(proto_sparkplugb, hf, array_length(hf)); + proto_register_subtree_array(ett, array_length(ett)); + + expert_sparkplugb = expert_register_protocol(proto_sparkplugb); + expert_register_field_array(expert_sparkplugb, ei, array_length(ei)); +} + +void proto_reg_handoff_sparkplug(void) +{ + protobuf_handle = find_dissector_add_dependency("protobuf", proto_sparkplugb); + + /* register as heuristic dissector with MQTT */ + heur_dissector_add("mqtt.topic", dissect_sparkplugb, "SparkplugB over MQTT", + "sparkplugb_mqtt", proto_sparkplugb, HEURISTIC_ENABLE); +} diff --git a/packaging/nsis/uninstall.nsi b/packaging/nsis/uninstall.nsi index c8493ae91b..3d115aa53f 100644 --- a/packaging/nsis/uninstall.nsi +++ b/packaging/nsis/uninstall.nsi @@ -215,6 +215,7 @@ Delete "$INSTDIR\snmp\*.*" Delete "$INSTDIR\snmp\mibs\*.*" Delete "$INSTDIR\styles\translations\*.*" Delete "$INSTDIR\styles\*.*" +Delete "$INSTDIR\protobuf\*.*" Delete "$INSTDIR\tpncp\*.*" Delete "$INSTDIR\translations\*.*" Delete "$INSTDIR\ui\*.*" @@ -295,6 +296,7 @@ RMDir "$INSTDIR\snmp\mibs" RMDir "$INSTDIR\snmp" RMDir "$INSTDIR\radius" RMDir "$INSTDIR\dtds" +RMDir "$INSTDIR\protobuf" RMDir "$INSTDIR\tpncp" RMDir "$INSTDIR\translations" RMDir "$INSTDIR\ui" diff --git a/packaging/nsis/wireshark.nsi b/packaging/nsis/wireshark.nsi index 7af2a5c6e5..b00bf59c82 100644 --- a/packaging/nsis/wireshark.nsi +++ b/packaging/nsis/wireshark.nsi @@ -857,6 +857,12 @@ SetOutPath $INSTDIR ; Create the extcap directory CreateDirectory $INSTDIR\extcap +; +; install the protobuf .proto definitions in the protobuf subdirectory +; +SetOutPath $INSTDIR\protobuf +File "${STAGING_DIR}\protobuf\sparkplug_b.proto" + ; Install the TPNCP DAT file in the "tpncp" subdirectory ; of the installation directory. SetOutPath $INSTDIR\tpncp diff --git a/packaging/wix/ComponentGroups.wxi b/packaging/wix/ComponentGroups.wxi index 59c6a1c84a..0dd35eff3e 100644 --- a/packaging/wix/ComponentGroups.wxi +++ b/packaging/wix/ComponentGroups.wxi @@ -221,6 +221,20 @@ + + + + + + + + + + + + + + diff --git a/packaging/wix/DirectoryStructure.wxi b/packaging/wix/DirectoryStructure.wxi index fe70b66393..fe1b9f6d68 100644 --- a/packaging/wix/DirectoryStructure.wxi +++ b/packaging/wix/DirectoryStructure.wxi @@ -30,6 +30,7 @@ + diff --git a/packaging/wix/Features.wxi b/packaging/wix/Features.wxi index 58837cba21..35aefc60de 100644 --- a/packaging/wix/Features.wxi +++ b/packaging/wix/Features.wxi @@ -20,6 +20,7 @@ + diff --git a/packaging/wix/InputPaths.wxi b/packaging/wix/InputPaths.wxi index 0fae77b721..5ff623559a 100644 --- a/packaging/wix/InputPaths.wxi +++ b/packaging/wix/InputPaths.wxi @@ -15,6 +15,7 @@ + diff --git a/protobuf/sparkplug_b.proto b/protobuf/sparkplug_b.proto new file mode 100644 index 0000000000..bda645cc91 --- /dev/null +++ b/protobuf/sparkplug_b.proto @@ -0,0 +1,197 @@ +syntax = "proto2"; + +// +// To compile: +// cd client_libraries/java +// protoc --proto_path=../../ --java_out=src/main/java ../../sparkplug_b.proto +// +package com.cirruslink.sparkplug.protobuf; + +option java_package = "com.cirruslink.sparkplug.protobuf"; +option java_outer_classname = "SparkplugBProto"; + +message Payload { + /* + // Indexes of Data Types + + // Unknown placeholder for future expansion. + Unknown = 0; + + // Basic Types + Int8 = 1; + Int16 = 2; + Int32 = 3; + Int64 = 4; + UInt8 = 5; + UInt16 = 6; + UInt32 = 7; + UInt64 = 8; + Float = 9; + Double = 10; + Boolean = 11; + String = 12; + DateTime = 13; + Text = 14; + + // Additional Metric Types + UUID = 15; + DataSet = 16; + Bytes = 17; + File = 18; + Template = 19; + + // Additional PropertyValue Types + PropertySet = 20; + PropertySetList = 21; + + */ + + message Template { + + message Parameter { + optional string name = 1; + optional uint32 type = 2; + + oneof value { + uint32 int_value = 3; + uint64 long_value = 4; + float float_value = 5; + double double_value = 6; + bool boolean_value = 7; + string string_value = 8; + ParameterValueExtension extension_value = 9; + } + + message ParameterValueExtension { + extensions 1 to max; + } + } + + optional string version = 1; // The version of the Template to prevent mismatches + repeated Metric metrics = 2; // Each metric is the name of the metric and the datatype of the member but does not contain a value + repeated Parameter parameters = 3; + optional string template_ref = 4; // Reference to a template if this is extending a Template or an instance - must exist if an instance + optional bool is_definition = 5; + extensions 6 to max; + } + + message DataSet { + + message DataSetValue { + + oneof value { + uint32 int_value = 1; + uint64 long_value = 2; + float float_value = 3; + double double_value = 4; + bool boolean_value = 5; + string string_value = 6; + DataSetValueExtension extension_value = 7; + } + + message DataSetValueExtension { + extensions 1 to max; + } + } + + message Row { + repeated DataSetValue elements = 1; + extensions 2 to max; // For third party extensions + } + + optional uint64 num_of_columns = 1; + repeated string columns = 2; + repeated uint32 types = 3; + repeated Row rows = 4; + extensions 5 to max; // For third party extensions + } + + message PropertyValue { + + optional uint32 type = 1; + optional bool is_null = 2; + + oneof value { + uint32 int_value = 3; + uint64 long_value = 4; + float float_value = 5; + double double_value = 6; + bool boolean_value = 7; + string string_value = 8; + PropertySet propertyset_value = 9; + PropertySetList propertysets_value = 10; // List of Property Values + PropertyValueExtension extension_value = 11; + } + + message PropertyValueExtension { + extensions 1 to max; + } + } + + message PropertySet { + repeated string keys = 1; // Names of the properties + repeated PropertyValue values = 2; + extensions 3 to max; + } + + message PropertySetList { + repeated PropertySet propertyset = 1; + extensions 2 to max; + } + + message MetaData { + // Bytes specific metadata + optional bool is_multi_part = 1; + + // General metadata + optional string content_type = 2; // Content/Media type + optional uint64 size = 3; // File size, String size, Multi-part size, etc + optional uint64 seq = 4; // Sequence number for multi-part messages + + // File metadata + optional string file_name = 5; // File name + optional string file_type = 6; // File type (i.e. xml, json, txt, cpp, etc) + optional string md5 = 7; // md5 of data + + // Catchalls and future expansion + optional string description = 8; // Could be anything such as json or xml of custom properties + extensions 9 to max; + } + + message Metric { + + optional string name = 1; // Metric name - should only be included on birth + optional uint64 alias = 2; // Metric alias - tied to name on birth and included in all later DATA messages + optional uint64 timestamp = 3; // Timestamp associated with data acquisition time + optional uint32 datatype = 4; // DataType of the metric/tag value + optional bool is_historical = 5; // If this is historical data and should not update real time tag + optional bool is_transient = 6; // Tells consuming clients such as MQTT Engine to not store this as a tag + optional bool is_null = 7; // If this is null - explicitly say so rather than using -1, false, etc for some datatypes. + optional MetaData metadata = 8; // Metadata for the payload + optional PropertySet properties = 9; + + oneof value { + uint32 int_value = 10; + uint64 long_value = 11; + float float_value = 12; + double double_value = 13; + bool boolean_value = 14; + string string_value = 15; + bytes bytes_value = 16; // Bytes, File + DataSet dataset_value = 17; + Template template_value = 18; + MetricValueExtension extension_value = 19; + } + + message MetricValueExtension { + extensions 1 to max; + } + } + + optional uint64 timestamp = 1; // Timestamp at message sending time + repeated Metric metrics = 2; // Repeated forever - no limit in Google Protobufs + optional uint64 seq = 3; // Sequence number + optional string uuid = 4; // UUID to track message type in terms of schema definitions + optional bytes body = 5; // To optionally bypass the whole definition above + extensions 6 to max; // For third party extensions +}