esp32_bluetooth_classic_sni.../libs/scapy/layers/zigbee.py

1008 lines
42 KiB
Python
Raw Normal View History

# This program is published under a GPLv2 license
# This file is part of Scapy
# See http://www.secdev.org/projects/scapy for more information
# Copyright (C) Ryan Speers <ryan@rmspeers.com> 2011-2012
# Copyright (C) Roger Meyer <roger.meyer@csus.edu>: 2012-03-10 Added frames
# Copyright (C) Gabriel Potter <gabriel@potter.fr>: 2018
# Intern at INRIA Grand Nancy Est
# This program is published under a GPLv2 license
"""
ZigBee bindings for IEEE 802.15.4.
"""
import struct
from scapy.compat import orb
from scapy.packet import bind_layers, bind_bottom_up, Packet
from scapy.fields import BitField, ByteField, XLEIntField, ConditionalField, \
ByteEnumField, EnumField, BitEnumField, FieldListField, FlagsField, \
IntField, PacketListField, ShortField, StrField, StrFixedLenField, \
StrLenField, XLEShortField, XStrField
from scapy.layers.dot15d4 import dot15d4AddressField, Dot15d4Beacon, Dot15d4, \
Dot15d4FCS
from scapy.layers.inet import UDP
from scapy.layers.ntp import TimeStampField
# ZigBee Cluster Library Identifiers, Table 2.2 ZCL
_zcl_cluster_identifier = {
# Functional Domain: General
0x0000: "basic",
0x0001: "power_configuration",
0x0002: "device_temperature_configuration",
0x0003: "identify",
0x0004: "groups",
0x0005: "scenes",
0x0006: "on_off",
0x0007: "on_off_switch_configuration",
0x0008: "level_control",
0x0009: "alarms",
0x000a: "time",
0x000b: "rssi_location",
0x000c: "analog_input",
0x000d: "analog_output",
0x000e: "analog_value",
0x000f: "binary_input",
0x0010: "binary_output",
0x0011: "binary_value",
0x0012: "multistate_input",
0x0013: "multistate_output",
0x0014: "multistate_value",
0x0015: "commissioning",
# 0x0016 - 0x00ff reserved
# Functional Domain: Closures
0x0100: "shade_configuration",
# 0x0101 - 0x01ff reserved
# Functional Domain: HVAC
0x0200: "pump_configuration_and_control",
0x0201: "thermostat",
0x0202: "fan_control",
0x0203: "dehumidification_control",
0x0204: "thermostat_user_interface_configuration",
# 0x0205 - 0x02ff reserved
# Functional Domain: Lighting
0x0300: "color_control",
0x0301: "ballast_configuration",
# Functional Domain: Measurement and sensing
0x0400: "illuminance_measurement",
0x0401: "illuminance_level_sensing",
0x0402: "temperature_measurement",
0x0403: "pressure_measurement",
0x0404: "flow_measurement",
0x0405: "relative_humidity_measurement",
0x0406: "occupancy_sensing",
# Functional Domain: Security and safethy
0x0500: "ias_zone",
0x0501: "ias_ace",
0x0502: "ias_wd",
# Functional Domain: Protocol Interfaces
0x0600: "generic_tunnel",
0x0601: "bacnet_protocol_tunnel",
0x0602: "analog_input_regular",
0x0603: "analog_input_extended",
0x0604: "analog_output_regular",
0x0605: "analog_output_extended",
0x0606: "analog_value_regular",
0x0607: "analog_value_extended",
0x0608: "binary_input_regular",
0x0609: "binary_input_extended",
0x060a: "binary_output_regular",
0x060b: "binary_output_extended",
0x060c: "binary_value_regular",
0x060d: "binary_value_extended",
0x060e: "multistate_input_regular",
0x060f: "multistate_input_extended",
0x0610: "multistate_output_regular",
0x0611: "multistate_output_extended",
0x0612: "multistate_value_regular",
0x0613: "multistate_value",
# Smart Energy Profile Clusters
0x0700: "price",
0x0701: "demand_response_and_load_control",
0x0702: "metering",
0x0703: "messaging",
0x0704: "smart_energy_tunneling",
0x0705: "prepayment",
# Functional Domain: General
# Key Establishment
0x0800: "key_establishment",
}
# ZigBee stack profiles
_zcl_profile_identifier = {
0x0000: "ZigBee_Stack_Profile_1",
0x0101: "IPM_Industrial_Plant_Monitoring",
0x0104: "HA_Home_Automation",
0x0105: "CBA_Commercial_Building_Automation",
0x0107: "TA_Telecom_Applications",
0x0108: "HC_Health_Care",
0x0109: "SE_Smart_Energy_Profile",
}
# ZigBee Cluster Library, Table 2.8 ZCL Command Frames
_zcl_command_frames = {
0x00: "read_attributes",
0x01: "read_attributes_response",
0x02: "write_attributes_response",
0x03: "write_attributes_undivided",
0x04: "write_attributes_response",
0x05: "write_attributes_no_response",
0x06: "configure_reporting",
0x07: "configure_reporting_response",
0x08: "read_reporting_configuration",
0x09: "read_reporting_configuration_response",
0x0a: "report_attributes",
0x0b: "default_response",
0x0c: "discover_attributes",
0x0d: "discover_attributes_response",
# 0x0e - 0xff Reserved
}
# ZigBee Cluster Library, Table 2.16 Enumerated Status Values
_zcl_enumerated_status_values = {
0x00: "SUCCESS",
0x02: "FAILURE",
# 0x02 - 0x7f Reserved
0x80: "MALFORMED_COMMAND",
0x81: "UNSUP_CLUSTER_COMMAND",
0x82: "UNSUP_GENERAL_COMMAND",
0x83: "UNSUP_MANUF_CLUSTER_COMMAND",
0x84: "UNSUP_MANUF_GENERAL_COMMAND",
0x85: "INVALID_FIELD",
0x86: "UNSUPPORTED_ATTRIBUTE",
0x87: "INVALID_VALUE",
0x88: "READ_ONLY",
0x89: "INSUFFICIENT_SPACE",
0x8a: "DUPLICATE_EXISTS",
0x8b: "NOT_FOUND",
0x8c: "UNREPORTABLE_ATTRIBUTE",
0x8d: "INVALID_DATA_TYPE",
# 0x8e - 0xbf Reserved
0xc0: "HARDWARE_FAILURE",
0xc1: "SOFTWARE_FAILURE",
0xc2: "CALIBRATION_ERROR",
# 0xc3 - 0xff Reserved
}
# ZigBee Cluster Library, Table 2.15 Data Types
_zcl_attribute_data_types = {
0x00: "no_data",
# General data
0x08: "8-bit_data",
0x09: "16-bit_data",
0x0a: "24-bit_data",
0x0b: "32-bit_data",
0x0c: "40-bit_data",
0x0d: "48-bit_data",
0x0e: "56-bit_data",
0x0f: "64-bit_data",
# Logical
0x10: "boolean",
# Bitmap
0x18: "8-bit_bitmap",
0x19: "16-bit_bitmap",
0x1a: "24-bit_bitmap",
0x1b: "32-bit_bitmap",
0x1c: "40-bit_bitmap",
0x1d: "48-bit_bitmap",
0x1e: "56-bit_bitmap",
0x1f: "64-bit_bitmap",
# Unsigned integer
0x20: "Unsigned_8-bit_integer",
0x21: "Unsigned_16-bit_integer",
0x22: "Unsigned_24-bit_integer",
0x23: "Unsigned_32-bit_integer",
0x24: "Unsigned_40-bit_integer",
0x25: "Unsigned_48-bit_integer",
0x26: "Unsigned_56-bit_integer",
0x27: "Unsigned_64-bit_integer",
# Signed integer
0x28: "Signed_8-bit_integer",
0x29: "Signed_16-bit_integer",
0x2a: "Signed_24-bit_integer",
0x2b: "Signed_32-bit_integer",
0x2c: "Signed_40-bit_integer",
0x2d: "Signed_48-bit_integer",
0x2e: "Signed_56-bit_integer",
0x2f: "Signed_64-bit_integer",
# Enumeration
0x30: "8-bit_enumeration",
0x31: "16-bit_enumeration",
# Floating point
0x38: "semi_precision",
0x39: "single_precision",
0x3a: "double_precision",
# String
0x41: "octet-string",
0x42: "character_string",
0x43: "long_octet_string",
0x44: "long_character_string",
# Ordered sequence
0x48: "array",
0x4c: "structure",
# Collection
0x50: "set",
0x51: "bag",
# Time
0xe0: "time_of_day",
0xe1: "date",
0xe2: "utc_time",
# Identifier
0xe8: "cluster_id",
0xe9: "attribute_id",
0xea: "bacnet_oid",
# Miscellaneous
0xf0: "ieee_address",
0xf1: "128-bit_security_key",
# Unknown
0xff: "unknown",
}
# ZigBee #
class ZigbeeNWK(Packet):
name = "Zigbee Network Layer"
fields_desc = [
BitField("discover_route", 0, 2),
BitField("proto_version", 2, 4),
BitEnumField("frametype", 0, 2, {0: 'data', 1: 'command'}),
FlagsField("flags", 0, 8, ['multicast', 'security', 'source_route', 'extended_dst', 'extended_src', 'reserved1', 'reserved2', 'reserved3']), # noqa: E501
XLEShortField("destination", 0),
XLEShortField("source", 0),
ByteField("radius", 0),
ByteField("seqnum", 1),
# ConditionalField(XLongField("ext_dst", 0), lambda pkt:pkt.flags & 8),
ConditionalField(dot15d4AddressField("ext_dst", 0, adjust=lambda pkt, x: 8), lambda pkt:pkt.flags & 8), # noqa: E501
ConditionalField(dot15d4AddressField("ext_src", 0, adjust=lambda pkt, x: 8), lambda pkt:pkt.flags & 16), # noqa: E501
ConditionalField(ByteField("relay_count", 1), lambda pkt:pkt.flags & 0x04), # noqa: E501
ConditionalField(ByteField("relay_index", 0), lambda pkt:pkt.flags & 0x04), # noqa: E501
ConditionalField(FieldListField("relays", [], XLEShortField("", 0x0000), count_from=lambda pkt:pkt.relay_count), lambda pkt:pkt.flags & 0x04), # noqa: E501
]
def guess_payload_class(self, payload):
if self.flags & 0x02:
return ZigbeeSecurityHeader
elif self.frametype == 0:
return ZigbeeAppDataPayload
elif self.frametype == 1:
return ZigbeeNWKCommandPayload
else:
return Packet.guess_payload_class(self, payload)
class LinkStatusEntry(Packet):
name = "ZigBee Link Status Entry"
fields_desc = [
# Neighbor network address (2 octets)
XLEShortField("neighbor_network_address", 0x0000),
# Link status (1 octet)
BitField("reserved1", 0, 1),
BitField("outgoing_cost", 0, 3),
BitField("reserved2", 0, 1),
BitField("incoming_cost", 0, 3),
]
class ZigbeeNWKCommandPayload(Packet):
name = "Zigbee Network Layer Command Payload"
fields_desc = [
ByteEnumField("cmd_identifier", 1, {
1: "route request",
2: "route reply",
3: "network status",
4: "leave",
5: "route record",
6: "rejoin request",
7: "rejoin response",
8: "link status",
9: "network report",
10: "network update"
# 0x0b - 0xff reserved
}),
# - Route Request Command - #
# Command options (1 octet)
ConditionalField(BitField("reserved", 0, 1), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501
ConditionalField(BitField("multicast", 0, 1), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501
ConditionalField(BitField("dest_addr_bit", 0, 1), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501
ConditionalField(
BitEnumField("many_to_one", 0, 2, {
0: "not_m2one", 1: "m2one_support_rrt", 2: "m2one_no_support_rrt", 3: "reserved"} # noqa: E501
), lambda pkt: pkt.cmd_identifier == 1),
ConditionalField(BitField("reserved", 0, 3), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501
# Route request identifier (1 octet)
ConditionalField(ByteField("route_request_identifier", 0), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501
# Destination address (2 octets)
ConditionalField(XLEShortField("destination_address", 0x0000), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501
# Path cost (1 octet)
ConditionalField(ByteField("path_cost", 0), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501
# Destination IEEE Address (0/8 octets), only present when dest_addr_bit has a value of 1 # noqa: E501
ConditionalField(dot15d4AddressField("ext_dst", 0, adjust=lambda pkt, x: 8), # noqa: E501
lambda pkt: (pkt.cmd_identifier == 1 and pkt.dest_addr_bit == 1)), # noqa: E501
# - Route Reply Command - #
# Command options (1 octet)
ConditionalField(BitField("reserved", 0, 1), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501
ConditionalField(BitField("multicast", 0, 1), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501
ConditionalField(BitField("responder_addr_bit", 0, 1), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501
ConditionalField(BitField("originator_addr_bit", 0, 1), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501
ConditionalField(BitField("reserved", 0, 4), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501
# Route request identifier (1 octet)
ConditionalField(ByteField("route_request_identifier", 0), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501
# Originator address (2 octets)
ConditionalField(XLEShortField("originator_address", 0x0000), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501
# Responder address (2 octets)
ConditionalField(XLEShortField("responder_address", 0x0000), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501
# Path cost (1 octet)
ConditionalField(ByteField("path_cost", 0), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501
# Originator IEEE address (0/8 octets)
ConditionalField(dot15d4AddressField("originator_addr", 0, adjust=lambda pkt, x: 8), # noqa: E501
lambda pkt: (pkt.cmd_identifier == 2 and pkt.originator_addr_bit == 1)), # noqa: E501
# Responder IEEE address (0/8 octets)
ConditionalField(dot15d4AddressField("responder_addr", 0, adjust=lambda pkt, x: 8), # noqa: E501
lambda pkt: (pkt.cmd_identifier == 2 and pkt.responder_addr_bit == 1)), # noqa: E501
# - Network Status Command - #
# Status code (1 octet)
ConditionalField(ByteEnumField("status_code", 0, {
0x00: "No route available",
0x01: "Tree link failure",
0x02: "Non-tree link failure",
0x03: "Low battery level",
0x04: "No routing capacity",
0x05: "No indirect capacity",
0x06: "Indirect transaction expiry",
0x07: "Target device unavailable",
0x08: "Target address unallocated",
0x09: "Parent link failure",
0x0a: "Validate route",
0x0b: "Source route failure",
0x0c: "Many-to-one route failure",
0x0d: "Address conflict",
0x0e: "Verify addresses",
0x0f: "PAN identifier update",
0x10: "Network address update",
0x11: "Bad frame counter",
0x12: "Bad key sequence number",
# 0x13 - 0xff Reserved
}), lambda pkt: pkt.cmd_identifier == 3),
# Destination address (2 octets)
ConditionalField(XLEShortField("destination_address", 0x0000), lambda pkt: pkt.cmd_identifier == 3), # noqa: E501
# - Leave Command - #
# Command options (1 octet)
# Bit 7: Remove children
ConditionalField(BitField("remove_children", 0, 1), lambda pkt: pkt.cmd_identifier == 4), # noqa: E501
# Bit 6: Request
ConditionalField(BitField("request", 0, 1), lambda pkt: pkt.cmd_identifier == 4), # noqa: E501
# Bit 5: Rejoin
ConditionalField(BitField("rejoin", 0, 1), lambda pkt: pkt.cmd_identifier == 4), # noqa: E501
# Bit 0 - 4: Reserved
ConditionalField(BitField("reserved", 0, 5), lambda pkt: pkt.cmd_identifier == 4), # noqa: E501
# - Route Record Command - #
# Relay count (1 octet)
ConditionalField(ByteField("rr_relay_count", 0), lambda pkt: pkt.cmd_identifier == 5), # noqa: E501
# Relay list (variable in length)
ConditionalField(
FieldListField("rr_relay_list", [], XLEShortField("", 0x0000), count_from=lambda pkt:pkt.rr_relay_count), # noqa: E501
lambda pkt:pkt.cmd_identifier == 5),
# - Rejoin Request Command - #
# Capability Information (1 octet)
ConditionalField(BitField("allocate_address", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Allocate Address # noqa: E501
ConditionalField(BitField("security_capability", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Security Capability # noqa: E501
ConditionalField(BitField("reserved2", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # bit 5 is reserved # noqa: E501
ConditionalField(BitField("reserved1", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # bit 4 is reserved # noqa: E501
ConditionalField(BitField("receiver_on_when_idle", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Receiver On When Idle # noqa: E501
ConditionalField(BitField("power_source", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Power Source # noqa: E501
ConditionalField(BitField("device_type", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Device Type # noqa: E501
ConditionalField(BitField("alternate_pan_coordinator", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Alternate PAN Coordinator # noqa: E501
# - Rejoin Response Command - #
# Network address (2 octets)
ConditionalField(XLEShortField("network_address", 0xFFFF), lambda pkt:pkt.cmd_identifier == 7), # noqa: E501
# Rejoin status (1 octet)
ConditionalField(ByteField("rejoin_status", 0), lambda pkt:pkt.cmd_identifier == 7), # noqa: E501
# - Link Status Command - #
# Command options (1 octet)
ConditionalField(BitField("reserved", 0, 1), lambda pkt:pkt.cmd_identifier == 8), # Reserved # noqa: E501
ConditionalField(BitField("last_frame", 0, 1), lambda pkt:pkt.cmd_identifier == 8), # Last frame # noqa: E501
ConditionalField(BitField("first_frame", 0, 1), lambda pkt:pkt.cmd_identifier == 8), # First frame # noqa: E501
ConditionalField(BitField("entry_count", 0, 5), lambda pkt:pkt.cmd_identifier == 8), # Entry count # noqa: E501
# Link status list (variable size)
ConditionalField(
PacketListField("link_status_list", [], LinkStatusEntry, count_from=lambda pkt:pkt.entry_count), # noqa: E501
lambda pkt:pkt.cmd_identifier == 8),
# - Network Report Command - #
# Command options (1 octet)
ConditionalField(
BitEnumField("report_command_identifier", 0, 3, {0: "PAN identifier conflict"}), # 0x01 - 0x07 Reserved # noqa: E501
lambda pkt: pkt.cmd_identifier == 9),
ConditionalField(BitField("report_information_count", 0, 5), lambda pkt: pkt.cmd_identifier == 9), # noqa: E501
# EPID: Extended PAN ID (8 octets)
ConditionalField(dot15d4AddressField("epid", 0, adjust=lambda pkt, x: 8), lambda pkt: pkt.cmd_identifier == 9), # noqa: E501
# Report information (variable length)
# Only present if we have a PAN Identifier Conflict Report
ConditionalField(
FieldListField("PAN_ID_conflict_report", [], XLEShortField("", 0x0000), # noqa: E501
count_from=lambda pkt:pkt.report_information_count),
lambda pkt:(pkt.cmd_identifier == 9 and pkt.report_command_identifier == 0) # noqa: E501
),
# - Network Update Command - #
# Command options (1 octet)
ConditionalField(
BitEnumField("update_command_identifier", 0, 3, {0: "PAN Identifier Update"}), # 0x01 - 0x07 Reserved # noqa: E501
lambda pkt: pkt.cmd_identifier == 10),
ConditionalField(BitField("update_information_count", 0, 5), lambda pkt: pkt.cmd_identifier == 10), # noqa: E501
# EPID: Extended PAN ID (8 octets)
ConditionalField(dot15d4AddressField("epid", 0, adjust=lambda pkt, x: 8), lambda pkt: pkt.cmd_identifier == 10), # noqa: E501
# Update Id (1 octet)
ConditionalField(ByteField("update_id", 0), lambda pkt: pkt.cmd_identifier == 10), # noqa: E501
# Update Information (Variable)
# Only present if we have a PAN Identifier Update
# New PAN ID (2 octets)
ConditionalField(XLEShortField("new_PAN_ID", 0x0000),
lambda pkt: (pkt.cmd_identifier == 10 and pkt.update_command_identifier == 0)), # noqa: E501
# StrField("data", ""),
]
def util_mic_len(pkt):
''' Calculate the length of the attribute value field '''
if (pkt.nwk_seclevel == 0): # no encryption, no mic
return 0
elif (pkt.nwk_seclevel == 1): # MIC-32
return 4
elif (pkt.nwk_seclevel == 2): # MIC-64
return 8
elif (pkt.nwk_seclevel == 3): # MIC-128
return 16
elif (pkt.nwk_seclevel == 4): # ENC
return 0
elif (pkt.nwk_seclevel == 5): # ENC-MIC-32
return 4
elif (pkt.nwk_seclevel == 6): # ENC-MIC-64
return 8
elif (pkt.nwk_seclevel == 7): # ENC-MIC-128
return 16
else:
return 0
class ZigbeeSecurityHeader(Packet):
name = "Zigbee Security Header"
fields_desc = [
# Security control (1 octet)
FlagsField("reserved1", 0, 2, ['reserved1', 'reserved2']),
BitField("extended_nonce", 1, 1), # set to 1 if the sender address field is present (source) # noqa: E501
# Key identifier
BitEnumField("key_type", 1, 2, {
0: 'data_key',
1: 'network_key',
2: 'key_transport_key',
3: 'key_load_key'
}),
# Security level (3 bits)
BitEnumField("nwk_seclevel", 0, 3, {
0: "None",
1: "MIC-32",
2: "MIC-64",
3: "MIC-128",
4: "ENC",
5: "ENC-MIC-32",
6: "ENC-MIC-64",
7: "ENC-MIC-128"
}),
# Frame counter (4 octets)
XLEIntField("fc", 0), # provide frame freshness and prevent duplicate frames # noqa: E501
# Source address (0/8 octets)
ConditionalField(dot15d4AddressField("source", 0, adjust=lambda pkt, x: 8), lambda pkt: pkt.extended_nonce), # noqa: E501
# Key sequence number (0/1 octet): only present when key identifier is 1 (network key) # noqa: E501
ConditionalField(ByteField("key_seqnum", 0), lambda pkt: pkt.getfieldval("key_type") == 1), # noqa: E501
# Payload
# the length of the encrypted data is the payload length minus the MIC
StrField("data", ""), # noqa: E501
# Message Integrity Code (0/variable in size), length depends on nwk_seclevel # noqa: E501
XStrField("mic", ""),
]
def post_dissect(self, s):
# Get the mic dissected correctly
mic_length = util_mic_len(self)
if mic_length > 0: # Slice "data" into "data + mic"
_data, _mic = self.data[:-mic_length], self.data[-mic_length:]
self.data, self.mic = _data, _mic
return s
class ZigbeeAppDataPayload(Packet):
name = "Zigbee Application Layer Data Payload (General APS Frame Format)"
fields_desc = [
# Frame control (1 octet)
FlagsField("frame_control", 2, 4,
['reserved1', 'security', 'ack_req', 'extended_hdr']),
BitEnumField("delivery_mode", 0, 2,
{0: 'unicast', 1: 'indirect',
2: 'broadcast', 3: 'group_addressing'}),
BitEnumField("aps_frametype", 0, 2,
{0: 'data', 1: 'command', 2: 'ack'}),
# Destination endpoint (0/1 octet)
ConditionalField(
ByteField("dst_endpoint", 10),
lambda pkt: (pkt.frame_control.ack_req or pkt.aps_frametype == 2)
),
# Group address (0/2 octets) TODO
# Cluster identifier (0/2 octets)
ConditionalField(
# unsigned short (little-endian)
EnumField("cluster", 0, _zcl_cluster_identifier, fmt="<H"),
lambda pkt: (pkt.frame_control.ack_req or pkt.aps_frametype == 2)
),
# Profile identifier (0/2 octets)
ConditionalField(
EnumField("profile", 0, _zcl_profile_identifier, fmt="<H"),
lambda pkt: (pkt.frame_control.ack_req or pkt.aps_frametype == 2)
),
# Source endpoint (0/1 octets)
ConditionalField(
ByteField("src_endpoint", 10),
lambda pkt: (pkt.frame_control.ack_req or pkt.aps_frametype == 2)
),
# APS counter (1 octet)
ByteField("counter", 0),
# Extended header (0/1/2 octets)
# cribbed from https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-zbee-aps.c # noqa: E501
ConditionalField(
ByteEnumField(
"fragmentation", 0,
{0: "none", 1: "first_block", 2: "middle_block"}),
lambda pkt: pkt.frame_control.extended_hdr
),
ConditionalField(ByteField("block_number", 0),
lambda pkt: pkt.fragmentation),
# variable length frame payload:
# 3 frame types: data, APS command, and acknowledgement
# ConditionalField(StrField("data", ""), lambda pkt:pkt.aps_frametype == 0), # noqa: E501
]
def guess_payload_class(self, payload):
if self.frame_control & 0x02: # we have a security header
return ZigbeeSecurityHeader
elif self.aps_frametype == 0: # data
return ZigbeeClusterLibrary # TODO might also be another frame
elif self.aps_frametype == 1: # command
return ZigbeeAppCommandPayload
else:
return Packet.guess_payload_class(self, payload)
_TransportKeyKeyTypes = {
0x00: "Trust Center Master Key",
0x01: "Standard Network Key",
0x02: "Application Master Key",
0x03: "Application Link Key",
0x04: "Trust Center Link Key",
0x05: "High-Security Network Key",
}
class ZigbeeAppCommandPayload(Packet):
name = "Zigbee Application Layer Command Payload"
fields_desc = [
ByteEnumField("cmd_identifier", 1, {
1: "APS_CMD_SKKE_1",
2: "APS_CMD_SKKE_2",
3: "APS_CMD_SKKE_3",
4: "APS_CMD_SKKE_4",
5: "APS_CMD_TRANSPORT_KEY",
6: "APS_CMD_UPDATE_DEVICE",
7: "APS_CMD_REMOVE_DEVICE",
8: "APS_CMD_REQUEST_KEY",
9: "APS_CMD_SWITCH_KEY",
# TODO: implement 10 to 14
10: "APS_CMD_EA_INIT_CHLNG",
11: "APS_CMD_EA_RSP_CHLNG",
12: "APS_CMD_EA_INIT_MAC_DATA",
13: "APS_CMD_EA_RSP_MAC_DATA",
14: "APS_CMD_TUNNEL"
}),
# SKKE Commands
ConditionalField(dot15d4AddressField("initiator", 0,
adjust=lambda pkt, x: 8),
lambda pkt: pkt.cmd_identifier in [1, 2, 3, 4]),
ConditionalField(dot15d4AddressField("responder", 0,
adjust=lambda pkt, x: 8),
lambda pkt: pkt.cmd_identifier in [1, 2, 3, 4]),
ConditionalField(StrFixedLenField("data", 0, length=16),
lambda pkt: pkt.cmd_identifier in [1, 2, 3, 4]),
# Transport-key Command
ConditionalField(ByteEnumField("key_type", 0, _TransportKeyKeyTypes),
lambda pkt: pkt.cmd_identifier == 5),
ConditionalField(StrFixedLenField("key", None, 16),
lambda pkt: pkt.cmd_identifier == 5),
ConditionalField(ByteField("key_seqnum", 0),
lambda pkt: (pkt.cmd_identifier == 5 and
pkt.key_type in [0x01, 0x05])),
ConditionalField(dot15d4AddressField("dest_addr", 0,
adjust=lambda pkt, x: 8),
lambda pkt: pkt.cmd_identifier == 5),
ConditionalField(dot15d4AddressField("src_addr", 0,
adjust=lambda pkt, x: 8),
lambda pkt: pkt.cmd_identifier == 5),
# Update-Device Command
ConditionalField(dot15d4AddressField("address", 0,
adjust=lambda pkt, x: 8),
lambda pkt: pkt.cmd_identifier == 6),
ConditionalField(XLEShortField("short_address", 0),
lambda pkt: pkt.cmd_identifier == 6),
ConditionalField(ByteField("status", 0),
lambda pkt: pkt.cmd_identifier == 6),
# Remove-Device Command
ConditionalField(dot15d4AddressField("address", 0,
adjust=lambda pkt, x: 8),
lambda pkt: pkt.cmd_identifier == 7),
# Request-Key Command
ConditionalField(ByteEnumField("key_type", 0, _TransportKeyKeyTypes),
lambda pkt: pkt.cmd_identifier == 8),
ConditionalField(StrFixedLenField("key", None, 16),
lambda pkt: pkt.cmd_identifier == 8),
# Switch-Key Command
ConditionalField(StrFixedLenField("seqnum", None, 8),
lambda pkt: pkt.cmd_identifier == 9),
# Un-implemented: 10-14 (+?)
ConditionalField(StrField("data", ""),
lambda pkt: (pkt.cmd_identifier < 0 or
pkt.cmd_identifier > 9))
]
class ZigBeeBeacon(Packet):
name = "ZigBee Beacon Payload"
fields_desc = [
# Protocol ID (1 octet)
ByteField("proto_id", 0),
# nwkcProtocolVersion (4 bits)
BitField("nwkc_protocol_version", 0, 4),
# Stack profile (4 bits)
BitField("stack_profile", 0, 4),
# End device capacity (1 bit)
BitField("end_device_capacity", 0, 1),
# Device depth (4 bits)
BitField("device_depth", 0, 4),
# Router capacity (1 bit)
BitField("router_capacity", 0, 1),
# Reserved (2 bits)
BitField("reserved", 0, 2),
# Extended PAN ID (8 octets)
dot15d4AddressField("extended_pan_id", 0, adjust=lambda pkt, x: 8),
# Tx offset (3 bytes)
# In ZigBee 2006 the Tx-Offset is optional, while in the 2007 and later versions, the Tx-Offset is a required value. # noqa: E501
BitField("tx_offset", 0, 24),
# Update ID (1 octet)
ByteField("update_id", 0),
]
# Inter-PAN Transmission #
class ZigbeeNWKStub(Packet):
name = "Zigbee Network Layer for Inter-PAN Transmission"
fields_desc = [
# NWK frame control
BitField("reserved", 0, 2), # remaining subfields shall have a value of 0 # noqa: E501
BitField("proto_version", 2, 4),
BitField("frametype", 0b11, 2), # 0b11 (3) is a reserved frame type
BitField("reserved", 0, 8), # remaining subfields shall have a value of 0 # noqa: E501
]
def guess_payload_class(self, payload):
if self.frametype == 0b11:
return ZigbeeAppDataPayloadStub
else:
return Packet.guess_payload_class(self, payload)
class ZigbeeAppDataPayloadStub(Packet):
name = "Zigbee Application Layer Data Payload for Inter-PAN Transmission"
fields_desc = [
FlagsField("frame_control", 0, 4, ['reserved1', 'security', 'ack_req', 'extended_hdr']), # noqa: E501
BitEnumField("delivery_mode", 0, 2, {0: 'unicast', 2: 'broadcast', 3: 'group'}), # noqa: E501
BitField("frametype", 3, 2), # value 0b11 (3) is a reserved frame type
# Group Address present only when delivery mode field has a value of 0b11 (group delivery mode) # noqa: E501
ConditionalField(
XLEShortField("group_addr", 0x0), # 16-bit identifier of the group
lambda pkt: pkt.getfieldval("delivery_mode") == 0b11
),
# Cluster identifier
EnumField("cluster", 0, _zcl_cluster_identifier, fmt="<H"), # unsigned short (little-endian) # noqa: E501
# Profile identifier
EnumField("profile", 0, _zcl_profile_identifier, fmt="<H"),
# ZigBee Payload
ConditionalField(
StrField("data", ""),
lambda pkt: pkt.frametype == 3
),
]
# ZigBee Cluster Library #
_ZCL_attr_length = {
0x00: 0, # no data
0x08: 1, # 8-bit data
0x09: 2, # 16-bit data
0x0a: 3, # 24-bit data
0x0b: 4, # 32-bit data
0x0c: 5, # 40-bit data
0x0d: 6, # 48-bit data
0x0e: 7, # 56-bit data
0x0f: 8, # 64-bit data
0x10: 1, # boolean
0x18: 1, # 8-bit bitmap
0x19: 2, # 16-bit bitmap
0x1a: 3, # 24-bit bitmap
0x1b: 4, # 32-bit bitmap
0x1c: 5, # 40-bit bitmap
0x1d: 6, # 48-bit bitmap
0x1e: 7, # 46-bit bitmap
0x1f: 8, # 64-bit bitmap
0x20: 1, # Unsigned 8-bit integer
0x21: 2, # Unsigned 16-bit integer
0x22: 3, # Unsigned 24-bit integer
0x23: 4, # Unsigned 32-bit integer
0x24: 5, # Unsigned 40-bit integer
0x25: 6, # Unsigned 48-bit integer
0x26: 7, # Unsigned 56-bit integer
0x27: 8, # Unsigned 64-bit integer
0x28: 1, # Signed 8-bit integer
0x29: 2, # Signed 16-bit integer
0x2a: 3, # Signed 24-bit integer
0x2b: 4, # Signed 32-bit integer
0x2c: 5, # Signed 40-bit integer
0x2d: 6, # Signed 48-bit integer
0x2e: 7, # Signed 56-bit integer
0x2f: 8, # Signed 64-bit integer
0x30: 1, # 8-bit enumeration
0x31: 2, # 16-bit enumeration
0x38: 2, # Semi-precision
0x39: 4, # Single precision
0x3a: 8, # Double precision
0x41: (1, "!B"), # Octet string
0x42: (1, "!B"), # Character string
0x43: (2, "!H"), # Long octet string
0x44: (2, "!H"), # Long character string
# TODO (implement Ordered sequence & collection
0xe0: 4, # Time of day
0xe1: 4, # Date
0xe2: 4, # UTCTime
0xe8: 2, # Cluster ID
0xe9: 2, # Attribute ID
0xea: 4, # BACnet OID
0xf0: 8, # IEEE address
0xf1: 16, # 128-bit security key
0xff: 0, # Unknown
}
class _DiscreteString(StrLenField):
def getfield(self, pkt, s):
dtype = pkt.attribute_data_type
length = _ZCL_attr_length.get(dtype, None)
if length is None:
return b"", self.m2i(pkt, s)
elif isinstance(length, tuple): # Variable length
size, fmt = length
# We add size as we include the length tag in the string
length = struct.unpack(fmt, s[:size])[0] + size
if isinstance(length, int):
self.length_from = lambda x: length
return StrLenField.getfield(self, pkt, s)
return s
class ZCLReadAttributeStatusRecord(Packet):
name = "ZCL Read Attribute Status Record"
fields_desc = [
# Attribute Identifier
XLEShortField("attribute_identifier", 0),
# Status
ByteEnumField("status", 0, _zcl_enumerated_status_values),
# Attribute data type (0/1 octet), and data (0/variable size)
# are only included if status == 0x00 (SUCCESS)
ConditionalField(
ByteEnumField("attribute_data_type", 0, _zcl_attribute_data_types),
lambda pkt:pkt.status == 0x00
),
ConditionalField(
_DiscreteString("attribute_value", ""),
lambda pkt:pkt.status == 0x00
),
]
def extract_padding(self, s):
return "", s
class ZCLGeneralReadAttributes(Packet):
name = "General Domain: Command Frame Payload: read_attributes"
fields_desc = [
FieldListField("attribute_identifiers", [], XLEShortField("", 0x0000)),
]
class ZCLGeneralReadAttributesResponse(Packet):
name = "General Domain: Command Frame Payload: read_attributes_response"
fields_desc = [
PacketListField("read_attribute_status_record", [], ZCLReadAttributeStatusRecord), # noqa: E501
]
class ZCLMeteringGetProfile(Packet):
name = "Metering Cluster: Get Profile Command (Server: Received)"
fields_desc = [
# Interval Channel (8-bit Enumeration): 1 octet
ByteField("Interval_Channel", 0), # 0 == Consumption Delivered ; 1 == Consumption Received # noqa: E501
# End Time (UTCTime): 4 octets
XLEIntField("End_Time", 0x00000000),
# NumberOfPeriods (Unsigned 8-bit Integer): 1 octet
ByteField("NumberOfPeriods", 1), # Represents the number of intervals being requested. # noqa: E501
]
class ZCLPriceGetCurrentPrice(Packet):
name = "Price Cluster: Get Current Price Command (Server: Received)"
fields_desc = [
BitField("reserved", 0, 7),
BitField("Requestor_Rx_On_When_Idle", 0, 1),
]
class ZCLPriceGetScheduledPrices(Packet):
name = "Price Cluster: Get Scheduled Prices Command (Server: Received)"
fields_desc = [
XLEIntField("start_time", 0x00000000), # UTCTime (4 octets)
ByteField("number_of_events", 0), # Number of Events (1 octet)
]
class ZCLPricePublishPrice(Packet):
name = "Price Cluster: Publish Price Command (Server: Generated)"
fields_desc = [
XLEIntField("provider_id", 0x00000000), # Unsigned 32-bit Integer (4 octets) # noqa: E501
# Rate Label is a UTF-8 encoded Octet String (0-12 octets). The first Octet indicates the length. # noqa: E501
StrLenField("rate_label", "", length_from=lambda pkt:int(pkt.rate_label[0])), # TODO verify # noqa: E501
XLEIntField("issuer_event_id", 0x00000000), # Unsigned 32-bit Integer (4 octets) # noqa: E501
XLEIntField("current_time", 0x00000000), # UTCTime (4 octets)
ByteField("unit_of_measure", 0), # 8 bits enumeration (1 octet)
XLEShortField("currency", 0x0000), # Unsigned 16-bit Integer (2 octets) # noqa: E501
ByteField("price_trailing_digit", 0), # 8-bit BitMap (1 octet)
ByteField("number_of_price_tiers", 0), # 8-bit BitMap (1 octet)
XLEIntField("start_time", 0x00000000), # UTCTime (4 octets)
XLEShortField("duration_in_minutes", 0x0000), # Unsigned 16-bit Integer (2 octets) # noqa: E501
XLEIntField("price", 0x00000000), # Unsigned 32-bit Integer (4 octets)
ByteField("price_ratio", 0), # Unsigned 8-bit Integer (1 octet)
XLEIntField("generation_price", 0x00000000), # Unsigned 32-bit Integer (4 octets) # noqa: E501
ByteField("generation_price_ratio", 0), # Unsigned 8-bit Integer (1 octet) # noqa: E501
XLEIntField("alternate_cost_delivered", 0x00000000), # Unsigned 32-bit Integer (4 octets) # noqa: E501
ByteField("alternate_cost_unit", 0), # 8-bit enumeration (1 octet)
ByteField("alternate_cost_trailing_digit", 0), # 8-bit BitMap (1 octet) # noqa: E501
ByteField("number_of_block_thresholds", 0), # 8-bit BitMap (1 octet)
ByteField("price_control", 0), # 8-bit BitMap (1 octet)
]
class ZigbeeClusterLibrary(Packet):
name = "Zigbee Cluster Library (ZCL) Frame"
fields_desc = [
# Frame control (8 bits)
BitField("reserved", 0, 3),
BitField("disable_default_response", 0, 1), # 0 default response command will be returned # noqa: E501
BitField("direction", 0, 1), # 0 command sent from client to server; 1 command sent from server to client # noqa: E501
BitField("manufacturer_specific", 0, 1), # 0 manufacturer code shall not be included in the ZCL frame # noqa: E501
# Frame Type
# 0b00 command acts across the entire profile
# 0b01 command is specific to a cluster
# 0b10 - 0b11 reserved
BitEnumField("zcl_frametype", 0, 2, {0: 'profile-wide', 1: 'cluster-specific', 2: 'reserved2', 3: 'reserved3'}), # noqa: E501
# Manufacturer code (0/16 bits) only present then manufacturer_specific field is set to 1 # noqa: E501
ConditionalField(XLEShortField("manufacturer_code", 0x0),
lambda pkt: pkt.getfieldval("manufacturer_specific") == 1 # noqa: E501
),
# Transaction sequence number (8 bits)
ByteField("transaction_sequence", 0),
# Command identifier (8 bits): the cluster command
ByteEnumField("command_identifier", 0, _zcl_command_frames),
]
def guess_payload_class(self, payload):
# Profile-wide commands
if self.zcl_frametype == 0x00 and self.command_identifier == 0x00:
# done in bind_layers
pass
# Cluster-specific commands
elif self.zcl_frametype == 0x01 and self.command_identifier == 0x00 and self.direction == 0 and self.underlayer.cluster == 0x0700: # "price" # noqa: E501
return ZCLPriceGetCurrentPrice
elif self.zcl_frametype == 0x01 and self.command_identifier == 0x01 and self.direction == 0 and self.underlayer.cluster == 0x0700: # "price" # noqa: E501
return ZCLPriceGetScheduledPrices
elif self.zcl_frametype == 0x01 and self.command_identifier == 0x00 and self.direction == 1 and self.underlayer.cluster == 0x0700: # "price" # noqa: E501
return ZCLPricePublishPrice
return Packet.guess_payload_class(self, payload)
bind_layers(ZigbeeClusterLibrary, ZCLGeneralReadAttributes,
zcl_frametype=0x00, command_identifier=0x00)
bind_layers(ZigbeeClusterLibrary, ZCLGeneralReadAttributesResponse,
zcl_frametype=0x00, command_identifier=0x01)
# Zigbee Encapsulation Protocol
class ZEP2(Packet):
name = "Zigbee Encapsulation Protocol (V2)"
fields_desc = [
StrFixedLenField("preamble", "EX", length=2),
ByteField("ver", 0),
ByteField("type", 0),
ByteField("channel", 0),
ShortField("device", 0),
ByteField("lqi_mode", 1),
ByteField("lqi_val", 0),
TimeStampField("timestamp", 0),
IntField("seq", 0),
BitField("res", 0, 80), # 10 bytes reserved field
ByteField("length", 0),
]
@classmethod
def dispatch_hook(cls, _pkt=b"", *args, **kargs):
if _pkt and len(_pkt) >= 4:
v = orb(_pkt[2])
if v == 1:
return ZEP1
elif v == 2:
return ZEP2
return cls
def guess_payload_class(self, payload):
if self.lqi_mode:
return Dot15d4
else:
return Dot15d4FCS
class ZEP1(ZEP2):
name = "Zigbee Encapsulation Protocol (V1)"
fields_desc = [
StrFixedLenField("preamble", "EX", length=2),
ByteField("ver", 0),
ByteField("channel", 0),
ShortField("device", 0),
ByteField("lqi_mode", 0),
ByteField("lqi_val", 0),
BitField("res", 0, 56), # 7 bytes reserved field
ByteField("len", 0),
]
# Bindings #
# TODO: find a way to chose between ZigbeeNWK and SixLoWPAN (cf. sixlowpan.py)
# Currently: use conf.dot15d4_protocol value
# bind_layers( Dot15d4Data, ZigbeeNWK)
bind_layers(ZigbeeAppDataPayload, ZigbeeAppCommandPayload, frametype=1)
bind_layers(Dot15d4Beacon, ZigBeeBeacon)
bind_bottom_up(UDP, ZEP2, sport=17754)
bind_bottom_up(UDP, ZEP2, sport=17754)
bind_layers(UDP, ZEP2, sport=17754, dport=17754)