/src/wireshark/epan/dissectors/packet-sparkplug.c
Line | Count | Source |
1 | | /* packet-sparkplug.c |
2 | | * Routines for Sparkplug dissection |
3 | | * Copyright 2021 Graham Bloice <graham.bloice<at>trihedral.com> |
4 | | * |
5 | | * Wireshark - Network traffic analyzer |
6 | | * By Gerald Combs <gerald@wireshark.org> |
7 | | * Copyright 1998 Gerald Combs |
8 | | * |
9 | | * SPDX-License-Identifier: GPL-2.0-or-later |
10 | | */ |
11 | | |
12 | | #include "config.h" |
13 | | #include <epan/packet.h> |
14 | | #include <epan/expert.h> |
15 | | |
16 | | /* |
17 | | * See |
18 | | * |
19 | | * https://cirrus-link.com/mqtt-sparkplug-tahu/ |
20 | | * |
21 | | * and the specification is at |
22 | | * |
23 | | * https://www.eclipse.org/tahu/spec/Sparkplug%20Topic%20Namespace%20and%20State%20ManagementV2.2-with%20appendix%20B%20format%20-%20Eclipse.pdf |
24 | | * |
25 | | */ |
26 | | |
27 | | /* Prototypes */ |
28 | | /* (Required to prevent [-Wmissing-prototypes] warnings */ |
29 | | void proto_reg_handoff_sparkplug(void); |
30 | | void proto_register_sparkplug(void); |
31 | | |
32 | | /* Initialize the protocol field */ |
33 | | static int proto_sparkplugb; |
34 | | |
35 | | /* Initialize the subtree pointers */ |
36 | | static int ett_sparkplugb; |
37 | | static int ett_sparkplugb_namespace; |
38 | | |
39 | | /* The handle to the protobuf dissector */ |
40 | | static dissector_handle_t protobuf_handle; |
41 | | |
42 | | /* The hf items */ |
43 | | static int hf_sparkplugb_namespace; |
44 | | static int hf_sparkplugb_groupid; |
45 | | static int hf_sparkplugb_messagetype; |
46 | | static int hf_sparkplugb_edgenodeid; |
47 | | static int hf_sparkplugb_deviceid; |
48 | | |
49 | | /* The expert info items */ |
50 | | static expert_field ei_sparkplugb_missing_groupid; |
51 | | static expert_field ei_sparkplugb_missing_messagetype; |
52 | | static expert_field ei_sparkplugb_missing_edgenodeid; |
53 | | |
54 | | static int |
55 | | dissect_sparkplugb(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) |
56 | 9 | { |
57 | 9 | proto_item *ti; |
58 | 9 | proto_tree *sparkplugb_tree, *namespace_tree; |
59 | 9 | char **topic_elements, **current_element; |
60 | 9 | char *topic = (char *)data; |
61 | | |
62 | | /* Confirm the expected topic data is present */ |
63 | 9 | if (topic == NULL) |
64 | 0 | return 0; |
65 | | |
66 | | /* Parse the topic into the elements */ |
67 | 9 | topic_elements = g_strsplit(topic, "/", 5); |
68 | | |
69 | | /* Heuristic check that the first element of the topic is the SparkplugB namespace */ |
70 | 9 | if (!topic_elements || (g_strcmp0("spBv1.0", topic_elements[0]) != 0)) { |
71 | 9 | g_strfreev(topic_elements); |
72 | 9 | return 0; |
73 | 9 | } |
74 | | |
75 | | /* Make entries in Protocol column */ |
76 | 0 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "SparkplugB"); |
77 | | |
78 | | /* Adjust the info column */ |
79 | 0 | col_clear(pinfo->cinfo, COL_INFO); |
80 | 0 | col_append_sep_str(pinfo->cinfo, COL_INFO, NULL, "SparkplugB"); |
81 | | |
82 | | /* create display subtree for the protocol */ |
83 | 0 | ti = proto_tree_add_item(tree, proto_sparkplugb, tvb, 0, -1, ENC_NA); |
84 | 0 | sparkplugb_tree = proto_item_add_subtree(ti, ett_sparkplugb); |
85 | | |
86 | | /* Add the elements parsed out from the topic string */ |
87 | 0 | namespace_tree = proto_tree_add_subtree(sparkplugb_tree, tvb, 0, 0, ett_sparkplugb_namespace, &ti, "Topic Namespace"); |
88 | 0 | proto_item_set_generated(ti); |
89 | |
|
90 | 0 | current_element = topic_elements; |
91 | 0 | ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_namespace, tvb, 0, 0, current_element[0]); |
92 | 0 | proto_item_set_generated(ti); |
93 | |
|
94 | 0 | current_element += 1; |
95 | 0 | if (current_element[0]) { |
96 | 0 | ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_groupid, tvb, 0, 0, current_element[0]); |
97 | 0 | proto_item_set_generated(ti); |
98 | 0 | } |
99 | 0 | else { |
100 | 0 | expert_add_info(pinfo, namespace_tree, &ei_sparkplugb_missing_groupid); |
101 | 0 | g_strfreev(topic_elements); |
102 | 0 | return 0; |
103 | 0 | } |
104 | | |
105 | | /* Adjust the info column text with the message type */ |
106 | 0 | current_element += 1; |
107 | 0 | if (current_element[0]) { |
108 | 0 | col_append_sep_str(pinfo->cinfo, COL_INFO, NULL, current_element[0]); |
109 | 0 | col_set_fence(pinfo->cinfo, COL_INFO); |
110 | |
|
111 | 0 | ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_messagetype, tvb, 0, 0, current_element[0]); |
112 | 0 | proto_item_set_generated(ti); |
113 | 0 | } |
114 | 0 | else { |
115 | 0 | expert_add_info(pinfo, namespace_tree, &ei_sparkplugb_missing_messagetype); |
116 | 0 | g_strfreev(topic_elements); |
117 | 0 | return 0; |
118 | 0 | } |
119 | | |
120 | 0 | current_element += 1; |
121 | 0 | if (current_element[0]) { |
122 | 0 | ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_edgenodeid, tvb, 0, 0, current_element[0]); |
123 | 0 | proto_item_set_generated(ti); |
124 | 0 | } |
125 | 0 | else { |
126 | 0 | expert_add_info(pinfo, namespace_tree, &ei_sparkplugb_missing_edgenodeid); |
127 | 0 | g_strfreev(topic_elements); |
128 | 0 | return 0; |
129 | 0 | } |
130 | | |
131 | | /* Device ID is optional */ |
132 | 0 | current_element += 1; |
133 | 0 | if (current_element[0]) { |
134 | 0 | ti = proto_tree_add_string(namespace_tree, hf_sparkplugb_deviceid, tvb, 0, 0, current_element[0]); |
135 | 0 | proto_item_set_generated(ti); |
136 | 0 | } |
137 | |
|
138 | 0 | g_strfreev(topic_elements); |
139 | | |
140 | | /* Now handoff the Payload message to the protobuf dissector */ |
141 | 0 | call_dissector_with_data(protobuf_handle, tvb, pinfo, sparkplugb_tree, "message,com.cirruslink.sparkplug.protobuf.Payload"); |
142 | |
|
143 | 0 | return tvb_captured_length(tvb); |
144 | 0 | } |
145 | | |
146 | | static bool dissect_sparkplugb_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree, void *data) |
147 | 9 | { |
148 | 9 | return (dissect_sparkplugb(tvb, pinfo, parent_tree, data)>0); |
149 | 9 | } |
150 | | |
151 | | void proto_register_sparkplug(void) |
152 | 16 | { |
153 | 16 | expert_module_t* expert_sparkplugb; |
154 | | |
155 | 16 | static int *ett[] = { |
156 | 16 | &ett_sparkplugb, |
157 | 16 | &ett_sparkplugb_namespace |
158 | 16 | }; |
159 | | |
160 | 16 | static hf_register_info hf[] = { |
161 | 16 | {&hf_sparkplugb_namespace, {"Namespace", "sparkplugb.namespace", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}}, |
162 | 16 | {&hf_sparkplugb_groupid, {"Group ID", "sparkplugb.groupid", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}}, |
163 | 16 | {&hf_sparkplugb_messagetype, {"Message Type", "sparkplugb.messagetype", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}}, |
164 | 16 | {&hf_sparkplugb_edgenodeid, {"Edge Node ID", "sparkplugb.edgenodeid", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}}, |
165 | 16 | {&hf_sparkplugb_deviceid, {"Device ID", "sparkplugb.deviceid", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL}}, |
166 | 16 | }; |
167 | | |
168 | 16 | static ei_register_info ei[] = { |
169 | 16 | { &ei_sparkplugb_missing_groupid, { "sparkplugb.missing_groupid", PI_MALFORMED, PI_ERROR, "Missing Group ID", EXPFILL }}, |
170 | 16 | { &ei_sparkplugb_missing_messagetype, { "sparkplugb.missing_messagetype", PI_MALFORMED, PI_ERROR, "Missing Message Type", EXPFILL }}, |
171 | 16 | { &ei_sparkplugb_missing_edgenodeid, { "sparkplugb.missing_edgenodeid", PI_MALFORMED, PI_ERROR, "Missing Edge Node ID", EXPFILL }}, |
172 | 16 | }; |
173 | | |
174 | | /* Register the protocol name and description, fields and trees */ |
175 | 16 | proto_sparkplugb = proto_register_protocol("SparkplugB", "SparkplugB", "sparkplugb"); |
176 | 16 | register_dissector("sparkplugb", dissect_sparkplugb, proto_sparkplugb); |
177 | | |
178 | 16 | proto_register_field_array(proto_sparkplugb, hf, array_length(hf)); |
179 | 16 | proto_register_subtree_array(ett, array_length(ett)); |
180 | | |
181 | 16 | expert_sparkplugb = expert_register_protocol(proto_sparkplugb); |
182 | 16 | expert_register_field_array(expert_sparkplugb, ei, array_length(ei)); |
183 | 16 | } |
184 | | |
185 | | void proto_reg_handoff_sparkplug(void) |
186 | 16 | { |
187 | 16 | protobuf_handle = find_dissector_add_dependency("protobuf", proto_sparkplugb); |
188 | | |
189 | | /* register as heuristic dissector with MQTT */ |
190 | 16 | heur_dissector_add("mqtt.topic", dissect_sparkplugb_heur, "SparkplugB over MQTT", |
191 | 16 | "sparkplugb_mqtt", proto_sparkplugb, HEURISTIC_ENABLE); |
192 | 16 | } |