wireshark/epan/dissectors/packet-matter.c

442 lines
17 KiB
C

/* packet-matter.c
* Routines for Matter IoT protocol dissection
* Copyright 2023, Nicolás Alvarez <nicolas.alvarez@gmail.com>
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
/*
* The Matter protocol provides an interoperable application
* layer solution for smart home devices over IPv6.
*
* The specification can be freely requested at:
* https://csa-iot.org/developer-resource/specifications-download-request/
*/
#include <config.h>
#include <epan/packet.h>
/* Prototypes */
/* (Required to prevent [-Wmissing-prototypes] warnings */
void proto_reg_handoff_matter(void);
void proto_register_matter(void);
/* Initialize the protocol and registered fields */
static dissector_handle_t matter_handle;
static int proto_matter;
static int hf_message_flags;
static int hf_message_version;
static int hf_message_has_source;
static int hf_message_dsiz;
static int hf_message_session_id;
static int hf_message_security_flags;
static int hf_message_flag_privacy;
static int hf_message_flag_control;
static int hf_message_flag_extensions;
static int hf_message_session_type;
static int hf_message_counter;
static int hf_message_src_id;
static int hf_message_dest_id;
static int hf_message_privacy_header;
static int hf_payload;
static int hf_payload_exchange_flags;
static int hf_payload_flag_initiator;
static int hf_payload_flag_ack;
static int hf_payload_flag_reliability;
static int hf_payload_flag_secured_extensions;
static int hf_payload_flag_vendor;
static int hf_payload_protocol_opcode;
static int hf_payload_exchange_id;
static int hf_payload_protocol_vendor_id;
static int hf_payload_protocol_id;
static int hf_payload_ack_counter;
static int hf_payload_secured_ext_length;
static int hf_payload_secured_ext;
static int hf_payload_application;
static gint ett_matter;
static gint ett_message_flags;
static gint ett_security_flags;
static gint ett_payload;
static gint ett_exchange_flags;
/* message flags + session ID + security flags + counter */
#define MATTER_MIN_LENGTH 8
#define MESSAGE_FLAG_VERSION_MASK 0xF0
#define MESSAGE_FLAG_HAS_SOURCE 0x04
#define MESSAGE_FLAG_HAS_DEST_NODE 0x01
#define MESSAGE_FLAG_HAS_DEST_GROUP 0x02
#define MESSAGE_FLAG_DSIZ_MASK 0x03
#define SECURITY_FLAG_HAS_PRIVACY 0x80
#define SECURITY_FLAG_IS_CONTROL 0x40
#define SECURITY_FLAG_HAS_EXTENSIONS 0x20
#define SECURITY_FLAG_SESSION_TYPE_MASK 0x03
#define EXCHANGE_FLAG_IS_INITIATOR 0x01
#define EXCHANGE_FLAG_ACK_MSG 0x02
#define EXCHANGE_FLAG_RELIABILITY 0x04
#define EXCHANGE_FLAG_HAS_SECURED_EXT 0x08
#define EXCHANGE_FLAG_HAS_VENDOR_PROTO 0x10
static const value_string dsiz_vals[] = {
{ 0, "Not present" },
{ MESSAGE_FLAG_HAS_DEST_NODE, "64-bit Node ID" },
{ MESSAGE_FLAG_HAS_DEST_GROUP, "16-bit Group ID" },
{ 0, NULL }
};
static const value_string session_type_vals[] = {
{ 0, "Unicast Session" },
{ 1, "Group Session" },
{ 0, NULL }
};
static int
dissect_matter_payload(tvbuff_t *tvb, packet_info *pinfo, proto_tree *pl_tree);
static int
dissect_matter(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
proto_item *ti;
proto_tree *matter_tree;
guint offset = 0;
/* info extracted from the packet */
guint8 message_flags = 0;
guint8 security_flags = 0;
guint8 message_dsiz = 0;
guint8 message_session_type = 0;
guint session_id = 0;
/* Check that the packet is long enough for it to belong to us. */
if (tvb_reported_length(tvb) < MATTER_MIN_LENGTH)
return 0;
col_set_str(pinfo->cinfo, COL_PROTOCOL, "Matter");
/* create display subtree for the protocol */
ti = proto_tree_add_item(tree, proto_matter, tvb, 0, -1, ENC_NA);
matter_tree = proto_item_add_subtree(ti, ett_matter);
static int* const message_flag_fields[] = {
&hf_message_version,
&hf_message_has_source,
&hf_message_dsiz,
NULL
};
static int* const message_secflag_fields[] = {
&hf_message_flag_privacy,
&hf_message_flag_control,
&hf_message_flag_extensions,
&hf_message_session_type,
NULL
};
proto_tree_add_bitmask(matter_tree, tvb, offset, hf_message_flags, ett_message_flags, message_flag_fields, ENC_LITTLE_ENDIAN);
message_flags = tvb_get_guint8(tvb, offset);
message_dsiz = (message_flags & MESSAGE_FLAG_DSIZ_MASK);
offset += 1;
proto_tree_add_item_ret_uint(matter_tree, hf_message_session_id, tvb, offset, 2, ENC_LITTLE_ENDIAN, &session_id);
offset += 2;
proto_tree_add_bitmask(matter_tree, tvb, offset, hf_message_security_flags, ett_security_flags, message_secflag_fields, ENC_LITTLE_ENDIAN);
security_flags = tvb_get_guint8(tvb, offset);
message_session_type = (security_flags & SECURITY_FLAG_SESSION_TYPE_MASK);
offset += 1;
// decryption of message privacy is not yet supported,
// but add an opaque field with the encrypted blob
if (security_flags & SECURITY_FLAG_HAS_PRIVACY) {
guint privacy_header_length = 4;
if (message_flags & MESSAGE_FLAG_HAS_SOURCE) {
privacy_header_length += 8;
}
if (message_dsiz == MESSAGE_FLAG_HAS_DEST_NODE) {
privacy_header_length += 8;
} else if (message_dsiz == MESSAGE_FLAG_HAS_DEST_GROUP) {
privacy_header_length += 2;
}
proto_tree_add_bytes_format(matter_tree, hf_message_privacy_header, tvb, offset, privacy_header_length, NULL, "Encrypted Headers");
offset += privacy_header_length;
} else {
proto_tree_add_item(matter_tree, hf_message_counter, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
if (message_flags & MESSAGE_FLAG_HAS_SOURCE) {
proto_tree_add_item(matter_tree, hf_message_src_id, tvb, offset, 8, ENC_LITTLE_ENDIAN);
offset += 8;
}
if (message_dsiz == MESSAGE_FLAG_HAS_DEST_NODE) {
proto_tree_add_item(matter_tree, hf_message_dest_id, tvb, offset, 8, ENC_LITTLE_ENDIAN);
offset += 8;
} else if (message_dsiz == MESSAGE_FLAG_HAS_DEST_GROUP) {
proto_tree_add_item(matter_tree, hf_message_dest_id, tvb, offset, 2, ENC_LITTLE_ENDIAN);
offset += 2;
}
}
// "The Unsecured Session SHALL be indicated when both Session Type and Session ID are set to 0."
// Secured sessions not yet supported.
if (message_session_type == 0 && session_id == 0) {
proto_item *payload_item = proto_tree_add_none_format(matter_tree, hf_payload, tvb, offset, -1, "Protocol Payload");
proto_tree *payload_tree = proto_item_add_subtree(payload_item, ett_payload);
tvbuff_t *next_tvb = tvb_new_subset_remaining(tvb, offset);
offset += dissect_matter_payload(next_tvb, pinfo, payload_tree);
} else {
guint payload_length = tvb_reported_length_remaining(tvb, offset);
proto_tree_add_none_format(matter_tree, hf_payload, tvb, offset, payload_length, "Encrypted Payload (%u bytes)", payload_length);
}
return offset;
}
static int
dissect_matter_payload(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *pl_tree)
{
guint offset = 0;
guint8 exchange_flags = 0;
static int* const exchange_flag_fields[] = {
&hf_payload_flag_initiator,
&hf_payload_flag_ack,
&hf_payload_flag_reliability,
&hf_payload_flag_secured_extensions,
&hf_payload_flag_vendor,
NULL
};
proto_tree_add_bitmask(pl_tree, tvb, offset, hf_payload_exchange_flags, ett_exchange_flags, exchange_flag_fields, ENC_LITTLE_ENDIAN);
exchange_flags = tvb_get_guint8(tvb, offset);
offset += 1;
proto_tree_add_item(pl_tree, hf_payload_protocol_opcode, tvb, offset, 1, ENC_LITTLE_ENDIAN);
offset += 1;
proto_tree_add_item(pl_tree, hf_payload_exchange_id, tvb, offset, 2, ENC_LITTLE_ENDIAN);
offset += 2;
if (exchange_flags & EXCHANGE_FLAG_HAS_VENDOR_PROTO) {
// NOTE: The Matter specification R1.0 (22-27349) section 4.4 says
// the Vendor ID comes after the Protocol ID. However, the SDK
// implementation expects and produces the vendor ID first and the
// protocol ID afterwards. This was reported, and the maintainers
// declared it a bug in the *specification*, which will be resolved
// in a future version:
// https://github.com/project-chip/connectedhomeip/issues/25003
// So we parse Vendor ID first, contrary to the current spec.
proto_tree_add_item(pl_tree, hf_payload_protocol_vendor_id, tvb, offset, 2, ENC_LITTLE_ENDIAN);
offset += 2;
}
proto_tree_add_item(pl_tree, hf_payload_protocol_id, tvb, offset, 2, ENC_LITTLE_ENDIAN);
offset += 2;
if (exchange_flags & EXCHANGE_FLAG_ACK_MSG) {
proto_tree_add_item(pl_tree, hf_payload_ack_counter, tvb, offset, 4, ENC_LITTLE_ENDIAN);
offset += 4;
}
if (exchange_flags & EXCHANGE_FLAG_HAS_SECURED_EXT) {
guint secured_ext_len = 0;
proto_tree_add_item_ret_uint(pl_tree, hf_payload_secured_ext_length, tvb, offset, 2, ENC_LITTLE_ENDIAN, &secured_ext_len);
offset += 2;
proto_tree_add_item(pl_tree, hf_payload_secured_ext, tvb, offset, secured_ext_len, ENC_NA);
offset += secured_ext_len;
}
guint application_length = tvb_reported_length_remaining(tvb, offset);
proto_tree_add_bytes_format(pl_tree, hf_payload_application, tvb, offset, application_length, NULL, "Application payload (%u bytes)", application_length);
offset += application_length;
return offset;
}
void
proto_register_matter(void)
{
static hf_register_info hf[] = {
{ &hf_message_flags,
{ "Message Flags", "matter.message.flags",
FT_UINT8, BASE_HEX, NULL, 0,
NULL, HFILL }
},
{ &hf_message_version,
{ "Version", "matter.message.version",
FT_UINT8, BASE_DEC, NULL, MESSAGE_FLAG_VERSION_MASK,
"Message format version", HFILL }
},
{ &hf_message_has_source,
{ "Has Source ID", "matter.message.has_source_id",
FT_BOOLEAN, 8, NULL, MESSAGE_FLAG_HAS_SOURCE,
"Source ID field is present", HFILL }
},
{ &hf_message_dsiz,
{ "Destination ID Type", "matter.message.dsiz",
FT_UINT8, BASE_DEC, VALS(dsiz_vals), MESSAGE_FLAG_DSIZ_MASK,
"Size and meaning of the Destination Node ID field", HFILL }
},
{ &hf_message_session_id,
{ "Session ID", "matter.message.session_id",
FT_UINT16, BASE_HEX, NULL, 0,
"The session associated with this message", HFILL }
},
{ &hf_message_security_flags,
{ "Security Flags", "matter.message.security_flags",
FT_UINT8, BASE_HEX, NULL, 0,
"Message security flags", HFILL }
},
{ &hf_message_flag_privacy,
{ "Privacy", "matter.message.has_privacy",
FT_BOOLEAN, 8, NULL, SECURITY_FLAG_HAS_PRIVACY,
"Whether the message is encoded with privacy enhancements", HFILL }
},
{ &hf_message_flag_control,
{ "Control", "matter.message.is_control",
FT_BOOLEAN, 8, NULL, SECURITY_FLAG_IS_CONTROL,
"Whether this is a control message", HFILL }
},
{ &hf_message_flag_extensions,
{ "Message Extensions", "matter.message.has_extensions",
FT_BOOLEAN, 8, NULL, SECURITY_FLAG_HAS_EXTENSIONS,
"Whether message extensions are present", HFILL }
},
{ &hf_message_session_type,
{ "Session Type", "matter.message.session_type",
FT_UINT8, BASE_HEX, VALS(session_type_vals), SECURITY_FLAG_SESSION_TYPE_MASK,
"The type of session associated with the message", HFILL }
},
{ &hf_message_counter,
{ "Message Counter", "matter.message.counter",
FT_UINT32, BASE_HEX, NULL, 0,
NULL, HFILL }
},
{ &hf_message_src_id,
{ "Source Node ID", "matter.message.src_id",
FT_UINT64, BASE_HEX, NULL, 0,
"Unique identifier of the source node", HFILL }
},
{ &hf_message_dest_id,
{ "Destination Node ID", "matter.message.dest_id",
FT_UINT64, BASE_HEX, NULL, 0,
"Unique identifier of the destination node or group", HFILL }
},
{ &hf_message_privacy_header,
{ "Encrypted header fields", "matter.message.privacy_header",
FT_BYTES, BASE_NONE, NULL, 0,
"Headers encrypted with message privacy", HFILL }
},
{ &hf_payload,
{ "Payload", "matter.payload",
FT_NONE, BASE_NONE, NULL, 0,
"Message Payload", HFILL }
},
{ &hf_payload_exchange_flags,
{ "Exchange Flags", "matter.payload.exchange_flags",
FT_UINT8, BASE_HEX, NULL, 0,
"Flags related to the exchange", HFILL }
},
{ &hf_payload_flag_initiator,
{ "Initiator", "matter.payload.initiator",
FT_BOOLEAN, 8, NULL, EXCHANGE_FLAG_IS_INITIATOR,
"Whether the message was sent by the initiator of the exchange", HFILL }
},
{ &hf_payload_flag_ack,
{ "Acknowledgement", "matter.payload.ack_msg",
FT_BOOLEAN, 8, NULL, EXCHANGE_FLAG_ACK_MSG,
"Whether the message is an acknowledgement of a previously-received message", HFILL }
},
{ &hf_payload_flag_reliability,
{ "Reliability", "matter.payload.reliability",
FT_BOOLEAN, 8, NULL, EXCHANGE_FLAG_RELIABILITY,
"Whether the sender wishes to receive an acknowledgement for this message", HFILL }
},
{ &hf_payload_flag_secured_extensions,
{ "Secure extensions", "matter.payload.has_secured_ext",
FT_BOOLEAN, 8, NULL, EXCHANGE_FLAG_HAS_SECURED_EXT,
"Whether this message contains Secured Extensions", HFILL }
},
{ &hf_payload_flag_vendor,
{ "Has Vendor ID", "matter.payload.has_vendor_protocol",
FT_BOOLEAN, 8, NULL, EXCHANGE_FLAG_HAS_VENDOR_PROTO,
"Whether this message contains a protocol vendor ID", HFILL }
},
{ &hf_payload_protocol_opcode,
{ "Protocol Opcode", "matter.payload.protocol_opcode",
FT_UINT8, BASE_HEX, NULL, 0,
"Opcode of the message (depends on Protocol ID)", HFILL }
},
{ &hf_payload_exchange_id,
{ "Exchange ID", "matter.payload.exchange_id",
FT_UINT16, BASE_HEX, NULL, 0,
"The exchange to which the message belongs", HFILL }
},
{ &hf_payload_protocol_vendor_id,
{ "Protocol Vendor ID", "matter.payload.protocol_vendor_id",
FT_UINT16, BASE_HEX, NULL, 0,
"Vendor ID namespace for the protocol ID", HFILL }
},
{ &hf_payload_protocol_id,
{ "Protocol ID", "matter.payload.protocol_id",
FT_UINT16, BASE_HEX, NULL, 0,
"The protocol in which the Protocol Opcode of the message is defined", HFILL }
},
{ &hf_payload_ack_counter,
{ "Acknowledged message counter", "matter.payload.ack_counter",
FT_UINT32, BASE_HEX, NULL, 0,
"The message counter of a previous message that is being acknowledged by this message", HFILL }
},
{ &hf_payload_secured_ext_length,
{ "Secured extensions length", "matter.payload.secured_ext.length",
FT_UINT16, BASE_DEC, NULL, 0,
"Secured extensions payload length, in bytes", HFILL }
},
{ &hf_payload_secured_ext,
{ "Secured extensions payload", "matter.payload.secured_ext",
FT_BYTES, BASE_NONE, NULL, 0,
NULL, HFILL }
},
{ &hf_payload_application,
{ "Application payload", "matter.payload.application",
FT_BYTES, BASE_NONE, NULL, 0,
NULL, HFILL }
}
};
/* Setup protocol subtree array */
static gint *ett[] = {
&ett_matter,
&ett_message_flags,
&ett_security_flags,
&ett_payload,
&ett_exchange_flags,
};
/* Register the protocol name and description */
proto_matter = proto_register_protocol("Matter", "Matter", "matter");
matter_handle = register_dissector("matter", dissect_matter, proto_matter);
/* Required function calls to register the header fields and subtrees */
proto_register_field_array(proto_matter, hf, array_length(hf));
proto_register_subtree_array(ett, array_length(ett));
}
void
proto_reg_handoff_matter(void)
{
dissector_add_for_decode_as("udp.port", matter_handle);
}