/src/wireshark/epan/dissectors/packet-matter.c
Line | Count | Source |
1 | | /* packet-matter.c |
2 | | * Routines for Matter IoT protocol dissection |
3 | | * Copyright 2023, Nicolás Alvarez <nicolas.alvarez@gmail.com> |
4 | | * Copyright 2024, Arkadiusz Bokowy <a.bokowy@samsung.com> |
5 | | * |
6 | | * Wireshark - Network traffic analyzer |
7 | | * By Gerald Combs <gerald@wireshark.org> |
8 | | * Copyright 1998 Gerald Combs |
9 | | * |
10 | | * SPDX-License-Identifier: GPL-2.0-or-later |
11 | | */ |
12 | | |
13 | | /* |
14 | | * The Matter protocol provides an interoperable application |
15 | | * layer solution for smart home devices over IPv6. |
16 | | * |
17 | | * The specification can be freely requested at: |
18 | | * https://csa-iot.org/developer-resource/specifications-download-request/ |
19 | | * |
20 | | * Comments below reference section numbers of the Matter Core Specification R1.0 (22-27349-001). |
21 | | * |
22 | | * Matter-TLV dissector is based on Matter Specification Version 1.3. |
23 | | */ |
24 | | |
25 | | #include <config.h> |
26 | | |
27 | | #include <epan/expert.h> |
28 | | #include <epan/packet.h> |
29 | | #include <wsutil/array.h> |
30 | | |
31 | | /* Prototypes */ |
32 | | /* (Required to prevent [-Wmissing-prototypes] warnings */ |
33 | | void proto_reg_handoff_matter(void); |
34 | | void proto_register_matter(void); |
35 | | |
36 | | /* Initialize the protocol and registered fields */ |
37 | | static dissector_handle_t matter_handle; |
38 | | |
39 | | static int proto_matter; |
40 | | static int hf_message_flags; |
41 | | static int hf_message_version; |
42 | | static int hf_message_has_source; |
43 | | static int hf_message_dsiz; |
44 | | static int hf_message_session_id; |
45 | | static int hf_message_security_flags; |
46 | | static int hf_message_flag_privacy; |
47 | | static int hf_message_flag_control; |
48 | | static int hf_message_flag_extensions; |
49 | | static int hf_message_session_type; |
50 | | static int hf_message_counter; |
51 | | static int hf_message_src_id; |
52 | | static int hf_message_dest_node_id; |
53 | | static int hf_message_dest_group_id; |
54 | | static int hf_message_privacy_header; |
55 | | |
56 | | static int hf_payload; |
57 | | static int hf_payload_mic; |
58 | | static int hf_payload_exchange_flags; |
59 | | static int hf_payload_flag_initiator; |
60 | | static int hf_payload_flag_ack; |
61 | | static int hf_payload_flag_reliability; |
62 | | static int hf_payload_flag_secured_extensions; |
63 | | static int hf_payload_flag_vendor; |
64 | | static int hf_payload_protocol_opcode; |
65 | | static int hf_payload_exchange_id; |
66 | | static int hf_payload_protocol_vendor_id; |
67 | | static int hf_payload_protocol_id; |
68 | | static int hf_payload_ack_counter; |
69 | | static int hf_payload_secured_ext_length; |
70 | | static int hf_payload_secured_ext; |
71 | | static int hf_payload_application; |
72 | | |
73 | | static int hf_matter_tlv_elem; |
74 | | static int hf_matter_tlv_elem_control; |
75 | | static int hf_matter_tlv_elem_control_tag_format; |
76 | | static int hf_matter_tlv_elem_control_element_type; |
77 | | static int hf_matter_tlv_elem_tag; |
78 | | static int hf_matter_tlv_elem_length; |
79 | | static int hf_matter_tlv_elem_value_int; |
80 | | static int hf_matter_tlv_elem_value_uint; |
81 | | static int hf_matter_tlv_elem_value_bytes; |
82 | | |
83 | | static int ett_matter; |
84 | | static int ett_message_flags; |
85 | | static int ett_security_flags; |
86 | | static int ett_payload; |
87 | | static int ett_exchange_flags; |
88 | | |
89 | | static int ett_matter_tlv; |
90 | | static int ett_matter_tlv_control; |
91 | | |
92 | | static expert_field ei_matter_tlv_unsupported_control; |
93 | | |
94 | | /* message flags + session ID + security flags + counter */ |
95 | 0 | #define MATTER_MIN_LENGTH 8 |
96 | | |
97 | | // Section 3.6 |
98 | 0 | #define CRYPTO_AEAD_MIC_LENGTH 16 |
99 | | |
100 | | // Section 4.4.1.2 |
101 | 14 | #define MESSAGE_FLAG_VERSION_MASK 0xF0 |
102 | 14 | #define MESSAGE_FLAG_HAS_SOURCE 0x04 |
103 | 0 | #define MESSAGE_FLAG_HAS_DEST_NODE 0x01 |
104 | 0 | #define MESSAGE_FLAG_HAS_DEST_GROUP 0x02 |
105 | 14 | #define MESSAGE_FLAG_DSIZ_MASK 0x03 |
106 | | |
107 | | // Section 4.4.1.4 |
108 | 14 | #define SECURITY_FLAG_HAS_PRIVACY 0x80 |
109 | 14 | #define SECURITY_FLAG_IS_CONTROL 0x40 |
110 | 14 | #define SECURITY_FLAG_HAS_EXTENSIONS 0x20 |
111 | 14 | #define SECURITY_FLAG_SESSION_TYPE_MASK 0x03 |
112 | 0 | #define SECURITY_FLAG_SESSION_TYPE_UNICAST 0 |
113 | 0 | #define SECURITY_FLAG_SESSION_TYPE_GROUP 1 |
114 | | |
115 | | // Section 4.4.3.1 |
116 | 14 | #define EXCHANGE_FLAG_IS_INITIATOR 0x01 |
117 | 14 | #define EXCHANGE_FLAG_ACK_MSG 0x02 |
118 | 14 | #define EXCHANGE_FLAG_RELIABILITY 0x04 |
119 | 14 | #define EXCHANGE_FLAG_HAS_SECURED_EXT 0x08 |
120 | 14 | #define EXCHANGE_FLAG_HAS_VENDOR_PROTO 0x10 |
121 | | |
122 | | static const value_string dsiz_vals[] = { |
123 | | { 0, "Not present" }, |
124 | | { MESSAGE_FLAG_HAS_DEST_NODE, "64-bit Node ID" }, |
125 | | { MESSAGE_FLAG_HAS_DEST_GROUP, "16-bit Group ID" }, |
126 | | { 0, NULL } |
127 | | }; |
128 | | |
129 | | static const value_string session_type_vals[] = { |
130 | | { 0, "Unicast Session" }, |
131 | | { 1, "Group Session" }, |
132 | | { 0, NULL } |
133 | | }; |
134 | | |
135 | | // Appendix 7.2. Tag Control Field |
136 | | static const value_string matter_tlv_tag_format_vals[] = { |
137 | | { 0, "Anonymous Tag Form, 0 octets" }, |
138 | | { 1, "Context-specific Tag Form, 1 octet" }, |
139 | | { 2, "Common Profile Tag Form, 2 octets" }, |
140 | | { 3, "Common Profile Tag Form, 4 octets" }, |
141 | | { 4, "Implicit Profile Tag Form, 2 octets" }, |
142 | | { 5, "Implicit Profile Tag Form, 4 octets" }, |
143 | | { 6, "Fully-qualified Tag Form, 6 octets" }, |
144 | | { 7, "Fully-qualified Tag Form, 8 octets" }, |
145 | | { 0, NULL } |
146 | | }; |
147 | | |
148 | | // Appendix 7.1. Element Type Field |
149 | | static const value_string matter_tlv_elem_type_vals[] = { |
150 | | { 0x00, "Signed Integer, 1-octet value" }, |
151 | | { 0x01, "Signed Integer, 2-octet value" }, |
152 | | { 0x02, "Signed Integer, 4-octet value" }, |
153 | | { 0x03, "Signed Integer, 8-octet value" }, |
154 | | { 0x04, "Unsigned Integer, 1-octet value" }, |
155 | | { 0x05, "Unsigned Integer, 2-octet value" }, |
156 | | { 0x06, "Unsigned Integer, 4-octet value" }, |
157 | | { 0x07, "Unsigned Integer, 8-octet value" }, |
158 | | { 0x08, "Boolean False" }, |
159 | | { 0x09, "Boolean True" }, |
160 | | { 0x0A, "Floating Point Number, 4-octet value" }, |
161 | | { 0x0B, "Floating Point Number, 8-octet value" }, |
162 | | { 0x0C, "UTF-8 String, 1-octet length" }, |
163 | | { 0x0D, "UTF-8 String, 2-octet length" }, |
164 | | { 0x0E, "UTF-8 String, 4-octet length" }, |
165 | | { 0x0F, "UTF-8 String, 8-octet length" }, |
166 | | { 0x10, "Octet String, 1-octet length" }, |
167 | | { 0x11, "Octet String, 2-octet length" }, |
168 | | { 0x12, "Octet String, 4-octet length" }, |
169 | | { 0x13, "Octet String, 8-octet length" }, |
170 | | { 0x14, "Null" }, |
171 | | { 0x15, "Structure" }, |
172 | | { 0x16, "Array" }, |
173 | | { 0x17, "List" }, |
174 | | // XXX: If the Tag Control Field is set to 0x00 (Anonymous Tag), the |
175 | | // value of 0x18 means "End of Container". For other Tag Control |
176 | | // Field values, the value of 0x18 is reserved. |
177 | | // TODO: This should be handled in the dissector. |
178 | | { 0x18, "End of Container" }, |
179 | | { 0x19, "Reserved" }, |
180 | | { 0x1A, "Reserved" }, |
181 | | { 0x1B, "Reserved" }, |
182 | | { 0x1C, "Reserved" }, |
183 | | { 0x1D, "Reserved" }, |
184 | | { 0x1E, "Reserved" }, |
185 | | { 0x1F, "Reserved" }, |
186 | | { 0, NULL } |
187 | | }; |
188 | | |
189 | | static int |
190 | | dissect_matter_payload(tvbuff_t *tvb, packet_info *pinfo, proto_tree *pl_tree); |
191 | | |
192 | | static int |
193 | | dissect_matter(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) |
194 | 0 | { |
195 | 0 | proto_item *ti; |
196 | 0 | proto_tree *matter_tree; |
197 | 0 | uint32_t offset = 0; |
198 | | |
199 | | /* info extracted from the packet */ |
200 | 0 | bool is_unsecured_session; |
201 | 0 | uint8_t message_flags = 0; |
202 | 0 | uint8_t security_flags = 0; |
203 | 0 | uint8_t message_dsiz = 0; |
204 | 0 | uint8_t message_session_type = 0; |
205 | 0 | uint32_t session_id = 0; |
206 | | |
207 | | /* Check that the packet is long enough for it to belong to us. */ |
208 | 0 | if (tvb_reported_length(tvb) < MATTER_MIN_LENGTH) |
209 | 0 | return 0; |
210 | | |
211 | 0 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "Matter"); |
212 | | |
213 | | /* create display subtree for the protocol */ |
214 | 0 | ti = proto_tree_add_item(tree, proto_matter, tvb, 0, -1, ENC_NA); |
215 | |
|
216 | 0 | matter_tree = proto_item_add_subtree(ti, ett_matter); |
217 | |
|
218 | 0 | static int* const message_flag_fields[] = { |
219 | 0 | &hf_message_version, |
220 | 0 | &hf_message_has_source, |
221 | 0 | &hf_message_dsiz, |
222 | 0 | NULL |
223 | 0 | }; |
224 | 0 | static int* const message_secflag_fields[] = { |
225 | 0 | &hf_message_flag_privacy, |
226 | 0 | &hf_message_flag_control, |
227 | 0 | &hf_message_flag_extensions, |
228 | 0 | &hf_message_session_type, |
229 | 0 | NULL |
230 | 0 | }; |
231 | | |
232 | | // Section 4.4.1.2 |
233 | 0 | proto_tree_add_bitmask(matter_tree, tvb, offset, hf_message_flags, ett_message_flags, message_flag_fields, ENC_LITTLE_ENDIAN); |
234 | 0 | message_flags = tvb_get_uint8(tvb, offset); |
235 | 0 | message_dsiz = (message_flags & MESSAGE_FLAG_DSIZ_MASK); |
236 | 0 | offset += 1; |
237 | | |
238 | | // Section 4.4.1.3 |
239 | 0 | proto_tree_add_item_ret_uint(matter_tree, hf_message_session_id, tvb, offset, 2, ENC_LITTLE_ENDIAN, &session_id); |
240 | 0 | offset += 2; |
241 | | |
242 | | // Section 4.4.1.4 |
243 | 0 | proto_tree_add_bitmask(matter_tree, tvb, offset, hf_message_security_flags, ett_security_flags, message_secflag_fields, ENC_LITTLE_ENDIAN); |
244 | 0 | security_flags = tvb_get_uint8(tvb, offset); |
245 | 0 | message_session_type = (security_flags & SECURITY_FLAG_SESSION_TYPE_MASK); |
246 | 0 | offset += 1; |
247 | | |
248 | | // Section 4.4.1.4: "The Unsecured Session SHALL be indicated |
249 | | // when both Session Type and Session ID are set to 0." |
250 | 0 | is_unsecured_session = (message_session_type == SECURITY_FLAG_SESSION_TYPE_UNICAST && session_id == 0); |
251 | |
|
252 | 0 | if (is_unsecured_session) |
253 | 0 | col_set_str(pinfo->cinfo, COL_INFO, "Unsecured Session"); |
254 | 0 | else if (message_session_type == SECURITY_FLAG_SESSION_TYPE_UNICAST) |
255 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, "Unicast Session [0x%04x]", session_id); |
256 | 0 | else if (message_session_type == SECURITY_FLAG_SESSION_TYPE_GROUP) |
257 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, "Group Session [0x%04x]", session_id); |
258 | | |
259 | | // decryption of message privacy is not yet supported, |
260 | | // but add an opaque field with the encrypted blob |
261 | | // Section 4.8.3 |
262 | 0 | if (security_flags & SECURITY_FLAG_HAS_PRIVACY) { |
263 | |
|
264 | 0 | uint32_t privacy_header_length = 4; |
265 | 0 | if (message_flags & MESSAGE_FLAG_HAS_SOURCE) { |
266 | 0 | privacy_header_length += 8; |
267 | 0 | } |
268 | 0 | if (message_dsiz == MESSAGE_FLAG_HAS_DEST_NODE) { |
269 | 0 | privacy_header_length += 8; |
270 | 0 | } else if (message_dsiz == MESSAGE_FLAG_HAS_DEST_GROUP) { |
271 | 0 | privacy_header_length += 2; |
272 | 0 | } |
273 | 0 | proto_tree_add_bytes_format(matter_tree, hf_message_privacy_header, tvb, offset, privacy_header_length, NULL, "Encrypted Headers"); |
274 | 0 | offset += privacy_header_length; |
275 | |
|
276 | 0 | } else { |
277 | | |
278 | | // Section 4.4.1.5 |
279 | 0 | unsigned int message_counter; |
280 | 0 | proto_tree_add_item_ret_uint(matter_tree, hf_message_counter, tvb, offset, 4, ENC_LITTLE_ENDIAN, &message_counter); |
281 | 0 | col_append_fstr(pinfo->cinfo, COL_INFO, ": Counter=%u", message_counter); |
282 | 0 | offset += 4; |
283 | | |
284 | | // Section 4.4.1.6 |
285 | 0 | if (message_flags & MESSAGE_FLAG_HAS_SOURCE) { |
286 | 0 | uint64_t node_id; |
287 | 0 | proto_tree_add_item_ret_uint64(matter_tree, hf_message_src_id, tvb, offset, 8, ENC_LITTLE_ENDIAN, &node_id); |
288 | 0 | col_append_fstr(pinfo->cinfo, COL_INFO, " Src=0x%016" PRIx64, node_id); |
289 | 0 | offset += 8; |
290 | 0 | } |
291 | | |
292 | | // Section 4.4.1.7 |
293 | 0 | if (message_dsiz == MESSAGE_FLAG_HAS_DEST_NODE) { |
294 | 0 | uint64_t node_id; |
295 | 0 | proto_tree_add_item_ret_uint64(matter_tree, hf_message_dest_node_id, tvb, offset, 8, ENC_LITTLE_ENDIAN, &node_id); |
296 | 0 | col_append_fstr(pinfo->cinfo, COL_INFO, " Dest=0x%016" PRIx64, node_id); |
297 | 0 | offset += 8; |
298 | 0 | } else if (message_dsiz == MESSAGE_FLAG_HAS_DEST_GROUP) { |
299 | 0 | unsigned int group_id; |
300 | 0 | proto_tree_add_item_ret_uint(matter_tree, hf_message_dest_group_id, tvb, offset, 2, ENC_LITTLE_ENDIAN, &group_id); |
301 | 0 | col_append_fstr(pinfo->cinfo, COL_INFO, " Group=0x%04x", group_id); |
302 | 0 | offset += 2; |
303 | 0 | } |
304 | |
|
305 | 0 | } |
306 | |
|
307 | 0 | if (is_unsecured_session) { |
308 | 0 | proto_item *payload_item = proto_tree_add_none_format(matter_tree, hf_payload, tvb, offset, -1, "Protocol Payload"); |
309 | 0 | proto_tree *payload_tree = proto_item_add_subtree(payload_item, ett_payload); |
310 | 0 | tvbuff_t *next_tvb = tvb_new_subset_remaining(tvb, offset); |
311 | |
|
312 | 0 | offset += dissect_matter_payload(next_tvb, pinfo, payload_tree); |
313 | 0 | } else { |
314 | | // Secured sessions not yet supported in the dissector. |
315 | 0 | uint32_t payload_length = tvb_reported_length_remaining(tvb, offset) - CRYPTO_AEAD_MIC_LENGTH; |
316 | 0 | proto_tree_add_none_format(matter_tree, hf_payload, tvb, offset, payload_length, "Encrypted Payload (%u bytes)", payload_length); |
317 | 0 | proto_tree_add_item(matter_tree, hf_payload_mic, tvb, offset + payload_length, CRYPTO_AEAD_MIC_LENGTH, ENC_NA); |
318 | 0 | } |
319 | |
|
320 | 0 | return offset; |
321 | 0 | } |
322 | | |
323 | | static int |
324 | | dissect_matter_payload(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *pl_tree) |
325 | 0 | { |
326 | 0 | uint32_t offset = 0; |
327 | |
|
328 | 0 | uint8_t exchange_flags = 0; |
329 | |
|
330 | 0 | static int* const exchange_flag_fields[] = { |
331 | 0 | &hf_payload_flag_initiator, |
332 | 0 | &hf_payload_flag_ack, |
333 | 0 | &hf_payload_flag_reliability, |
334 | 0 | &hf_payload_flag_secured_extensions, |
335 | 0 | &hf_payload_flag_vendor, |
336 | 0 | NULL |
337 | 0 | }; |
338 | | // Section 4.4.3.1 |
339 | 0 | proto_tree_add_bitmask(pl_tree, tvb, offset, hf_payload_exchange_flags, ett_exchange_flags, exchange_flag_fields, ENC_LITTLE_ENDIAN); |
340 | 0 | exchange_flags = tvb_get_uint8(tvb, offset); |
341 | 0 | offset += 1; |
342 | | |
343 | | // Section 4.4.3.2 |
344 | 0 | proto_tree_add_item(pl_tree, hf_payload_protocol_opcode, tvb, offset, 1, ENC_LITTLE_ENDIAN); |
345 | 0 | offset += 1; |
346 | | |
347 | | // Section 4.4.3.3 |
348 | 0 | proto_tree_add_item(pl_tree, hf_payload_exchange_id, tvb, offset, 2, ENC_LITTLE_ENDIAN); |
349 | 0 | offset += 2; |
350 | |
|
351 | 0 | if (exchange_flags & EXCHANGE_FLAG_HAS_VENDOR_PROTO) { |
352 | | // NOTE: The Matter specification R1.0 (22-27349) section 4.4 says |
353 | | // the Vendor ID comes after the Protocol ID. However, the SDK |
354 | | // implementation expects and produces the vendor ID first and the |
355 | | // protocol ID afterwards. This was reported, and the maintainers |
356 | | // declared it a bug in the *specification*, which will be resolved |
357 | | // in a future version: |
358 | | // https://github.com/project-chip/connectedhomeip/issues/25003 |
359 | | // So we parse Vendor ID first, contrary to the current spec. |
360 | 0 | proto_tree_add_item(pl_tree, hf_payload_protocol_vendor_id, tvb, offset, 2, ENC_LITTLE_ENDIAN); |
361 | 0 | offset += 2; |
362 | 0 | } |
363 | | |
364 | | // Section 4.4.3.4 |
365 | 0 | proto_tree_add_item(pl_tree, hf_payload_protocol_id, tvb, offset, 2, ENC_LITTLE_ENDIAN); |
366 | 0 | offset += 2; |
367 | | |
368 | | // Section 4.4.3.6 |
369 | 0 | if (exchange_flags & EXCHANGE_FLAG_ACK_MSG) { |
370 | 0 | unsigned int ack_counter; |
371 | 0 | proto_tree_add_item_ret_uint(pl_tree, hf_payload_ack_counter, tvb, offset, 4, ENC_LITTLE_ENDIAN, &ack_counter); |
372 | 0 | col_append_fstr(pinfo->cinfo, COL_INFO, " AckCounter=%u", ack_counter); |
373 | 0 | offset += 4; |
374 | 0 | } |
375 | | |
376 | | // Section 4.4.3.7 |
377 | 0 | if (exchange_flags & EXCHANGE_FLAG_HAS_SECURED_EXT) { |
378 | 0 | uint32_t secured_ext_len = 0; |
379 | 0 | proto_tree_add_item_ret_uint(pl_tree, hf_payload_secured_ext_length, tvb, offset, 2, ENC_LITTLE_ENDIAN, &secured_ext_len); |
380 | 0 | offset += 2; |
381 | 0 | proto_tree_add_item(pl_tree, hf_payload_secured_ext, tvb, offset, secured_ext_len, ENC_NA); |
382 | 0 | offset += secured_ext_len; |
383 | 0 | } |
384 | 0 | uint32_t application_length = tvb_reported_length_remaining(tvb, offset); |
385 | 0 | proto_tree_add_bytes_format(pl_tree, hf_payload_application, tvb, offset, application_length, NULL, "Application payload (%u bytes)", application_length); |
386 | 0 | offset += application_length; |
387 | 0 | return offset; |
388 | 0 | } |
389 | | |
390 | | // Dissect the Matter-defined TLV encoding. |
391 | | // Appendix A: Tag-length-value (TLV) Encoding Format |
392 | | static int |
393 | | // NOLINTNEXTLINE(misc-no-recursion) |
394 | | dissect_matter_tlv(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) |
395 | 0 | { |
396 | | // For signed and unsigned integer types and for UTF-8 and octet strings, |
397 | | // the length is encoded in the lowest 2 bits of the control byte. |
398 | 0 | static const int elem_sizes[] = { 1, 2, 4, 8 }; |
399 | |
|
400 | 0 | int matter_tlv_elem_tag = hf_matter_tlv_elem_tag; |
401 | 0 | int length = tvb_reported_length_remaining(tvb, 0); |
402 | 0 | int offset = 0; |
403 | |
|
404 | 0 | if (data != NULL) |
405 | | // Use caller-provided tag field. |
406 | 0 | matter_tlv_elem_tag = *((int *)data); |
407 | |
|
408 | 0 | while (offset < length) { |
409 | | |
410 | | // The new element is created with initial length set to 1 which accounts |
411 | | // for the control byte (tag format and element type). The length will be |
412 | | // updated once the element is fully dissected. |
413 | 0 | proto_item *ti_element = proto_tree_add_item(tree, hf_matter_tlv_elem, tvb, offset, 1, ENC_NA); |
414 | 0 | proto_tree *tree_element = proto_item_add_subtree(ti_element, ett_matter_tlv); |
415 | 0 | int base_offset = offset; |
416 | |
|
417 | 0 | uint32_t control_tag_format = 0; |
418 | 0 | uint32_t control_element = 0; |
419 | |
|
420 | 0 | proto_item *ti_control = proto_tree_add_item(tree_element, hf_matter_tlv_elem_control, tvb, offset, 1, ENC_NA); |
421 | 0 | proto_tree *tree_control = proto_item_add_subtree(ti_control, ett_matter_tlv_control); |
422 | | // The tag format is determined by the upper 3 bits of the control byte. |
423 | 0 | proto_tree_add_item_ret_uint(tree_control, hf_matter_tlv_elem_control_tag_format, tvb, offset, 1, ENC_NA, &control_tag_format); |
424 | | // The element type is determined by the lower 5 bits of the control byte. |
425 | 0 | proto_tree_add_item_ret_uint(tree_control, hf_matter_tlv_elem_control_element_type, tvb, offset, 1, ENC_NA, &control_element); |
426 | |
|
427 | 0 | offset += 1; |
428 | |
|
429 | 0 | proto_item_append_text(ti_element, ": %s", val_to_str_const(control_element, matter_tlv_elem_type_vals, "Unknown")); |
430 | | |
431 | | // The control byte 0x18 means "End of Container". |
432 | 0 | if (control_tag_format == 0 && control_element == 0x18) |
433 | 0 | return offset; |
434 | | |
435 | 0 | switch (control_tag_format) |
436 | 0 | { |
437 | 0 | case 0: // Anonymous Tag Form (0 octets) |
438 | 0 | break; |
439 | 0 | case 1: // Context-specific Tag Form (1 octet) |
440 | 0 | proto_tree_add_item(tree_element, matter_tlv_elem_tag, tvb, offset, 1, ENC_NA); |
441 | 0 | offset += 1; |
442 | 0 | break; |
443 | 0 | default: |
444 | 0 | goto unsupported_control; |
445 | 0 | } |
446 | | |
447 | | // The string length might be encoded on 1, 2, 4 or 8 octets. In theory, |
448 | | // the length can be up to 2^64 - 1 bytes, but in practice, it should be |
449 | | // limited to a reasonable value (it should be safe to assume that the |
450 | | // length will not exceed 2^16 - 1 bytes). |
451 | 0 | uint64_t str_length; |
452 | |
|
453 | 0 | switch (control_element) |
454 | 0 | { |
455 | 0 | case 0x00: // Signed Integer, 1-octet value |
456 | 0 | case 0x01: // Signed Integer, 2-octet value |
457 | 0 | case 0x02: // Signed Integer, 4-octet value |
458 | 0 | case 0x03: // Signed Integer, 8-octet value |
459 | 0 | case 0x04: // Unsigned Integer, 1-octet value |
460 | 0 | case 0x05: // Unsigned Integer, 2-octet value |
461 | 0 | case 0x06: // Unsigned Integer, 4-octet value |
462 | 0 | case 0x07: // Unsigned Integer, 8-octet value |
463 | 0 | { |
464 | | // Integer type (signed or unsigned) is encoded in the 3rd bit of the control element. |
465 | 0 | int hf = (control_element & 0x04) ? hf_matter_tlv_elem_value_uint : hf_matter_tlv_elem_value_int; |
466 | 0 | int size = elem_sizes[control_element & 0x03]; |
467 | 0 | proto_tree_add_item(tree_element, hf, tvb, offset, size, ENC_LITTLE_ENDIAN); |
468 | 0 | offset += size; |
469 | 0 | break; |
470 | 0 | } |
471 | 0 | case 0x08: // Boolean False |
472 | 0 | case 0x09: // Boolean True |
473 | 0 | break; |
474 | 0 | case 0x10: // Octet String (1-octet length) |
475 | 0 | case 0x11: // Octet String (2-octet length) |
476 | 0 | case 0x12: // Octet String (4-octet length) |
477 | 0 | case 0x13: // Octet String (8-octet length) |
478 | 0 | { |
479 | 0 | int size = elem_sizes[control_element & 0x03]; |
480 | 0 | proto_tree_add_item_ret_uint64(tree_element, hf_matter_tlv_elem_length, tvb, offset, size, ENC_LITTLE_ENDIAN, &str_length); |
481 | 0 | offset += size; |
482 | 0 | proto_tree_add_item(tree_element, hf_matter_tlv_elem_value_bytes, tvb, offset, (int)str_length, ENC_NA); |
483 | 0 | offset += (int)str_length; |
484 | 0 | break; |
485 | 0 | } |
486 | 0 | case 0x14: // Null |
487 | 0 | break; |
488 | 0 | case 0x15: // Structure |
489 | 0 | case 0x16: // Array |
490 | 0 | case 0x17: // List |
491 | 0 | offset += dissect_matter_tlv(tvb_new_subset_remaining(tvb, offset), pinfo, tree_element, data); |
492 | 0 | break; |
493 | 0 | default: |
494 | 0 | goto unsupported_control; |
495 | 0 | } |
496 | | |
497 | 0 | proto_item_set_len(ti_element, offset - base_offset); |
498 | 0 | continue; |
499 | | |
500 | 0 | unsupported_control: |
501 | 0 | expert_add_info(pinfo, tree_control, &ei_matter_tlv_unsupported_control); |
502 | 0 | proto_item_set_len(ti_element, offset - base_offset); |
503 | 0 | return length; |
504 | 0 | } |
505 | | |
506 | 0 | return length; |
507 | 0 | } |
508 | | |
509 | | void |
510 | | proto_register_matter(void) |
511 | 14 | { |
512 | 14 | static hf_register_info hf[] = { |
513 | 14 | { &hf_message_flags, |
514 | 14 | { "Message Flags", "matter.message.flags", |
515 | 14 | FT_UINT8, BASE_HEX, NULL, 0, |
516 | 14 | NULL, HFILL } |
517 | 14 | }, |
518 | 14 | { &hf_message_version, |
519 | 14 | { "Version", "matter.message.version", |
520 | 14 | FT_UINT8, BASE_DEC, NULL, MESSAGE_FLAG_VERSION_MASK, |
521 | 14 | "Message format version", HFILL } |
522 | 14 | }, |
523 | 14 | { &hf_message_has_source, |
524 | 14 | { "Has Source ID", "matter.message.has_source_id", |
525 | 14 | FT_BOOLEAN, 8, NULL, MESSAGE_FLAG_HAS_SOURCE, |
526 | 14 | "Source ID field is present", HFILL } |
527 | 14 | }, |
528 | 14 | { &hf_message_dsiz, |
529 | 14 | { "Destination ID Type", "matter.message.dsiz", |
530 | 14 | FT_UINT8, BASE_DEC, VALS(dsiz_vals), MESSAGE_FLAG_DSIZ_MASK, |
531 | 14 | "Size and meaning of the Destination Node ID field", HFILL } |
532 | 14 | }, |
533 | 14 | { &hf_message_session_id, |
534 | 14 | { "Session ID", "matter.message.session_id", |
535 | 14 | FT_UINT16, BASE_HEX, NULL, 0, |
536 | 14 | "The session associated with this message", HFILL } |
537 | 14 | }, |
538 | 14 | { &hf_message_security_flags, |
539 | 14 | { "Security Flags", "matter.message.security_flags", |
540 | 14 | FT_UINT8, BASE_HEX, NULL, 0, |
541 | 14 | "Message security flags", HFILL } |
542 | 14 | }, |
543 | 14 | { &hf_message_flag_privacy, |
544 | 14 | { "Privacy", "matter.message.has_privacy", |
545 | 14 | FT_BOOLEAN, 8, NULL, SECURITY_FLAG_HAS_PRIVACY, |
546 | 14 | "Whether the message is encoded with privacy enhancements", HFILL } |
547 | 14 | }, |
548 | 14 | { &hf_message_flag_control, |
549 | 14 | { "Control", "matter.message.is_control", |
550 | 14 | FT_BOOLEAN, 8, NULL, SECURITY_FLAG_IS_CONTROL, |
551 | 14 | "Whether this is a control message", HFILL } |
552 | 14 | }, |
553 | 14 | { &hf_message_flag_extensions, |
554 | 14 | { "Message Extensions", "matter.message.has_extensions", |
555 | 14 | FT_BOOLEAN, 8, NULL, SECURITY_FLAG_HAS_EXTENSIONS, |
556 | 14 | "Whether message extensions are present", HFILL } |
557 | 14 | }, |
558 | 14 | { &hf_message_session_type, |
559 | 14 | { "Session Type", "matter.message.session_type", |
560 | 14 | FT_UINT8, BASE_HEX, VALS(session_type_vals), SECURITY_FLAG_SESSION_TYPE_MASK, |
561 | 14 | "The type of session associated with the message", HFILL } |
562 | 14 | }, |
563 | 14 | { &hf_message_counter, |
564 | 14 | { "Message Counter", "matter.message.counter", |
565 | 14 | FT_UINT32, BASE_DEC, NULL, 0, |
566 | 14 | NULL, HFILL } |
567 | 14 | }, |
568 | 14 | { &hf_message_src_id, |
569 | 14 | { "Source Node ID", "matter.message.src_id", |
570 | 14 | FT_UINT64, BASE_HEX, NULL, 0, |
571 | 14 | "Unique identifier of the source node", HFILL } |
572 | 14 | }, |
573 | 14 | { &hf_message_dest_node_id, |
574 | 14 | { "Destination Node ID", "matter.message.dest_node_id", |
575 | 14 | FT_UINT64, BASE_HEX, NULL, 0, |
576 | 14 | "Unique identifier of the destination node", HFILL } |
577 | 14 | }, |
578 | 14 | { &hf_message_dest_group_id, |
579 | 14 | { "Destination Group ID", "matter.message.dest_group_id", |
580 | 14 | FT_UINT16, BASE_HEX, NULL, 0, |
581 | 14 | "Unique identifier of the destination group", HFILL } |
582 | 14 | }, |
583 | 14 | { &hf_message_privacy_header, |
584 | 14 | { "Encrypted header fields", "matter.message.privacy_header", |
585 | 14 | FT_BYTES, BASE_NONE, NULL, 0, |
586 | 14 | "Headers encrypted with message privacy", HFILL } |
587 | 14 | }, |
588 | 14 | { &hf_payload, |
589 | 14 | { "Payload", "matter.payload", |
590 | 14 | FT_NONE, BASE_NONE, NULL, 0, |
591 | 14 | "Message Payload", HFILL } |
592 | 14 | }, |
593 | 14 | { &hf_payload_mic, |
594 | 14 | { "Integrity Check", "matter.payload.mic", |
595 | 14 | FT_BYTES, BASE_NONE, NULL, 0, |
596 | 14 | "Message Integrity Check (MIC) for the encrypted payload", HFILL } |
597 | 14 | }, |
598 | 14 | { &hf_payload_exchange_flags, |
599 | 14 | { "Exchange Flags", "matter.payload.exchange_flags", |
600 | 14 | FT_UINT8, BASE_HEX, NULL, 0, |
601 | 14 | "Flags related to the exchange", HFILL } |
602 | 14 | }, |
603 | 14 | { &hf_payload_flag_initiator, |
604 | 14 | { "Initiator", "matter.payload.initiator", |
605 | 14 | FT_BOOLEAN, 8, NULL, EXCHANGE_FLAG_IS_INITIATOR, |
606 | 14 | "Whether the message was sent by the initiator of the exchange", HFILL } |
607 | 14 | }, |
608 | 14 | { &hf_payload_flag_ack, |
609 | 14 | { "Acknowledgement", "matter.payload.ack_msg", |
610 | 14 | FT_BOOLEAN, 8, NULL, EXCHANGE_FLAG_ACK_MSG, |
611 | 14 | "Whether the message is an acknowledgement of a previously-received message", HFILL } |
612 | 14 | }, |
613 | 14 | { &hf_payload_flag_reliability, |
614 | 14 | { "Reliability", "matter.payload.reliability", |
615 | 14 | FT_BOOLEAN, 8, NULL, EXCHANGE_FLAG_RELIABILITY, |
616 | 14 | "Whether the sender wishes to receive an acknowledgement for this message", HFILL } |
617 | 14 | }, |
618 | 14 | { &hf_payload_flag_secured_extensions, |
619 | 14 | { "Secure extensions", "matter.payload.has_secured_ext", |
620 | 14 | FT_BOOLEAN, 8, NULL, EXCHANGE_FLAG_HAS_SECURED_EXT, |
621 | 14 | "Whether this message contains Secured Extensions", HFILL } |
622 | 14 | }, |
623 | 14 | { &hf_payload_flag_vendor, |
624 | 14 | { "Has Vendor ID", "matter.payload.has_vendor_protocol", |
625 | 14 | FT_BOOLEAN, 8, NULL, EXCHANGE_FLAG_HAS_VENDOR_PROTO, |
626 | 14 | "Whether this message contains a protocol vendor ID", HFILL } |
627 | 14 | }, |
628 | 14 | { &hf_payload_protocol_opcode, |
629 | 14 | { "Protocol Opcode", "matter.payload.protocol_opcode", |
630 | 14 | FT_UINT8, BASE_HEX, NULL, 0, |
631 | 14 | "Opcode of the message (depends on Protocol ID)", HFILL } |
632 | 14 | }, |
633 | 14 | { &hf_payload_exchange_id, |
634 | 14 | { "Exchange ID", "matter.payload.exchange_id", |
635 | 14 | FT_UINT16, BASE_HEX, NULL, 0, |
636 | 14 | "The exchange to which the message belongs", HFILL } |
637 | 14 | }, |
638 | 14 | { &hf_payload_protocol_vendor_id, |
639 | 14 | { "Protocol Vendor ID", "matter.payload.protocol_vendor_id", |
640 | 14 | FT_UINT16, BASE_HEX, NULL, 0, |
641 | 14 | "Vendor ID namespace for the protocol ID", HFILL } |
642 | 14 | }, |
643 | 14 | { &hf_payload_protocol_id, |
644 | 14 | { "Protocol ID", "matter.payload.protocol_id", |
645 | 14 | FT_UINT16, BASE_HEX, NULL, 0, |
646 | 14 | "The protocol in which the Protocol Opcode of the message is defined", HFILL } |
647 | 14 | }, |
648 | 14 | { &hf_payload_ack_counter, |
649 | 14 | { "Acknowledged message counter", "matter.payload.ack_counter", |
650 | 14 | FT_UINT32, BASE_DEC, NULL, 0, |
651 | 14 | "The message counter of a previous message that is being acknowledged by this message", HFILL } |
652 | 14 | }, |
653 | 14 | { &hf_payload_secured_ext_length, |
654 | 14 | { "Secured extensions length", "matter.payload.secured_ext.length", |
655 | 14 | FT_UINT16, BASE_DEC, NULL, 0, |
656 | 14 | "Secured extensions payload length, in bytes", HFILL } |
657 | 14 | }, |
658 | 14 | { &hf_payload_secured_ext, |
659 | 14 | { "Secured extensions payload", "matter.payload.secured_ext", |
660 | 14 | FT_BYTES, BASE_NONE, NULL, 0, |
661 | 14 | NULL, HFILL } |
662 | 14 | }, |
663 | 14 | { &hf_payload_application, |
664 | 14 | { "Application payload", "matter.payload.application", |
665 | 14 | FT_BYTES, BASE_NONE, NULL, 0, |
666 | 14 | NULL, HFILL } |
667 | 14 | }, |
668 | 14 | { &hf_matter_tlv_elem, |
669 | 14 | { "TLV Element", "matter.tlv", |
670 | 14 | FT_NONE, BASE_NONE, NULL, 0x0, |
671 | 14 | "Matter-TLV Element", HFILL } |
672 | 14 | }, |
673 | 14 | { &hf_matter_tlv_elem_control, |
674 | 14 | { "Control Byte", "matter.tlv.control", |
675 | 14 | FT_UINT8, BASE_HEX, NULL, 0x0, |
676 | 14 | "Matter-TLV Control Byte", HFILL } |
677 | 14 | }, |
678 | 14 | { &hf_matter_tlv_elem_control_tag_format, |
679 | 14 | { "Tag Format", "matter.tlv.control.tag", |
680 | 14 | FT_UINT8, BASE_HEX, VALS(matter_tlv_tag_format_vals), 0xE0, |
681 | 14 | NULL, HFILL } |
682 | 14 | }, |
683 | 14 | { &hf_matter_tlv_elem_control_element_type, |
684 | 14 | { "Element Type", "matter.tlv.control.element", |
685 | 14 | FT_UINT8, BASE_HEX, VALS(matter_tlv_elem_type_vals), 0x1F, |
686 | 14 | NULL, HFILL } |
687 | 14 | }, |
688 | 14 | { &hf_matter_tlv_elem_tag, |
689 | 14 | { "Tag", "matter.tlv.tag", |
690 | 14 | FT_UINT32, BASE_HEX, NULL, 0x0, |
691 | 14 | NULL, HFILL } |
692 | 14 | }, |
693 | 14 | { &hf_matter_tlv_elem_length, |
694 | 14 | { "Length", "matter.tlv.length", |
695 | 14 | FT_UINT64, BASE_DEC, NULL, 0x0, |
696 | 14 | NULL, HFILL } |
697 | 14 | }, |
698 | 14 | { &hf_matter_tlv_elem_value_int, |
699 | 14 | { "Value", "matter.tlv.value_int", |
700 | 14 | FT_INT64, BASE_DEC, NULL, 0x0, |
701 | 14 | NULL, HFILL } |
702 | 14 | }, |
703 | 14 | { &hf_matter_tlv_elem_value_uint, |
704 | 14 | { "Value", "matter.tlv.value_uint", |
705 | 14 | FT_UINT64, BASE_DEC, NULL, 0x0, |
706 | 14 | NULL, HFILL } |
707 | 14 | }, |
708 | 14 | { &hf_matter_tlv_elem_value_bytes, |
709 | 14 | { "Value", "matter.tlv.value_bytes", |
710 | 14 | FT_BYTES, BASE_NONE, NULL, 0x0, |
711 | 14 | NULL, HFILL } |
712 | 14 | }, |
713 | 14 | }; |
714 | | |
715 | | /* Setup protocol subtree array */ |
716 | 14 | static int *ett[] = { |
717 | 14 | &ett_matter, |
718 | 14 | &ett_message_flags, |
719 | 14 | &ett_security_flags, |
720 | 14 | &ett_payload, |
721 | 14 | &ett_exchange_flags, |
722 | 14 | &ett_matter_tlv, |
723 | 14 | &ett_matter_tlv_control, |
724 | 14 | }; |
725 | | |
726 | 14 | static ei_register_info ei[] = { |
727 | 14 | { &ei_matter_tlv_unsupported_control, |
728 | 14 | { "matter.tlv.control.unsupported", PI_UNDECODED, PI_WARN, |
729 | 14 | "Unsupported Matter-TLV control byte", EXPFILL } |
730 | 14 | }, |
731 | 14 | }; |
732 | | |
733 | | /* Register the protocol name and description */ |
734 | 14 | proto_matter = proto_register_protocol("Matter", "Matter", "matter"); |
735 | 14 | matter_handle = register_dissector("matter", dissect_matter, proto_matter); |
736 | 14 | register_dissector("matter.tlv", dissect_matter_tlv, proto_matter); |
737 | | |
738 | | /* Required function calls to register the header fields and subtrees */ |
739 | 14 | proto_register_field_array(proto_matter, hf, array_length(hf)); |
740 | 14 | proto_register_subtree_array(ett, array_length(ett)); |
741 | | |
742 | 14 | expert_module_t *expert = expert_register_protocol(proto_matter); |
743 | 14 | expert_register_field_array(expert, ei, array_length(ei)); |
744 | | |
745 | 14 | } |
746 | | |
747 | | void |
748 | | proto_reg_handoff_matter(void) |
749 | 14 | { |
750 | 14 | dissector_add_for_decode_as("udp.port", matter_handle); |
751 | 14 | } |