diff options
author | Ralf Nasilowski <Ralf.Nasilowski@ise.de> | 2018-08-16 10:49:31 +0200 |
---|---|---|
committer | Roland Knall <rknall@gmail.com> | 2018-10-16 09:03:43 +0000 |
commit | 9769df50efd830254aef0310562cdf47edd4ada3 (patch) | |
tree | 4e0b386f619e5a85c44a13dac9cf00071bfece1c | |
parent | 84fd2d79682278927ce07361d901faed35bd1202 (diff) |
KNX-IP: new KNXnet/IP dissector
The new KNXnet/IP dissector replaces the old KNXnet/IP dissector.
The new KNXnet/IP dissector supports the new KNX features
- A_MemoryExtended services
- A_PropertyExt services
- KNX Data Security
- KNXnet/IP Core V2
- KNXnet/IP Device Management V2
- KNXnet/IP Tunneling V2
- KNXnet/IP Routing V2
- KNXnet/IP Security
Change-Id: I3d1d716ef03d16d2720e6a1fcb23c2243d1cd956
Reviewed-on: https://code.wireshark.org/review/29155
Petri-Dish: Roland Knall <rknall@gmail.com>
Tested-by: Petri Dish Buildbot
Reviewed-by: Peter Wu <peter@lekensteyn.nl>
Reviewed-by: Roland Knall <rknall@gmail.com>
-rw-r--r-- | epan/dissectors/CMakeLists.txt | 6 | ||||
-rw-r--r-- | epan/dissectors/packet-cemi.c | 3515 | ||||
-rw-r--r-- | epan/dissectors/packet-knxip.c | 4136 | ||||
-rw-r--r-- | epan/dissectors/packet-knxip.h | 56 | ||||
-rw-r--r-- | epan/dissectors/packet-knxip_decrypt.c | 811 | ||||
-rw-r--r-- | epan/dissectors/packet-knxip_decrypt.h | 97 | ||||
-rw-r--r-- | epan/dissectors/packet-knxnetip.c | 1842 | ||||
-rw-r--r-- | test/captures/knxip_DataSec.pcap | bin | 0 -> 125 bytes | |||
-rw-r--r-- | test/captures/knxip_SecureWrapper.pcap | bin | 0 -> 137 bytes | |||
-rw-r--r-- | test/captures/knxip_TimerNotify.pcap | bin | 0 -> 118 bytes | |||
-rw-r--r-- | test/keys/knx_keyring.xml | 58 | ||||
-rw-r--r-- | test/suite_decryption.py | 115 |
12 files changed, 8793 insertions, 1843 deletions
diff --git a/epan/dissectors/CMakeLists.txt b/epan/dissectors/CMakeLists.txt index 86ae83c3b9..5e099e1b17 100644 --- a/epan/dissectors/CMakeLists.txt +++ b/epan/dissectors/CMakeLists.txt @@ -405,6 +405,8 @@ set(DISSECTOR_PUBLIC_HEADERS packet-juniper.h packet-jxta.h packet-kerberos.h + packet-knxip.h + packet-knxip_decrypt.h packet-l2tp.h packet-lapdm.h packet-lbm.h @@ -762,6 +764,7 @@ set(DISSECTOR_SRC ${CMAKE_CURRENT_SOURCE_DIR}/packet-ccsds.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-cdp.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-cell_broadcast.c + ${CMAKE_CURRENT_SOURCE_DIR}/packet-cemi.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-ceph.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-cfdp.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-cfm.c @@ -1243,7 +1246,8 @@ set(DISSECTOR_SRC ${CMAKE_CURRENT_SOURCE_DIR}/packet-kismet.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-klm.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-knet.c - ${CMAKE_CURRENT_SOURCE_DIR}/packet-knxnetip.c + ${CMAKE_CURRENT_SOURCE_DIR}/packet-knxip.c + ${CMAKE_CURRENT_SOURCE_DIR}/packet-knxip_decrypt.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-kpasswd.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-kt.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-l1-events.c diff --git a/epan/dissectors/packet-cemi.c b/epan/dissectors/packet-cemi.c new file mode 100644 index 0000000000..490476ad34 --- /dev/null +++ b/epan/dissectors/packet-cemi.c @@ -0,0 +1,3515 @@ +/* packet-cemi.c + * Routines for cEMI (Common External Message Interface) dissection + * By Jan Kessler <kessler@ise.de> + * Copyright 2004, Jan Kessler <kessler@ise.de> + * + * Ethereal - Network traffic analyzer + * By Gerald Combs <gerald@ethereal.com> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "packet-knxip.h" + +/* cEMI Message Codes +*/ +#define CEMI_L_BUSMON_IND 0x2B +#define CEMI_L_RAW_IND 0x2D +#define CEMI_L_RAW_REQ 0x10 +#define CEMI_L_RAW_CON 0x2F +#define CEMI_L_DATA_REQ 0x11 +#define CEMI_L_DATA_CON 0x2E +#define CEMI_L_DATA_IND 0x29 +#define CEMI_L_POLL_DATA_REQ 0x13 +#define CEMI_L_POLL_DATA_CON 0x25 +#define CEMI_T_DATA_INDIVIDUAL_REQ 0x4A +#define CEMI_T_DATA_INDIVIDUAL_IND 0x94 +#define CEMI_T_DATA_CONNECTED_REQ 0x41 +#define CEMI_T_DATA_CONNECTED_IND 0x89 +#define CEMI_M_PROPREAD_REQ 0xFC +#define CEMI_M_PROPREAD_CON 0xFB +#define CEMI_M_PROPWRITE_REQ 0xF6 +#define CEMI_M_PROPWRITE_CON 0xF5 +#define CEMI_M_PROPINFO_IND 0xF7 +#define CEMI_M_FUNCPROPCMD_REQ 0xF8 +#define CEMI_M_FUNCPROPREAD_REQ 0xF9 +#define CEMI_M_FUNCPROP_CON 0xFA +#define CEMI_M_RESET_REQ 0xF1 +#define CEMI_M_RESET_IND 0xF0 + +/* Additional Information Types +*/ + /* 0x00 Reserved. */ +#define CEMI_PL_MEDIUM_INFORMATION 0x01 /*!< (2 octets) Domain Address used by PL medium; Client <-> Server */ +#define CEMI_RF_MEDIUM_INFORMATION 0x02 /*!< (7 octet) RF-Control byte and serial number/DoA; + Client <-> Server Busmonitor */ +#define CEMI_STATUS_INFO 0x03 /*!< (1 octet) Busmonitor Error Flags; see clause 2.5.5.5; Client <- Server */ +#define CEMI_TIMESTAMP_RELATIVE 0x04 /*!< (2 octets) Relative timestamp; e.g. for L_Raw.ind; Client <- Server */ +#define CEMI_TIME_DELAY_UNTIL_SENDING 0x05 /*!< (4 octets) Time delay (L_Raw.req, see clause 2.5.5.3); Client <- Server */ + /* 0x06-0xFE Not used. */ + /* 0xFF For future system extension (ESC Code). */ + +/* Error Codes +*/ +#define CEMI_UNSPECIFIED_ERROR 0x00 /*!< Unknown error (R/W). */ +#define CEMI_OUT_OF_RANGE 0x01 /*!< Write value not allowed (general, if not error 2 or 3) (W). */ +#define CEMI_OUT_OF_MAXRANGE 0x02 /*!< Write value to high (W). */ +#define CEMI_OUT_OF_MINRANGE 0x03 /*!< Write value to low (W). */ +#define CEMI_MEMORY_ERROR 0x04 /*!< Memory can not be written or only with fault(s) (W). */ +#define CEMI_READ_ONLY 0x05 /*!< Write access to a 'read only' or a write protected property (W). */ +#define CEMI_ILLEGAL_COMMAND 0x06 /*!< COMMAND not valid or not supported (W). */ +#define CEMI_VOID_DP 0x07 /*!< Read or write access to an non existing property (R/W). */ +#define CEMI_TYPE_CONFLICT 0x08 /*!< Write access with a wrong data type (datapoint length) (W). */ +#define CEMI_PROP_INDEX_RANGE_ERROR 0x09 /* Read or write access to a non existing property array index (R/W). */ + +/* Common EMI specific device server properties +*/ +#define CEMI_PID_DOMAIN_ADDRESS 0x70 /*!< Domain Address of a PL medium (cEMI server) device. + PDT_UNSIGNED_INT O - r/w */ +#define CEMI_PID_IO_LIST 0x71 /*!< List of Interface Objects in the (cEMI server) device. + PDT_UNSIGNED_INT O - r/w */ + +#define CEMI_PID_MEDIUM_TYPE 0x51 /*!< Media Type(s) supported by cEMI server. + DPT_Media M - read only */ +#define CEMI_PID_COMM_MODE 0x52 /*!< Link Layer / Raw (Busmonitor) / Transport L. + DPT_CommMode O - r/w */ +#define CEMI_PID_MEDIUM_AVAILABILITY 0x53 /*!< Bus available (1) or not (0) ? + DPT_Media O - read only */ +#define CEMI_PID_ADD_INFO_TYPES 0x54 /*!< cEMI supported Additional Information Types. + DPT_AddInfoTypes O - read only */ +#define CEMI_PID_TRANSP_ENABLE 0x56 /*!< LL Transparency Mode of cEMI server. + DPT_Enable O - r/w */ + /* 0x57 Reserved for cEMI client's subnetwork address. + PDT_UNSIGNED_CHAR O - r/w */ + /* 0x58 Reserved for cEMI client's device address. + PDT_UNSIGNED_CHAR O - r/w */ + /* 0x59 t.b.d.*/ + /* 0x60 t.b.d.*/ + /* 0x61 DoA Filter. t.b.d. O - read only */ + +#define CEMI_PID_MEDIUM_TYPE_TP0 0x0001 /*!< TP 0 */ +#define CEMI_PID_MEDIUM_TYPE_TP1 0x0002 /*!< TP 1 */ +#define CEMI_PID_MEDIUM_TYPE_PL110 0x0004 /*!< PL 110 */ +#define CEMI_PID_MEDIUM_TYPE_PL132 0x0008 /*!< PL 132 */ +#define CEMI_PID_MEDIUM_TYPE_RF 0x0010 /*!< RF */ + +#define CEMI_PID_COMM_MODE_LL 0x00 /*!< Link Layer = default comm. mode. */ +#define CEMI_PID_COMM_MODE_LLB 0x01 /*!< Link Layer Busmonitor. */ +#define CEMI_PID_COMM_MODE_LLR 0x02 /*!< Link Layer Raw Frames. */ + /* 0x03 Reserved for Network Layer. */ + /* 0x04 Reserved for TL group oriented. */ + /* 0x05 Reserved for TL connection oriented. */ + /* 0x05-0xFF Reserved for other 'destination layers'. */ + +/* - - - - - - - T R E E V I E W I D E N T I F I E R - - - - - - - - +*/ + +/* Initialize the protocol identifier that is needed for the + protocol hook and to register the fields in the protocol tree +*/ +static gint proto_cemi = -1; + +/* Initialize the registered fields identifiers. These fields + will be registered with the protocol during initialization. + Protocol fields are like type definitions. The protocol dissector + later on adds items of these types to the protocol tree. +*/ +static gint hf_bytes = -1; +static gint hf_folder = -1; +static gint hf_cemi_mc = -1; +static gint hf_cemi_error = -1; +static gint hf_cemi_ai_length = -1; +static gint hf_cemi_aie_type = -1; +static gint hf_cemi_aie_length = -1; +static gint hf_cemi_ot = -1; +static gint hf_cemi_oi = -1; +static gint hf_cemi_ox = -1; +static gint hf_cemi_px = -1; +static gint hf_cemi_pid = -1; +static gint hf_cemi_ne = -1; +static gint hf_cemi_sx = -1; +static gint hf_cemi_ft = -1; +static gint hf_cemi_rep = -1; +static gint hf_cemi_bt = -1; +static gint hf_cemi_prio = -1; +static gint hf_cemi_ack = -1; +static gint hf_cemi_ce = -1; +static gint hf_cemi_at = -1; +static gint hf_cemi_hc = -1; +static gint hf_cemi_eff = -1; +static gint hf_cemi_sa = -1; +static gint hf_cemi_da = -1; +static gint hf_cemi_len = -1; +static gint hf_cemi_tpt = -1; +static gint hf_cemi_tst = -1; +static gint hf_cemi_num = -1; +static gint hf_cemi_tc = -1; +static gint hf_cemi_ac = -1; +static gint hf_cemi_ad = -1; +static gint hf_cemi_ad_memory_length = -1; +static gint hf_cemi_ad_channel = -1; +static gint hf_cemi_ad_type = -1; +static gint hf_cemi_ax = -1; +static gint hf_cemi_pw = -1; +static gint hf_cemi_pdt = -1; +static gint hf_cemi_me = -1; +static gint hf_cemi_ra = -1; +static gint hf_cemi_wa = -1; +static gint hf_cemi_ext_oi = -1; +static gint hf_cemi_ext_pid = -1; +static gint hf_cemi_ext_ne = -1; +static gint hf_cemi_ext_sx = -1; +static gint hf_cemi_ext_dt = -1; +static gint hf_cemi_ext_px = -1; +static gint hf_cemi_ext_memory_length = -1; +static gint hf_cemi_ext_memory_address = -1; +static gint hf_cemi_memory_length = -1; +static gint hf_cemi_memory_address = -1; +static gint hf_cemi_memory_address_ext = -1; +static gint hf_cemi_level = -1; +static gint hf_cemi_snp_pid = -1; +static gint hf_cemi_snp_reserved = -1; +static gint hf_cemi_dpt_major = -1; +static gint hf_cemi_dpt_minor = -1; +static gint hf_cemi_scf = -1; +static gint hf_cemi_scf_t = -1; +static gint hf_cemi_scf_sai = -1; +static gint hf_cemi_scf_sbc = -1; +static gint hf_cemi_scf_svc = -1; +static gint hf_cemi_adc_count = -1; + +/* Initialize the subtree pointers. These pointers are needed to + display the protocol in a structured tree. Subtrees hook on + already defined fields or (the topmost) on the protocol itself +*/ +static gint ett_cemi = -1; +static gint ett_cemi_ai = -1; +static gint ett_cemi_aie = -1; +static gint ett_cemi_ctrl1 = -1; +static gint ett_cemi_ctrl2 = -1; +static gint ett_cemi_tpci = -1; +static gint ett_cemi_apci = -1; +static gint ett_cemi_range = -1; +static gint ett_cemi_pd = -1; +static gint ett_cemi_dpt = -1; +static gint ett_cemi_scf = -1; +static gint ett_cemi_decrypted = -1; + +/* - - - - - - - - - - - V A L U E T A B L E S - - - - - - - - - - - - +*/ + +/* See following docs: + + "AN033 v03 cEMI.pdf", + "AN057 v01 System B RfV.pdf", + "KSG259 2004.02.03 Identifiers.pdf", + "03_07_03 Standardized Identifier Tables.pdf" +*/ + +/* Message Code +*/ +static const value_string mc_vals[] = { + { CEMI_L_BUSMON_IND, "L_Busmon.ind" }, + { CEMI_L_RAW_IND, "L_Raw.ind" }, + { CEMI_L_RAW_REQ, "L_Raw.req" }, + { CEMI_L_RAW_CON, "L_Raw.con" }, + { CEMI_L_DATA_REQ, "L_Data.req" }, + { CEMI_L_DATA_CON, "L_Data.con" }, + { CEMI_L_DATA_IND, "L_Data.ind" }, + { CEMI_L_POLL_DATA_REQ, "L_PollData.req" }, + { CEMI_L_POLL_DATA_CON, "L_PollData.con" }, + { CEMI_T_DATA_INDIVIDUAL_REQ, "T_Data_Individual.req" }, + { CEMI_T_DATA_INDIVIDUAL_IND, "T_Data_Individual.ind" }, + { CEMI_T_DATA_CONNECTED_REQ, "T_Data_Connected.req" }, + { CEMI_T_DATA_CONNECTED_IND, "T_Data_Connected.ind" }, + { CEMI_M_PROPREAD_REQ, "M_PropRead.req" }, + { CEMI_M_PROPREAD_CON, "M_PropRead.con" }, + { CEMI_M_PROPWRITE_REQ, "M_PropWrite.req" }, + { CEMI_M_PROPWRITE_CON, "M_PropWrite.con" }, + { CEMI_M_PROPINFO_IND, "M_PropInfo.ind" }, + { CEMI_M_FUNCPROPCMD_REQ, "M_FuncPropCmd.req" }, + { CEMI_M_FUNCPROPREAD_REQ, "M_FuncPropRead.req" }, + { CEMI_M_FUNCPROP_CON, "M_FuncProp.con" }, + { CEMI_M_RESET_REQ, "M_Reset.req" }, + { CEMI_M_RESET_IND, "M_Reset.ind" }, + { 0, NULL } +}; + +/* Property access flags +*/ +#define PA_RESPONSE 0x01 +#define PA_DATA 0x02 + +/* Additional Info Element Type +*/ +static const value_string aiet_vals[] = { + { 1, "PL Medium Info" }, + { 2, "RF Medium Info" }, + { 3, "BusMonitor Status Info" }, + { 4, "Timestamp Relative" }, + { 5, "Time Delay Until Sending" }, + { 6, "Extended Relative Timestamp" }, + { 7, "BiBat Info" }, + { 0, NULL } +}; + +/* Frame Type +*/ +static const value_string ft_vals[] = { + { 0, "Extended" }, + { 1, "Standard" }, + { 0, NULL } +}; + +/* Repeat on error? +*/ +static const value_string rep_vals[] = { + { 0, "Yes" }, + { 1, "No" }, + { 0, NULL } +}; + +/* Broadcast Type +*/ +static const value_string bt_vals[] = { + { 0, "System" }, + { 1, "Domain" }, + { 0, NULL } +}; + +/* Priority +*/ +static const value_string prio_vals[] = { + { 0, "System" }, + { 2, "Urgent" }, + { 1, "Normal" }, + { 3, "Low" }, + { 0, NULL } +}; + +/* Ack requested? +*/ +static const value_string ack_vals[] = { + { 0, "No" }, + { 1, "Yes" }, + { 0, NULL } +}; + +/* Confirmation Error? +*/ +static const value_string ce_vals[] = { + { 0, "No" }, + { 1, "Yes" }, + { 0, NULL } +}; + +/* Address Type +*/ +static const value_string at_vals[] = { + { 0, "Individual" }, + { 1, "Group" }, + { 0, NULL } +}; + +/* Packet Type +*/ +static const value_string pt_vals[] = { + { 0, "Data" }, + { 1, "Control" }, + { 0, NULL } +}; + +/* Sequence Type +*/ +static const value_string st_vals[] = { + { 0, "Unnumbered" }, + { 1, "Numbered" }, + { 0, NULL } +}; + +/* Transport Layer Code +*/ +static const value_string tc_vals[] = { + { 0, "Connect" }, + { 1, "Disconnect" }, + { 2, "ACK" }, + { 3, "NAK" }, + { 0, NULL } +}; + +/* Application Layer Code +*/ +#define AC_GroupValueRead 0 +#define AC_GroupValueResp 1 +#define AC_GroupValueWrite 2 +#define AC_IndAddrWrite 3 +#define AC_IndAddrRead 4 +#define AC_IndAddrResp 5 +#define AC_AdcRead 6 +#define AC_AdcResp 7 +#define AC_MemRead 8 +#define AC_MemResp 9 +#define AC_MemWrite 10 +#define AC_UserMsg 11 +#define AC_DevDescrRead 12 +#define AC_DevDescrResp 13 +#define AC_Restart 14 +#define AC_Escape 15 + +static const value_string ac_vals[] = +{ + { AC_GroupValueRead, "GroupValueRead" }, + { AC_GroupValueResp, "GroupValueResp" }, + { AC_GroupValueWrite, "GroupValueWrite" }, + { AC_IndAddrWrite, "IndAddrWrite" }, + { AC_IndAddrRead, "IndAddrRead" }, + { AC_IndAddrResp, "IndAddrResp" }, + { AC_AdcRead, "AdcRead" }, + { AC_AdcResp, "AdcResp" }, + { AC_MemRead, "MemRead" }, + { AC_MemResp, "MemResp" }, + { AC_MemWrite, "MemWrite" }, + { AC_UserMsg, "UserMsg" }, + { AC_DevDescrRead, "DevDescrRead" }, + { AC_DevDescrResp, "DevDescrResp" }, + { AC_Restart, "Restart" }, + { AC_Escape, "Escape" }, + { 0, NULL } +}; + +/* Extended AL codes +*/ +#define AX_SysNwkParamRead 0x1C8 +#define AX_SysNwkParamResp 0x1C9 +#define AX_SysNwkParamWrite 0x1CA +#define AX_PropExtValueRead 0x1CC +#define AX_PropExtValueResp 0x1CD +#define AX_PropExtValueWriteCon 0x1CE +#define AX_PropExtValueWriteConRes 0x1CF +#define AX_PropExtValueWriteUnCon 0x1D0 +#define AX_PropExtValueInfoReport 0x1D1 +#define AX_PropExtDescrRead 0x1D2 +#define AX_PropExtDescrResp 0x1D3 +#define AX_FuncPropExtCmd 0x1D4 +#define AX_FuncPropExtRead 0x1D5 +#define AX_FuncPropExtResp 0x1D6 +#define AX_MemExtWrite 0x1FB +#define AX_MemExtWriteResp 0x1FC +#define AX_MemExtRead 0x1FD +#define AX_MemExtReadResp 0x1FE +#define AX_UserMemRead 0x2C0 +#define AX_UserMemResp 0x2C1 +#define AX_UserMemWrite 0x2C2 +#define AX_UserMemBitWrite 0x2C4 +#define AX_UserMfrInfoRead 0x2C5 +#define AX_UserMfrInfoResp 0x2C6 +#define AX_FuncPropCmd 0x2C7 +#define AX_FuncPropRead 0x2C8 +#define AX_FuncPropResp 0x2C9 +#define AX_Restart 0x380 +#define AX_RestartReq 0x381 +#define AX_RestartResp 0x3A1 +#define AX_RoutingTableOpen 0x3C0 +#define AX_RoutingTableRead 0x3C1 +#define AX_RoutingTableResp 0x3C2 +#define AX_RoutingTableWrite 0x3C3 +#define AX_RouterMemRead 0x3C8 +#define AX_RouterMemResp 0x3C9 +#define AX_RouterMemWrite 0x3CA +#define AX_RouterStatusRead 0x3CD +#define AX_RouterStatusResp 0x3CE +#define AX_RouterStatusWrite 0x3CF +#define AX_MemBitWrite 0x3D0 +#define AX_AuthReq 0x3D1 +#define AX_AuthResp 0x3D2 +#define AX_KeyWrite 0x3D3 +#define AX_KeyResp 0x3D4 +#define AX_PropValueRead 0x3D5 +#define AX_PropValueResp 0x3D6 +#define AX_PropValueWrite 0x3D7 +#define AX_PropDescrRead 0x3D8 +#define AX_PropDescrResp 0x3D9 +#define AX_NwkParamRead 0x3DA +#define AX_NwkParamResp 0x3DB +#define AX_IndAddrSerNumRead 0x3DC +#define AX_IndAddrSerNumResp 0x3DD +#define AX_IndAddrSerNumWrite 0x3DE +#define AX_DomAddrWrite 0x3E0 +#define AX_DomAddrRead 0x3E1 +#define AX_DomAddrResp 0x3E2 +#define AX_DomAddrSelRead 0x3E3 +#define AX_NwkParamWrite 0x3E4 +#define AX_LinkRead 0x3E5 +#define AX_LinkResp 0x3E6 +#define AX_LinkWrite 0x3E7 +#define AX_GroupPropValueRead 0x3E8 +#define AX_GroupPropValueResp 0x3E9 +#define AX_GroupPropValueWrite 0x3EA +#define AX_GroupPropValueInfo 0x3EB +#define AX_DomAddrSerNumRead 0x3EC +#define AX_DomAddrSerNumResp 0x3ED +#define AX_DomAddrSerNumWrite 0x3EE +#define AX_FileStreamInfo 0x3F0 +#define AX_DataSec 0x3F1 + +static const value_string ax_vals[] = +{ + { AX_SysNwkParamRead, "SysNwkParamRead" }, + { AX_SysNwkParamResp, "SysNwkParamResp" }, + { AX_SysNwkParamWrite, "SysNwkParamWrite" }, + { AX_PropExtValueRead, "PropExtValueRead" }, + { AX_PropExtValueResp, "PropExtValueResp" }, + { AX_PropExtValueWriteCon, "PropExtValueWriteCon" }, + { AX_PropExtValueWriteConRes, "PropExtValueWriteConRes" }, + { AX_PropExtValueWriteUnCon, "PropExtValueWriteUnCon" }, + { AX_PropExtDescrRead, "PropExtDescrRead" }, + { AX_PropExtDescrResp, "PropExtDescrResp" }, + { AX_FuncPropExtCmd, "FuncPropExtCmd" }, + { AX_FuncPropExtRead, "FuncPropExtRead" }, + { AX_FuncPropExtResp, "FuncPropExtResp" }, + { AX_MemExtWrite, "MemExtWrite" }, + { AX_MemExtWriteResp, "MemExtWriteResp" }, + { AX_MemExtRead, "MemExtRead" }, + { AX_MemExtReadResp, "MemExtReadResp" }, + { AX_UserMemRead, "UserMemRead" }, + { AX_UserMemResp, "UserMemResp" }, + { AX_UserMemWrite, "UserMemWrite" }, + { AX_UserMemBitWrite, "UserMemBitWrite" }, + { AX_UserMfrInfoRead, "UserMfrInfoRead" }, + { AX_UserMfrInfoResp, "UserMfrInfoResp" }, + { AX_FuncPropCmd, "FuncPropCmd" }, + { AX_FuncPropRead, "FuncPropRead" }, + { AX_FuncPropResp, "FuncPropResp" }, + { AX_Restart, "Restart" }, + { AX_RestartReq, "RestartReq" }, + { AX_RestartResp, "RestartResp" }, + { AX_RoutingTableOpen, "RoutingTableOpen" }, + { AX_RoutingTableRead, "RoutingTableRead" }, + { AX_RoutingTableResp, "RoutingTableResp" }, + { AX_RoutingTableWrite, "RoutingTableWrite" }, + { AX_RouterMemRead, "RouterMemRead" }, + { AX_RouterMemResp, "RouterMemResp" }, + { AX_RouterMemWrite, "RouterMemWrite" }, + { AX_RouterStatusRead, "RouterStatusRead" }, + { AX_RouterStatusResp, "RouterStatusResp" }, + { AX_RouterStatusWrite, "RouterStatusWrite" }, + { AX_MemBitWrite, "MemBitWrite" }, + { AX_AuthReq, "AuthReq" }, + { AX_AuthResp, "AuthResp" }, + { AX_KeyWrite, "KeyWrite" }, + { AX_KeyResp, "KeyResp" }, + { AX_PropValueRead, "PropValueRead" }, + { AX_PropValueResp, "PropValueResp" }, + { AX_PropValueWrite, "PropValueWrite" }, + { AX_PropDescrRead, "PropDescrRead" }, + { AX_PropDescrResp, "PropDescrResp" }, + { AX_NwkParamRead, "NwkParamRead" }, + { AX_NwkParamResp, "NwkParamResp" }, + { AX_IndAddrSerNumRead, "IndAddrSerNumRead" }, + { AX_IndAddrSerNumResp, "IndAddrSerNumResp" }, + { AX_IndAddrSerNumWrite, "IndAddrSerNumWrite" }, + { AX_DomAddrWrite, "DomAddrWrite" }, + { AX_DomAddrRead, "DomAddrRead" }, + { AX_DomAddrResp, "DomAddrResp" }, + { AX_DomAddrSelRead, "DomAddrSelRead" }, + { AX_NwkParamWrite, "NwkParamWrite" }, + { AX_LinkRead, "LinkRead" }, + { AX_LinkResp, "LinkResp" }, + { AX_LinkWrite, "LinkWrite" }, + { AX_GroupPropValueRead, "GroupPropValueRead" }, + { AX_GroupPropValueResp, "GroupPropValueResp" }, + { AX_GroupPropValueWrite, "GroupPropValueWrite" }, + { AX_GroupPropValueInfo, "GroupPropValueInfo" }, + { AX_DomAddrSerNumRead, "DomAddrSerNumRead" }, + { AX_DomAddrSerNumResp, "DomAddrSerNumResp" }, + { AX_DomAddrSerNumWrite, "DomAddrSerNumWrite" }, + { AX_FileStreamInfo, "FileStreamInfo" }, + { AX_DataSec, "DataSec" }, + { 0, NULL } +}; + +/* SCF (Security Control Field) +*/ +static const value_string scf_vals[] = +{ + { 0x00, "CCM S-A_Data with Authentication-only" }, + { 0x10, "CCM S-A_Data with Authentication+Confidentiality" }, + { 0x12, "CCM S-A_Sync_Req with Authentication+Confidentiality" }, + { 0x13, "CCM S-A_Sync_Res with Authentication+Confidentiality" }, + { 0x08, "CCM S-A_Data with Authentication-only, System Broadcast" }, + { 0x18, "CCM S-A_Data with Authentication+Confidentiality, System Broadcast" }, + { 0x1a, "CCM S-A_Sync_Req with Authentication+Confidentiality, System Broadcast" }, + { 0x1b, "CCM S-A_Sync_Res with Authentication+Confidentiality, System Broadcast" }, + { 0x80, "CCM S-A_Data with Authentication-only, Tool Access" }, + { 0x90, "CCM S-A_Data with Authentication+Confidentiality, Tool Access" }, + { 0x92, "CCM S-A_Sync_Req with Authentication+Confidentiality, Tool Access" }, + { 0x93, "CCM S-A_Sync_Res with Authentication+Confidentiality, Tool Access" }, + { 0x88, "CCM S-A_Data with Authentication-only, System Broadcast, Tool Access" }, + { 0x98, "CCM S-A_Data with Authentication+Confidentiality, Tool Access, System Broadcast" }, + { 0x9a, "CCM S-A_Sync_Req with Authentication+Confidentiality, Tool Access, System Broadcast" }, + { 0x9b, "CCM S-A_Sync_Res with Authentication+Confidentiality, Tool Access, System Broadcast" }, + { 0, NULL } +}; + +/* SCF (Security Control Field). +*/ +static const value_string scf_short_vals[] = +{ + { 0x00, "Data+A" }, + { 0x10, "Data+A+C" }, + { 0x12, "SyncReq" }, + { 0x13, "SyncRes" }, + { 0x08, "Data+A+SBC" }, + { 0x18, "Data+A+C+SBC" }, + { 0x1a, "SyncReq+SBC" }, + { 0x1b, "SyncRes+SBC" }, + { 0x80, "Data+A+T" }, + { 0x90, "Data+A+C+T" }, + { 0x92, "SyncReq+T" }, + { 0x93, "SyncRes+T" }, + { 0x88, "Data+A+T+SBC" }, + { 0x98, "Data+A+C+T+SBC" }, + { 0x9a, "SyncReq+T+SBC" }, + { 0x9b, "SyncRes+T+SBC" }, + { 0, NULL } +}; + +/* SCF.SAI (Security Algorithm Identifier) +*/ +static const value_string scf_sai_vals[] = +{ + { 0, "CCM A" }, + { 1, "CCM A+S" }, + { 0, NULL } +}; + +/* SCF.Service +*/ +static const value_string scf_svc_vals[] = +{ + { 0, "Data" }, + { 2, "Sync_Req" }, + { 3, "Sync_Res" }, + { 0, NULL } +}; + +/* See KNX documents: +* "03_07_03 Standardized Identifier Tables v01.03.01 AS" +* "03_05_01 Resources v01.09.03 AS" +*/ + +/* Property Data Types +* See "4 Property Datatypes Identifiers" in "03_07_03 Standardized Identifier Tables v01.03.01 AS" +*/ +static const value_string pdt_vals[] = { + { 0x00, "PDT_CONTROL" }, + { 0x01, "PDT_CHAR" }, + { 0x02, "PDT_UNSIGNED_CHAR" }, + { 0x03, "PDT_INT" }, + { 0x04, "PDT_UNSIGNED_INT" }, + { 0x05, "PDT_KNX_FLOAT" }, + { 0x06, "PDT_DATE" }, + { 0x07, "PDT_TIME" }, + { 0x08, "PDT_LONG" }, + { 0x09, "PDT_UNSIGNED_LONG" }, + { 0x0A, "PDT_FLOAT" }, + { 0x0B, "PDT_DOUBLE" }, + { 0x0C, "PDT_CHAR_BLOCK" }, + { 0x0D, "PDT_POLL_GROUP_SETTINGS" }, + { 0x0E, "PDT_SHORT_CHAR_BLOCK" }, + { 0x0F, "PDT_DATE_TIME" }, + { 0x10, "PDT_VARIABLE_LENGTH" }, + { 0x11, "PDT_GENERIC_01" }, + { 0x12, "PDT_GENERIC_02" }, + { 0x13, "PDT_GENERIC_03" }, + { 0x14, "PDT_GENERIC_04" }, + { 0x15, "PDT_GENERIC_05" }, + { 0x16, "PDT_GENERIC_06" }, + { 0x17, "PDT_GENERIC_07" }, + { 0x18, "PDT_GENERIC_08" }, + { 0x19, "PDT_GENERIC_09" }, + { 0x1A, "PDT_GENERIC_10" }, + { 0x1B, "PDT_GENERIC_11" }, + { 0x1C, "PDT_GENERIC_12" }, + { 0x1D, "PDT_GENERIC_13" }, + { 0x1E, "PDT_GENERIC_14" }, + { 0x1F, "PDT_GENERIC_15" }, + { 0x20, "PDT_GENERIC_16" }, + { 0x21, "PDT_GENERIC_17" }, + { 0x22, "PDT_GENERIC_18" }, + { 0x23, "PDT_GENERIC_19" }, + { 0x24, "PDT_GENERIC_20" }, + { 0x2F, "PDT_UTF-8" }, + { 0x30, "PDT_VERSION" }, + { 0x31, "PDT_ALARM_INFO" }, + { 0x32, "PDT_BINARY_INFORMATION" }, + { 0x33, "PDT_BITSET8" }, + { 0x34, "PDT_BITSET16" }, + { 0x35, "PDT_ENUM8" }, + { 0x36, "PDT_SCALING" }, + { 0x3C, "PDT_NE_VL" }, + { 0x3D, "PDT_NE_FL" }, + { 0x3E, "PDT_FUNCTION" }, + { 0x3F, "PDT_ESCAPE" }, + { 0, NULL } +}; + +/* Interface Object Types +* See "2 Interface Object Types" in "03_07_03 Standardized Identifier Tables v01.03.01 AS" +*/ +static const value_string ot_vals[] = { + { 0, "Device" }, + { 1, "Address Table" }, + { 2, "Association Table" }, + { 3, "Application Program" }, + { 4, "Interface Program" }, + { 5, "KNX-Object Association Table" }, + { 6, "Router" }, + { 7, "LTE Address Routing Table" }, + { 8, "cEMI Server" }, + { 9, "Group Object Table" }, + { 10, "Polling Master" }, + { 11, "KNXnet/IP Parameter" }, + { 13, "File Server" }, + { 17, "Data Security" }, + { 0, NULL } +}; + +/* IOT independent PIDs +* See "3.2 Interface Object Type independent standard Properties" in "03_07_03 Standardized Identifier Tables v01.03.01 AS" +* See "4.2 Interface Object Type independent Properties" in "03_05_01 Resources v01.09.03 AS" +*/ +static const value_string pid_vals[] = { + { 1, "PID_OBJECT_TYPE" }, + { 2, "PID_OBJECT_NAME" }, + { 3, "PID_SEMAPHOR" }, + { 4, "PID_GROUP_OBJECT_REFERENCE" }, + { 5, "PID_LOAD_STATE_CONTROL" }, + { 6, "PID_RUN_STATE_CONTROL" }, + { 7, "PID_TABLE_REFERENCE" }, + { 8, "PID_SERVICE_CONTROL" }, + { 9, "PID_FIRMWARE_REVISION" }, + { 10, "PID_SERVICES_SUPPORTED" }, + { 11, "PID_SERIAL_NUMBER" }, + { 12, "PID_MANUFACTURER_ID" }, + { 13, "PID_PROGRAM_VERSION" }, + { 14, "PID_DEVICE_CONTROL" }, + { 15, "PID_ORDER_INFO" }, + { 16, "PID_PEI_TYPE" }, + { 17, "PID_PORT_CONFIGURATION" }, + { 18, "PID_POLL_GROUP_SETTINGS" }, + { 19, "PID_MANUFACTURER_DATA" }, + { 21, "PID_DESCRIPTION" }, + { 23, "PID_TABLE" }, + { 24, "PID_ENROL" }, + { 25, "PID_VERSION" }, + { 26, "PID_GROUP_OBJECT_LINK" }, + { 27, "PID_MCB_TABLE" }, + { 28, "PID_ERROR_CODE" }, + { 29, "PID_OBJECT_INDEX" }, + { 30, "PID_DOWNLOAD_COUNTER" }, + { 0, NULL } +}; + +/* PIDs for IOT = 0 (Device) +* See "3.3.1 Device Object Interface Object (Object Type = 0)" in "03_07_03 Standardized Identifier Tables v01.03.01 AS" +* See "4.3 Device Object (Object Type 0)" in "03_05_01 Resources v01.09.03 AS" +*/ +static const value_string pid0_vals[] = { + { 51, "PID_ROUTING_COUNT" }, + { 52, "PID_MAX_RETRY_COUNT" }, + { 53, "PID_ERROR_FLAGS" }, + { 54, "PID_PROGMODE" }, + { 55, "PID_PRODUCT_ID" }, + { 56, "PID_MAX_APDULENGTH" }, + { 57, "PID_SUBNET_ADDR" }, + { 58, "PID_DEVICE_ADDR" }, + { 59, "PID_PB_CONFIG" }, + { 60, "PID_ADDR_REPORT" }, + { 61, "PID_ADDR_CHECK" }, + { 62, "PID_OBJECT_VALUE" }, + { 63, "PID_OBJECTLINK" }, + { 64, "PID_APPLICATION" }, + { 65, "PID_PARAMETER" }, + { 66, "PID_OBJECTADDRESS" }, + { 67, "PID_PSU_TYPE" }, + { 68, "PID_PSU_STATUS" }, + { 69, "PID_PSU_ENABLE" }, + { 70, "PID_DOMAIN_ADDRESS" }, + { 71, "PID_IO_LIST" }, + { 72, "PID_MGT_DESCRIPTOR_01" }, + { 73, "PID_PL110_PARAM" }, + { 74, "PID_RF_REPEAT_COUNTER" }, + { 75, "PID_RECEIVE_BLOCK_TABLE" }, + { 76, "PID_RANDOM_PAUSE_TABLE" }, + { 77, "PID_RECEIVE_BLOCK_NR" }, + { 78, "PID_HARDWARE_TYPE" }, + { 79, "PID_RETRANSMITTER_NUMBER" }, + { 80, "PID_SERIAL_NR_TABLE" }, + { 81, "PID_BIBATMASTER_ADDRESS" }, + { 82, "PID_RF_DOMAIN_ADDRESS" }, + { 83, "PID_DEVICE_DESCRIPTOR" }, + { 84, "PID_METERING_FILTER_TABLE" }, + { 85, "PID_GROUP_TELEGR_RATE_LIMIT_TIME_BASE" }, + { 86, "PID_GROUP_TELEGR_RATE_LIMIT_NO_OF_TELEGR" }, + { 0, NULL } +}; + +/* PIDs for IOT = 1 (Address Table) +* See "4.10.6 Group Address Table - Realisation Type 6" in "03_05_01 Resources v01.09.03 AS" +* See "4.10.7 Group Address Table - Realisation Type 7" in "03_05_01 Resources v01.09.03 AS" +*/ +static const value_string pid1_vals[] = { + { 51, "PID_EXT_FRAMEFORMAT" }, + { 52, "PID_ADDRTAB1" }, + { 53, "PID_GROUP_RESPONSER_TABLE" }, + { 0, NULL } +}; + +/* PIDs for IOT = 6 (Router) +* See "4.4 Router Object (Object Type 6)" in "03_05_01 Resources v01.09.03 AS" +* See "2.4.4 Router Object" in "AN161 v05 Coupler Model 2.0 AS" +*/ +static const value_string pid6_vals[] = { + { 51, "PID_MEDIUM_STATUS" }, /* alias "PID_LINE_STATUS" */ + { 52, "PID_MAIN_LCCONFIG" }, + { 53, "PID_SUB_LCCONFIG" }, + { 54, "PID_MAIN_LCGRPCONFIG" }, + { 55, "PID_SUB_LCGRPCONFIG" }, + { 56, "PID_ROUTETABLE_CONTROL" }, + { 57, "PID_COUPL_SERV_CONTROL" }, + { 58, "PID_MAX_APDU_LENGTH" }, + { 59, "PID_L2_COUPLER_TYPE" }, + { 61, "PID_HOP_COUNT" }, + { 63, "PID_MEDIUM" }, + { 67, "PID_FILTER_TABLE_USE" }, + { 104, "PID_PL110_SBC_CONTROL" }, + { 105, "PID_PL110_DOA" }, + { 112, "PID_RF_SBC_CONTROL" }, + { 0, NULL } +}; + +/* PIDs for IOT = 7 (LTE Address Routing Table) +* See "4.5 LTE Address Routing Table Object (Object Type 7)" in "03_05_01 Resources v01.09.03 AS" +*/ +static const value_string pid7_vals[] = { + { 51, "PID_LTE_ROUTESELECT" }, + { 52, "PID_LTE_ROUTETABLE" }, + { 0, NULL } +}; + +/* PIDs for IOT = 8 (cEMI Server) +* See "4.6 cEMI Server Object (Object Type 8)" in "03_05_01 Resources v01.09.03 AS" +*/ +static const value_string pid8_vals[] = { + { 51, "PID_MEDIUM_TYPE" }, + { 52, "PID_COMM_MODE" }, + { 53, "PID_MEDIUM_AVAILABILITY" }, + { 54, "PID_ADD_INFO_TYPES" }, + { 55, "PID_TIME_BASE" }, + { 56, "PID_TRANSP_ENABLE" }, + { 59, "PID_BIBAT_NEXTBLOCK" }, + { 60, "PID_RF_MODE_SELECT" }, + { 61, "PID_RF_MODE_SUPPORT" }, + { 62, "PID_RF_FILTERING_MODE_SELECT" }, + { 63, "PID_RF_FILTERING_MODE_SUPPORT" }, + { 0, NULL } +}; + +/* PIDs for IOT = 9 (Group Object Table) +* See "4.12.4 Group Object Table - Realisation Type 6" in "03_05_01 Resources v01.09.03 AS" +*/ +static const value_string pid9_vals[] = { + { 51, "PID_GRPOBJTABLE" }, + { 52, "PID_EXT_GRPOBJREFERENCE" }, + { 0, NULL } +}; + +/* PIDs for IOT = 11 (KNXnet/IP Parameter), +* See "2.5 KNXnet/IP Parameter Object" in "03_08_03 Management v01.06.02 AS" +* See "2.3.1 KNXnet/IP Parameter Object" in "AN159 v06 KNXnet-IP Secure AS" +*/ +static const value_string pid11_vals[] = { + { 51, "PID_PROJECT_INSTALLATION_ID" }, + { 52, "PID_KNX_INDIVIDUAL_ADDRESS" }, + { 53, "PID_ADDITIONAL_INDIVIDUAL_ADDRESSES" }, + { 54, "PID_CURRENT_IP_ASSIGNMENT_METHOD" }, + { 55, "PID_IP_ASSIGNMENT_METHOD" }, + { 56, "PID_IP_CAPABILITIES" }, + { 57, "PID_CURRENT_IP_ADDRESS" }, + { 58, "PID_CURRENT_SUBNET_MASK" }, + { 59, "PID_CURRENT_DEFAULT_GATEWAY" }, + { 60, "PID_IP_ADDRESS" }, + { 61, "PID_SUBNET_MASK" }, + { 62, "PID_DEFAULT_GATEWAY" }, + { 63, "PID_DHCP_BOOTP_SERVER" }, + { 64, "PID_MAC_ADDRESS" }, + { 65, "PID_SYSTEM_SETUP_MULTICAST_ADDRESS" }, + { 66, "PID_ROUTING_MULTICAST_ADDRESS" }, + { 67, "PID_TTL" }, + { 68, "PID_KNXNETIP_DEVICE_CAPABILITIES" }, + { 69, "PID_KNXNETIP_DEVICE_STATE" }, + { 70, "PID_KNXNETIP_ROUTING_CAPABILITIES" }, + { 71, "PID_PRIORITY_FIFO_ENABLED" }, + { 72, "PID_QUEUE_OVERFLOW_TO_IP" }, + { 73, "PID_QUEUE_OVERFLOW_TO_KNX" }, + { 74, "PID_MSG_TRANSMIT_TO_IP" }, + { 75, "PID_MSG_TRANSMIT_TO_KNX" }, + { 76, "PID_FRIENDLY_NAME" }, + { 78, "PID_ROUTING_BUSY_WAIT_TIME" }, + { 91, "PID_BACKBONE_KEY" }, + { 92, "PID_DEVICE_AUTHENTICATION_CODE" }, + { 93, "PID_PASSWORD_HASHES" }, + { 94, "PID_SECURED_SERVICE_FAMILIES" }, + { 95, "PID_MULTICAST_LATENCY_TOLERANCE" }, + { 96, "PID_SYNC_LATENCY_FRACTION" }, + { 97, "PID_TUNNELLING_USERS" }, + { 0, NULL } +}; + +/* PIDs for IOT = 17 (Security) +* See "2.3.5 Security Interface Object" in "KSG638-26.03 KNX Data Security" +*/ +static const value_string pid17_vals[] = { + { 51, "PID_SECURITY_MODE" }, + { 52, "PID_P2P_KEY_TABLE" }, + { 53, "PID_GRP_KEY_TABLE" }, + { 54, "PID_SECURITY_INDIVIDUAL_ADDRESS_TABLE" }, + { 55, "PID_SECURITY_FAILURES_LOG" }, + { 56, "PID_TOOL_KEY" }, + { 57, "PID_SECURITY_REPORT" }, + { 58, "PID_SECURITY_REPORT_CONTROL" }, + { 59, "PID_SEQUENCE_NUMBER_SENDING" }, + { 60, "PID_ZONE_KEY_TABLE" }, + { 61, "PID_GO_SECURITY_FLAGS" }, + { 62, "PID_ROLE_TABLE" }, + { 0, NULL } +}; + +/* - - - - - - - - - H E L P E R F U N C T I O N S - - - - - - - - - - +*/ + +/* Add raw data to list view, tree view, and parent folder +*/ +static proto_item* proto_tree_add_data( proto_tree* tree, tvbuff_t* tvb, gint offset, gint length, column_info* cinfo, proto_item* item, + const gchar* name, const gchar* text1, const gchar* text2 ) +{ + proto_item* new_item = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, length, NULL, "%s: $", name ); + if( text1 ) col_append_str( cinfo, COL_INFO, text1 ); + if( text2 ) proto_item_append_text( item, "%s", text2 ); + + while( length > 0 ) + { + guint8 value = tvb_get_guint8( tvb, offset ); + if( text1 ) col_append_fstr( cinfo, COL_INFO, "%02X", value ); + if( text2 ) proto_item_append_text( item, "%02X", value ); + proto_item_append_text( new_item, " %02X", value ); + offset++; + length--; + } + + return new_item; +} + +static const gchar* get_pid_name( gint ot, gint pid ) +{ + if( pid <= 50 ) + { + return try_val_to_str( pid, pid_vals ); + } + { + const value_string* vals = NULL; + switch( ot ) + { + case 0: + vals = pid0_vals; + break; + case 1: + vals = pid1_vals; + break; + case 6: + vals = pid6_vals; + break; + case 7: + vals = pid7_vals; + break; + case 8: + vals = pid8_vals; + break; + case 9: + vals = pid9_vals; + break; + case 11: + vals = pid11_vals; + break; + case 17: + vals = pid17_vals; + break; + } + if( vals ) + { + return try_val_to_str( pid, vals ); + } + } + return NULL; +} + +/* Decrypt data security APDU with a specific key. +*/ +static const guint8* decrypt_data_security_data_with_key( const guint8* key, const guint8* encrypted, gint encrypted_size, const guint8* cemi, gint cemi_size ) +{ + guint8 ctr_0[ 16 ]; + guint8 b_0[ 16 ]; + guint8 mac[ 16 ]; + guint8* a_bytes = 0; + const guint8* p_bytes = NULL; + gint a_length = 0; + gint p_length = 0; + + guint8* decrypted = NULL; + + if( encrypted_size > 4 ) // contains 4 bytes MAC + { + if( cemi_size >= 2 ) + { + gint additionalInfoLength = cemi[ 1 ]; + gint offsetToData = additionalInfoLength + 11; + if( offsetToData + 6 <= cemi_size ) + { + /* 1 byte Security Control Field */ + guint8 scf = cemi[ offsetToData ]; + + // Get A and P. + if( (scf & 0x30) == 0x10 ) // A+C + { + p_bytes = encrypted; + p_length = encrypted_size - 4; + } + + // Build b_0. + b_0[ 0 ] = cemi[ offsetToData + 1 ]; // SeqNr + b_0[ 1 ] = cemi[ offsetToData + 2 ]; + b_0[ 2 ] = cemi[ offsetToData + 3 ]; + b_0[ 3 ] = cemi[ offsetToData + 4 ]; + b_0[ 4 ] = cemi[ offsetToData + 5 ]; + b_0[ 5 ] = cemi[ offsetToData + 6 ]; + b_0[ 6 ] = cemi[ additionalInfoLength + 4 ]; // SA + b_0[ 7 ] = cemi[ additionalInfoLength + 5 ]; + b_0[ 8 ] = cemi[ additionalInfoLength + 6 ]; // DA + b_0[ 9 ] = cemi[ additionalInfoLength + 7 ]; + b_0[ 10 ] = 0; // cemi[additionalInfoLength + 2] & 0x80; // FT + b_0[ 11 ] = cemi[ additionalInfoLength + 3 ] & 0x8F; // AT (AT+EFF) + b_0[ 12 ] = cemi[ additionalInfoLength + 9 ]; // TPCI + ApciSec + b_0[ 13 ] = cemi[ additionalInfoLength + 10 ]; + b_0[ 14 ] = 0; + b_0[ 15 ] = (guint8) p_length; + + // Build ctr_0. + ctr_0[ 0 ] = cemi[ offsetToData + 1 ]; // SeqNr + ctr_0[ 1 ] = cemi[ offsetToData + 2 ]; + ctr_0[ 2 ] = cemi[ offsetToData + 3 ]; + ctr_0[ 3 ] = cemi[ offsetToData + 4 ]; + ctr_0[ 4 ] = cemi[ offsetToData + 5 ]; + ctr_0[ 5 ] = cemi[ offsetToData + 6 ]; + ctr_0[ 6 ] = cemi[ additionalInfoLength + 4 ]; // SA + ctr_0[ 7 ] = cemi[ additionalInfoLength + 5 ]; + ctr_0[ 8 ] = cemi[ additionalInfoLength + 6 ]; // DA + ctr_0[ 9 ] = cemi[ additionalInfoLength + 7 ]; + ctr_0[ 10 ] = 0; + ctr_0[ 11 ] = 0; + ctr_0[ 12 ] = 0; + ctr_0[ 13 ] = 0; + ctr_0[ 14 ] = 0x01; + ctr_0[ 15 ] = 0; + + decrypted = knx_ccm_encrypt( 0, key, p_bytes, p_length, encrypted + encrypted_size - 4, 4, ctr_0, 4 ); + + a_bytes = (guint8*) wmem_alloc( wmem_packet_scope(), encrypted_size ); + if( (scf & 0x30) == 0x10 ) // A+C + { + a_bytes[ 0 ] = scf; + a_length = 1; + p_bytes = decrypted; + p_length = encrypted_size - 4; + } + else if( (scf & 0x30) == 0x00 ) // A + { + a_bytes[ 0 ] = scf; + memcpy( a_bytes + 1, decrypted, encrypted_size - 4 ); + a_length = encrypted_size - 3; + } + + knx_ccm_calc_cbc_mac( mac, key, a_bytes, a_length, p_bytes, p_length, b_0 ); + wmem_free( wmem_packet_scope(), a_bytes ); + + if( memcmp( mac, decrypted + p_length, 4 ) != 0 ) + { + // Wrong mac. Return 0. + wmem_free( wmem_packet_scope(), decrypted ); + decrypted = NULL; + } + } + } + } + + return decrypted; +} + +/* Context info for decrypt_data_security_data +*/ +struct data_security_info +{ + guint16 source; // KNX source address + guint16 dest; // KNX source address + guint8 multicast; // KNX multicast (group addressed)? + guint64 seq_nr; // 6-byte data security sequence number + gchar output_text[ 128 ]; // buffer for diagnostic output text +}; + +/* Decrypt data security APDU. +*/ +static const guint8* decrypt_data_security_data( const guint8* encrypted, gint encrypted_size, const guint8* cemi, gint cemi_size, struct data_security_info* info ) +{ + const guint8* key = NULL; + const guint8* decrypted = NULL; + guint8 keys_found = 0; + + // Get context info + guint16 source = info->source; + guint16 dest = info->dest; + guint8 multicast = info->multicast; + + gchar* output = info->output_text; + gint output_max = sizeof info->output_text; + g_snprintf( output, output_max, "with " ); + while( *output ) { ++output; --output_max; } + + // Try keys from keyring.XML + if( multicast ) + { + // Try keys associated with GA + struct knx_keyring_ga_keys* ga_key; + + for( ga_key = knx_keyring_ga_keys; ga_key; ga_key = ga_key->next ) + { + if( ga_key->ga == dest ) + { + keys_found = 1; + key = ga_key->key; + decrypted = decrypt_data_security_data_with_key( key, encrypted, encrypted_size, cemi, cemi_size ); + + if( decrypted ) + { + g_snprintf( output, output_max, "GA " ); + while( *output ) { ++output; --output_max; } + break; + } + } + } + } + else + { + // Try keys associated with dest IA + struct knx_keyring_ia_keys* ia_key; + + for( ia_key = knx_keyring_ia_keys; ia_key; ia_key = ia_key->next ) + { + if( ia_key->ia == dest ) + { + keys_found = 1; + key = ia_key->key; + decrypted = decrypt_data_security_data_with_key( key, encrypted, encrypted_size, cemi, cemi_size ); + + if( decrypted ) + { + g_snprintf( output, output_max, "dest IA " ); + while( *output ) { ++output; --output_max; } + break; + } + } + } + } + + if( !decrypted ) + { + // Try keys associated with source IA + struct knx_keyring_ia_keys* ia_key; + + for( ia_key = knx_keyring_ia_keys; ia_key; ia_key = ia_key->next ) + { + if( ia_key->ia == source ) + { + keys_found = 1; + key = ia_key->key; + decrypted = decrypt_data_security_data_with_key( key, encrypted, encrypted_size, cemi, cemi_size ); + + if( decrypted ) + { + g_snprintf( output, output_max, "source IA " ); + while( *output ) { ++output; --output_max; } + break; + } + } + } + } + + if( !decrypted && knx_decryption_key_count ) + { + // Try all explicitly specified keys + guint8 key_index; + + for( key_index = 0; key_index < knx_decryption_key_count; ++key_index ) + { + keys_found = 1; + key = knx_decryption_keys[ key_index ]; + decrypted = decrypt_data_security_data_with_key( key, encrypted, encrypted_size, cemi, cemi_size ); + + if( decrypted ) + { + break; + } + } + } + + if( decrypted ) + { + guint8 count; + + g_snprintf( output, output_max, "key" ); + + for( count = 16; count; --count ) + { + while( *output ) { ++output; --output_max; } + g_snprintf( output, output_max, " %02X", *key++ ); + } + } + else + { + g_snprintf( info->output_text, sizeof info->output_text, keys_found ? "failed" : "no keys found" ); + } + + return decrypted; +} + +/* Dissect Object Index (1 byte) +*/ +static guint8 dissect_ox( tvbuff_t *tvb, packet_info *pinfo, proto_item *node, proto_tree *list, gint *p_offset, gint end_pos, guint8 *p_error ) +{ + gint offset = *p_offset; + + if( offset < end_pos ) + { + guint8 ox = tvb_get_guint8( tvb, offset ); + column_info *cinfo = pinfo->cinfo; + + col_append_fstr( cinfo, COL_INFO, " OX=%u", ox ); + proto_item_append_text( node, ", OX=%u", ox ); + proto_tree_add_item( list, hf_cemi_ox, tvb, offset, 1, ENC_BIG_ENDIAN ); + + *p_offset = ++offset; + return ox; + } + + node = proto_tree_add_debug_text( list, "? Object Index" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + + if( p_error ) + { + *p_error = 1; + } + + return 0; +} + +/* Dissect Object Type (2 bytes) +*/ +static guint16 dissect_ot( tvbuff_t *tvb, packet_info *pinfo, proto_item *node, proto_tree *list, gint *p_offset, gint end_pos, guint8 *p_error ) +{ + gint offset = *p_offset; + + if( offset + 1 < end_pos ) + { + guint16 ot = tvb_get_ntohs( tvb, offset ); + column_info *cinfo = pinfo->cinfo; + + col_append_fstr( cinfo, COL_INFO, " OT=%u", ot ); + proto_item_append_text( node, ", OT=%u", ot ); + + proto_tree_add_item( list, hf_cemi_ot, tvb, offset, 2, ENC_BIG_ENDIAN ); + + *p_offset = offset += 2; + return ot; + } + + node = proto_tree_add_bytes_format( list, hf_bytes, tvb, offset, end_pos - offset, NULL, "? Object Type" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 2 bytes" ); + + if( p_error ) + { + *p_error = 1; + } + + *p_offset = end_pos; + return 0; +} + +/* Dissect Property Identifier (1 byte) +*/ +static guint8 dissect_pid( tvbuff_t *tvb, packet_info *pinfo, proto_item *node, proto_tree *list, gint *p_offset, gint end_pos, gint ot, guint8 show, guint8 *p_error ) +{ + gint offset = *p_offset; + + if( offset < end_pos ) + { + guint8 pid = tvb_get_guint8( tvb, offset ); + column_info *cinfo = pinfo->cinfo; + const gchar* name; + + if( pid || show ) + { + col_append_fstr( cinfo, COL_INFO, " P=%u", pid ); + proto_item_append_text( node, ", PID=%u", pid ); + } + + if( list ) + { + node = proto_tree_add_item( list, hf_cemi_pid, tvb, offset, 1, ENC_BIG_ENDIAN ); + name = get_pid_name( ot, pid ); + if( name ) + { + proto_item_append_text( node, " = %s", name ); + } + } + + *p_offset = ++offset; + return pid; + } + + node = proto_tree_add_debug_text( list, "? Property ID" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + + if( p_error ) + { + *p_error = 1; + } + + return 0; +} + +/* Dissect Property Index (1 byte) +*/ +static guint8 dissect_px( tvbuff_t *tvb, packet_info *pinfo, proto_item *node, proto_tree *list, gint *p_offset, gint end_pos, guint8 show, guint8 *p_error ) +{ + gint offset = *p_offset; + + if( offset < end_pos ) + { + guint8 px = tvb_get_guint8( tvb, offset ); + + if( show ) + { + column_info *cinfo = pinfo->cinfo; + col_append_fstr( cinfo, COL_INFO, " PX=%u", px ); + proto_item_append_text( node, ", PX=%u", px ); + } + + proto_tree_add_item( list, hf_cemi_px, tvb, offset, 1, ENC_BIG_ENDIAN ); + + *p_offset = ++offset; + return px; + } + + node = proto_tree_add_debug_text( list, "? Property Index" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + + if( p_error ) + { + *p_error = 1; + } + + return 0; +} + +/* Dissect Property Range (2 bytes: Number Of Elements (4 bits), Start Index (12 bits)) + and subsequent Data +*/ +static void dissect_range( tvbuff_t *tvb, packet_info *pinfo, proto_item *node, proto_tree *list, gint *p_offset, gint end_pos, guint8 pa_flags, guint8 *p_error ) +{ + gint offset = *p_offset; + guint8 error = 0; + + if( offset + 1 >= end_pos ) + { + node = proto_tree_add_bytes_format( list, hf_bytes, tvb, offset, end_pos - offset, NULL, "? Range" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 2 bytes" ); + + *p_offset = end_pos; + error = 1; + } + else + { + column_info *cinfo = pinfo->cinfo; + proto_item *cemi_node = node; + guint16 sx = tvb_get_ntohs( tvb, offset ); + guint8 ne = sx >> 12; + sx &= 0x0FFF; + + /* 4 bits Number Of Elements */ + if( ne != 1 ) + { + col_append_fstr( cinfo, COL_INFO, " N=%u", ne ); + proto_item_append_text( node, ", N=%u", ne ); + + if( ne == 0 && !(pa_flags & PA_RESPONSE) ) + { + error = 1; + } + else if( sx == 0 ) + { + error = 2; + } + } + + /* 12 bits Start Index */ + if( sx != 1 ) + { + col_append_fstr( cinfo, COL_INFO, " X=%u", sx ); + proto_item_append_text( node, ", X=%u", sx ); + } + + if( list ) + { + proto_item *range_node = proto_tree_add_none_format( list, hf_folder, tvb, offset, 2, "Range: %u element%s at position %u", ne, (ne == 1) ? "" : "s", sx ); + proto_tree *range_list = proto_item_add_subtree( range_node, ett_cemi_range ); + + /* 4 bits Number Of Elements */ + node = proto_tree_add_item( range_list, hf_cemi_ne, tvb, offset, 1, ENC_BIG_ENDIAN ); + + if( error ) + { + proto_item_prepend_text( range_node, "? " ); + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, (error == 1) ? "Expected: >= 1 element(s)" : "Expected: 1 element" ); + } + + /* 12 bits Start Index */ + node = proto_tree_add_item( range_list, hf_cemi_sx, tvb, offset, 2, ENC_BIG_ENDIAN ); + } + + offset += 2; + + /* Data */ + { + gint length = end_pos - offset; + if( length > 0 ) + { + node = proto_tree_add_data( list, tvb, offset, length, cinfo, cemi_node, "Data", " $", ", $" ); + if( !pa_flags ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Unexpected" ); + error = 1; + } + else if( (pa_flags & PA_RESPONSE) && (!(pa_flags & PA_DATA) || ne == 0) && length != 1 ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: max 1 byte" ); + error = 1; + } + else if( pa_flags & PA_DATA ) + { + if( ne == 1 && sx == 0 && length != 2 ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 2 bytes" ); + error = 1; + } + else if( ne >= 2 && (length % ne) != 0 ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: multiple of %u bytes", ne ); + error = 1; + } + } + } + } + + *p_offset = end_pos; + } + + if( error && p_error ) + { + *p_error = 1; + } +} + +/* Dissect Property Description: PDT (1 byte), Max Count (2 bytes), Access Levels (1 byte) +*/ +static void dissect_prop_descr( tvbuff_t* tvb, packet_info* pinfo, proto_item* cemi_node, proto_tree* cemi_list, gint* p_offset, gint size, guint8* p_error ) +{ + gint offset = *p_offset; + column_info* cinfo = pinfo ? pinfo->cinfo : NULL; + guint8 error = 0; + + /* 4 bytes Property Description */ + if( offset + 4 > size ) + { + proto_item* node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? Property Description" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 4 bytes" ); + + error = 1; + offset = size; + } + else + { + /* 1 bit Writability, 1 bit reserved, 6 bits Property Data Type */ + guint8 pdt = tvb_get_guint8( tvb, offset ); + guint8 writable = (pdt & 0x80) != 0; + pdt &= 0x3F; + col_append_fstr( cinfo, COL_INFO, " T=%u", pdt ); + proto_item_append_text( cemi_node, ", T=%u", pdt ); + + /* 4 bits reserved, 12 bits Max Elements */ + guint16 me = tvb_get_ntohs( tvb, offset + 1) & 0x0FFF; + if( me != 1 ) + { + col_append_fstr( cinfo, COL_INFO, " N=%u", me ); + proto_item_append_text( cemi_node, ", N=%u", me ); + } + + /* 4 bits Read Access, 4 bits Write Access */ + guint8 wa = tvb_get_guint8( tvb, offset + 3 ); + guint8 ra = (wa & 0xF0) >> 4; + wa &= 0x0F; + col_append_fstr( cinfo, COL_INFO, " R=%u", ra ); + if( writable ) + col_append_fstr( cinfo, COL_INFO, " W=%u", wa ); + proto_item_append_text( cemi_node, ", R=%u", ra ); + if( writable ) + proto_item_append_text( cemi_node, ", W=%u", wa ); + + if( cemi_list ) + { + proto_item *pd_node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, offset, 4, "Property Description: " ); + proto_tree *pd_list = proto_item_add_subtree( pd_node, ett_cemi_pd ); + + const gchar* pdt_name = try_val_to_str( pdt, pdt_vals ); + if( pdt_name ) + proto_item_append_text( pd_node, "%s", pdt_name ); + else + proto_item_append_text( pd_node, "PDT = 0x%02X", pdt ); + + if( me != 1 ) + proto_item_append_text( pd_node, ", Max Elements = %u", me ); + + proto_item_append_text( pd_node, ", Read = %u", ra ); + if( writable ) + proto_item_append_text( pd_node, ", Write = %u", wa ); + + proto_tree_add_item( pd_list, hf_cemi_pw, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( pd_list, hf_cemi_pdt, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( pd_list, hf_cemi_me, tvb, offset + 1, 2, ENC_BIG_ENDIAN ); + proto_tree_add_item( pd_list, hf_cemi_ra, tvb, offset + 3, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( pd_list, hf_cemi_wa, tvb, offset + 3, 1, ENC_BIG_ENDIAN ); + } + + offset += 4; + } + + if( error && p_error ) + { + *p_error = 1; + } + + *p_offset = offset; +} + +/* Dissect OT (2 bytes), OI (12 bits), and PID (12 bits) for PropertyExt services +*/ +static void dissect_pid_ext( tvbuff_t *tvb, packet_info *pinfo, proto_item *cemi_node, proto_tree *cemi_list, gint *p_offset, gint size, guint8 *p_error ) +{ + gint offset = *p_offset; + column_info* cinfo = pinfo ? pinfo->cinfo : NULL; + guint8 error = 0; + + /* 2 bytes Object Type */ + guint16 ot = dissect_ot( tvb, pinfo, cemi_node, cemi_list, &offset, size, &error ); + + if( offset + 3 > size ) + { + proto_item* node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? Object Instance, PID" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 3 bytes" ); + + error = 1; + offset = size; + } + else + { + /* 12 bits Object Instance */ + guint16 cc = tvb_get_ntohs( tvb, offset ) >> 4; + col_append_fstr( cinfo, COL_INFO, " OI=%u", cc ); + proto_item_append_text( cemi_node, ", OI=%u", cc ); + proto_tree_add_item( cemi_list, hf_cemi_ext_oi, tvb, offset, 2, ENC_BIG_ENDIAN ); + ++offset; + + /* 12 bits Property ID */ + cc = tvb_get_ntohs( tvb, offset ) & 0x0FFF; + col_append_fstr( cinfo, COL_INFO, " P=%u", cc ); + proto_item_append_text( cemi_node, ", PID=%u", cc ); + + if( cemi_list ) + { + proto_item* node = proto_tree_add_item( cemi_list, hf_cemi_ext_pid, tvb, offset, 2, ENC_BIG_ENDIAN ); + const gchar* name = get_pid_name( ot, cc ); + if( name ) + { + proto_item_append_text( node, " = %s", name ); + } + } + + offset += 2; + } + + if( error && p_error ) + { + *p_error = 1; + } + + *p_offset = offset; +} + +/* Dissect cEMI Management packet + (M_PropRead.req, M_PropRead.con, M_PropWrite.req, M_PropWrite.con, M_PropInfo.ind, M_Reset.req, M_Reset.ind) +*/ +static void dissect_cemi_mgmt_packet( tvbuff_t* tvb, packet_info* pinfo, proto_item *cemi_node, proto_tree *cemi_list, guint8 mc, gint *p_offset, gint size, guint8* p_pa_flags, guint8 *p_error ) +{ + column_info* cinfo = pinfo->cinfo; + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + guint8 min_size = 1; + + switch( mc ) + { + case CEMI_M_PROPREAD_REQ: + pa_flags = 0; + goto case_CEMI_M_PROP; + + case CEMI_M_PROPWRITE_CON: + pa_flags = PA_RESPONSE; + goto case_CEMI_M_PROP; + + case CEMI_M_PROPREAD_CON: + pa_flags = PA_RESPONSE | PA_DATA; + goto case_CEMI_M_PROP; + + case CEMI_M_PROPWRITE_REQ: + case CEMI_M_PROPINFO_IND: + //pa_flags = PA_DATA; + + case_CEMI_M_PROP: + min_size = 7; + break; + + case CEMI_M_FUNCPROPCMD_REQ: + case CEMI_M_FUNCPROPREAD_REQ: + case CEMI_M_FUNCPROP_CON: + //pa_flags = PA_DATA; + min_size = 5; + break; + + case CEMI_M_RESET_REQ: + case CEMI_M_RESET_IND: + pa_flags = 0; + break; + } + + if( min_size >= 5 ) + { + /* 2 bytes Object Type */ + guint16 ot = dissect_ot( tvb, pinfo, cemi_node, cemi_list, &offset, size, &error ); + + /* 1 byte Object Instance */ + if( size < 4 ) + { + proto_item* node = proto_tree_add_debug_text( cemi_list, "? Object Instance" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + error = 1; + } + else + { + guint8 oi = tvb_get_guint8( tvb, 3 ); + if( oi != 1 ) + { + col_append_fstr( cinfo, COL_INFO, " OI=%u", oi ); + proto_item_append_text( cemi_node, ", OI=%u", oi ); + } + proto_tree_add_item( cemi_list, hf_cemi_oi, tvb, 3, 1, ENC_BIG_ENDIAN ); + offset = 4; + } + + /* 1 byte Property ID + */ + dissect_pid( tvb, pinfo, cemi_node, cemi_list, &offset, size, ot, 1, &error ); + + if( min_size >= 7 ) + { + /* Range (Start Index, Number Of Elements) and Data + */ + dissect_range( tvb, pinfo, cemi_node, cemi_list, &offset, size, pa_flags, &error ); + pa_flags = 0; + } + } + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +/* Dissect A_MemoryExt service */ +static void dissect_memory_ext_service( tvbuff_t* tvb, packet_info* pinfo, proto_item* cemi_node, proto_tree* cemi_list, + guint16 ax, gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + column_info* cinfo = pinfo->cinfo; + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + /* 4 bytes Range (1 byte Memory Length, 3 bytes Memory Address) */ + if( offset + 4 > size ) + { + proto_item* node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? Range" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 4 bytes" ); + error = 1; + offset = size; + } + else + { + /* 1 byte Memory Length or Error Code */ + guint8 is_response = (ax == AX_MemExtReadResp || ax == AX_MemExtWriteResp); + guint8 n = tvb_get_guint8( tvb, offset ); + if( is_response ) + { + if( n != 0 ) + { + col_append_fstr( cinfo, COL_INFO, " E=$%02X", n ); + proto_item_append_text( cemi_node, ", E=$%02X", n ); + } + } + else + { + if( n != 1 ) + { + col_append_fstr( cinfo, COL_INFO, " N=%u", n ); + proto_item_append_text( cemi_node, ", N=%u", n ); + } + } + + /* 3 bytes Memory Address */ + guint32 x = tvb_get_guint24( tvb, offset + 1, ENC_BIG_ENDIAN ); + col_append_fstr( cinfo, COL_INFO, " X=$%06" G_GINT32_MODIFIER "X", x ); + proto_item_append_text( cemi_node, ", X=$%06" G_GINT32_MODIFIER "X", x ); + + if( is_response ) + { + proto_tree_add_item( cemi_list, hf_cemi_error, tvb, offset, 1, ENC_BIG_ENDIAN ); + } + else + { + proto_tree_add_item( cemi_list, hf_cemi_ext_memory_length, tvb, offset, 1, ENC_BIG_ENDIAN ); + } + + proto_tree_add_item( cemi_list, hf_cemi_ext_memory_address, tvb, offset + 1, 3, ENC_BIG_ENDIAN ); + + offset += 4; + } + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +/* Dissect A_UserMemory service */ +static void dissect_user_memory_service( tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, proto_item* cemi_node, proto_tree* cemi_list, + gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + column_info* cinfo = pinfo->cinfo; + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + proto_item* node; + proto_tree* list; + + /* 3 bytes Range (Memory Length, Memory Address) */ + if( offset + 3 > size ) + { + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? Range" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 3 bytes" ); + error = 1; + offset = size; + } + else + { + guint8 c2 = tvb_get_guint8( tvb, offset ); + guint8 c1 = c2 >> 4; + guint32 c3 = tvb_get_ntohs( tvb, offset + 1 ); + c2 &= 0x0F; + c3 |= c1 << 16UL; + if( c2 != 1 ) + col_append_fstr( cinfo, COL_INFO, " N=%u", c2 ); + col_append_fstr( cinfo, COL_INFO, " X=$%05X", c3 ); + if( tree ) + { + if( c2 != 1 ) + proto_item_append_text( cemi_node, ", N=%u", c2 ); + proto_item_append_text( cemi_node, ", X=$%05X", c3 ); + node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, offset, 1, + "Range: %u byte%s at address $%05X", c2, (c2 == 1) ? "" : "s", c3 ); + list = proto_item_add_subtree( node, ett_cemi_range ); + proto_tree_add_item( list, hf_cemi_memory_address_ext, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( list, hf_cemi_memory_length, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( list, hf_cemi_memory_address, tvb, offset + 1, 2, ENC_BIG_ENDIAN ); + } + offset += 3; + } + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +/* Dissect A_FunctionProperty service */ +static void dissect_function_property_service( tvbuff_t* tvb, packet_info* pinfo, proto_item* cemi_node, proto_tree* cemi_list, + gint* p_offset, gint size, guint8* p_error ) +{ + /* 1 byte Object Index */ + dissect_ox( tvb, pinfo, cemi_node, cemi_list, p_offset, size, p_error ); + + /* 1 byte Property ID */ + dissect_pid( tvb, pinfo, cemi_node, cemi_list, p_offset, size, -1, 1, p_error ); +} + +/* Dissect (obsolete) A_Router service */ +static void dissect_router_service( tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, proto_item* cemi_node, proto_tree* cemi_list, + gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + column_info* cinfo = pinfo->cinfo; + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + proto_item* node; + proto_tree* list; + + /* 3 bytes Range (1 byte Memory Length, 2 bytes Memory Address) */ + if( offset + 3 > size ) + { + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? Range" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 3 bytes" ); + error = 1; + offset = size; + } + else + { + guint8 c = tvb_get_guint8( tvb, offset ); + guint16 cc = tvb_get_ntohs( tvb, offset + 1 ); + if( c != 1 ) + col_append_fstr( cinfo, COL_INFO, " N=%u", c ); + col_append_fstr( cinfo, COL_INFO, " X=$%04X", cc ); + if( tree ) + { + proto_item_append_text( cemi_node, ", N=%u, X=$%04X", c, cc ); + node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, offset, 3, + "Range: %u byte%s at address $%04X", c, (c == 1) ? "" : "s", cc ); + list = proto_item_add_subtree( node, ett_cemi_range ); + proto_tree_add_item( list, hf_cemi_ext_memory_length, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( list, hf_cemi_ext_memory_address, tvb, offset + 1, 2, ENC_BIG_ENDIAN ); + } + offset += 3; + } + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +/* Dissect A_Authenticate or A_Key service */ +static void dissect_authenticate_service( tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, proto_item* cemi_node, proto_tree* cemi_list, + guint16 ax, gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + column_info* cinfo = pinfo->cinfo; + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + /* 1 byte Level */ + if( offset >= size ) + { + proto_item* node = proto_tree_add_debug_text( cemi_list, "? Level" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + error = 1; + } + else + { + guint8 c = tvb_get_guint8( tvb, offset ); + if( ax != AX_AuthReq || c != 0 ) + { + col_append_fstr( cinfo, COL_INFO, " L=%u", c ); + if( tree ) + { + proto_item_append_text( cemi_node, ", L=%u", c ); + } + } + if( tree ) + { + proto_tree_add_item( cemi_list, hf_cemi_level, tvb, offset, 1, ENC_BIG_ENDIAN ); + } + offset++; + } + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +/* Dissect A_PropertyValue service */ +static void dissect_property_value_service( tvbuff_t* tvb, packet_info* pinfo, proto_item* cemi_node, proto_tree* cemi_list, + gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + /* 1 byte Object Index */ + dissect_ox( tvb, pinfo, cemi_node, cemi_list, p_offset, size, p_error ); + + /* 1 byte Property ID */ + dissect_pid( tvb, pinfo, cemi_node, cemi_list, p_offset, size, -1, 1, p_error ); + + /* 2 bytes Range */ + dissect_range( tvb, pinfo, cemi_node, cemi_list, p_offset, size, *p_pa_flags, p_error ); +} + +/* Dissect A_PropertyDescription service */ +static void dissect_property_description_service( tvbuff_t* tvb, packet_info* pinfo, proto_item* cemi_node, proto_tree* cemi_list, + gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + /* 1 byte Object Index */ + dissect_ox( tvb, pinfo, cemi_node, cemi_list, p_offset, size, p_error ); + + /* 1 byte Property ID */ + { + guint8 pa_flags = *p_pa_flags; + guint8 pid = dissect_pid( tvb, pinfo, cemi_node, cemi_list, p_offset, size, -1, pa_flags, p_error ); + + /* 1 byte Property Index */ + dissect_px( tvb, pinfo, cemi_node, cemi_list, p_offset, size, pa_flags || !pid, p_error ); + + if( pa_flags ) /* A_PropertyDescription_Response */ + { + /* 1 byte PDT, 2 bytes Max Elements, 1 byte Access Levels */ + dissect_prop_descr( tvb, pinfo, cemi_node, cemi_list, p_offset, size, p_error ); + + /* No further trailing data */ + *p_pa_flags = 0; + } + } +} + +/* Dissect A_NetworkParameter or A_GroupPropertyValue service */ +static void dissect_network_parameter_service( tvbuff_t* tvb, packet_info* pinfo, proto_item* cemi_node, proto_tree* cemi_list, + gint* p_offset, gint size, guint8* p_error ) +{ + /* 2 bytes Object Type */ + guint16 ot = dissect_ot( tvb, pinfo, cemi_node, cemi_list, p_offset, size, p_error ); + + /* 1 byte Property ID */ + dissect_pid( tvb, pinfo, cemi_node, cemi_list, p_offset, size, ot, 1, p_error ); +} + +/* Dissect A_IndividualAddressSerialNumber or A_DomainAddressSerialNumber service */ +static void dissect_ia_serial_number_service( tvbuff_t* tvb, packet_info* pinfo, proto_item* cemi_node, proto_tree* cemi_list, + gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + column_info* cinfo = pinfo->cinfo; + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + proto_item* node; + + /* 6 bytes Serial Nr */ + if( offset + 6 > size ) + { + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? Serial Number" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 6 bytes" ); + error = 1; + offset = size; + } + else + { + proto_tree_add_data( cemi_list, tvb, offset, 6, cinfo, cemi_node, "Serial Number", " SN=$", ", SerNr=$" ); + offset += 6; + } + + if( pa_flags ) + { + if( offset >= size ) + { + node = proto_tree_add_debug_text( cemi_list, "? Data" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Missing" ); + error = 1; + } + } + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +/* Dissect A_SystemNetworkParameter service */ +static void dissect_system_network_parameter_service( tvbuff_t* tvb, packet_info* pinfo, proto_item* cemi_node, proto_tree* cemi_list, + gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + column_info* cinfo = pinfo->cinfo; + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + proto_item* node; + const gchar* name; + guint16 ot; + guint16 cc; + guint8 c; + + /* 2 bytes Object Type */ + if( offset + 1 >= size ) + { + ot = 0; + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? Object Type" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 2 bytes" ); + error = 1; + offset = size; + } + else + { + ot = cc = tvb_get_ntohs( tvb, offset ); + + if( cc ) + { + col_append_fstr( cinfo, COL_INFO, " OT=%u", cc ); + proto_item_append_text( cemi_node, ", OT=%u", cc ); + } + + if( cemi_list ) + { + node = proto_tree_add_item( cemi_list, hf_cemi_ot, tvb, offset, 2, ENC_BIG_ENDIAN ); + name = try_val_to_str( cc, ot_vals ); + if( name ) + { + proto_item_append_text( node, " = %s", name ); + } + } + + offset += 2; + } + + /* 2 bytes Property ID (12 bits) and Reserved (4 bits) */ + if( offset + 1 >= size ) + { + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? Property ID" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 2 bytes" ); + error = 1; + offset = size; + } + else + { + /* 12 bits Property ID */ + cc = tvb_get_ntohs( tvb, offset ); + c = cc & 0x000F; + cc >>= 4; + + col_append_fstr( cinfo, COL_INFO, " P=%u", cc ); + proto_item_append_text( cemi_node, ", PID=%u", cc ); + + if( cemi_list ) + { + node = proto_tree_add_item( cemi_list, hf_cemi_snp_pid, tvb, offset, 2, ENC_BIG_ENDIAN ); + name = get_pid_name( ot, cc ); + if( name ) + { + proto_item_append_text( node, " = %s", name ); + } + } + + ++offset; + + /* 4 bits Reserved */ + if( c ) + { + col_append_fstr( cinfo, COL_INFO, " $%X", c ); + proto_item_append_text( cemi_node, ", $%X", c ); + node = proto_tree_add_item( cemi_list, hf_cemi_snp_reserved, tvb, offset, 1, ENC_BIG_ENDIAN ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: zero" ); + error = 1; + } + + ++offset; + } + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +/* Dissect A_PropertyExtValue service */ +static void dissect_property_ext_value_service( tvbuff_t* tvb, packet_info* pinfo, proto_item* cemi_node, proto_tree* cemi_list, + gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + column_info* cinfo = pinfo->cinfo; + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + proto_item* node; + + /* 2 bytes OT, 12 bits OI, 12 bits PID */ + dissect_pid_ext( tvb, pinfo, cemi_node, cemi_list, &offset, size, &error ); + + /* 3 bytes Range (1 byte Count, 2 bytes Index) */ + if( offset + 3 > size ) + { + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? Range" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 3 bytes" ); + error = 1; + offset = size; + } + else + { + /* 1 byte Count */ + guint8 ne = tvb_get_guint8( tvb, offset ); + if( ne != 1 ) + { + col_append_fstr( cinfo, COL_INFO, " N=%u", ne ); + proto_item_append_text( cemi_node, ", N=%u", ne ); + } + + /* 2 bytes Index */ + guint16 sx = tvb_get_ntohs( tvb, offset + 1 ); + if( sx != 1 ) + { + col_append_fstr( cinfo, COL_INFO, " X=%u", sx ); + proto_item_append_text( cemi_node, ", X=%u", sx ); + } + + if( cemi_list ) + { + proto_item *range_node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, offset, 3, "Range: %u element%s at position %u", ne, (ne == 1) ? "" : "s", sx ); + proto_tree *range_list = proto_item_add_subtree( range_node, ett_cemi_range ); + proto_tree_add_item( range_list, hf_cemi_ext_ne, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( range_list, hf_cemi_ext_sx, tvb, offset + 1, 2, ENC_BIG_ENDIAN ); + } + + offset += 3; + } + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +/* Dissect A_PropertyExtDescription service */ +static void dissect_property_ext_description_service( tvbuff_t* tvb, packet_info* pinfo, proto_item* cemi_node, proto_tree* cemi_list, + gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + column_info* cinfo = pinfo->cinfo; + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + proto_item* node; + guint16 cc; + guint8 c; + + /* 2 bytes OT, 12 bits OI, 12 bits PID */ + dissect_pid_ext( tvb, pinfo, cemi_node, cemi_list, &offset, size, &error ); + + /* 4 bits Description Type */ + if( offset >= size ) + { + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? Description Type" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 4 bits" ); + error = 1; + } + else + { + c = tvb_get_guint8( tvb, offset ) >> 4; + col_append_fstr( cinfo, COL_INFO, " D=%u", c ); + proto_item_append_text( cemi_node, ", D=%u", c ); + proto_tree_add_item( cemi_list, hf_cemi_ext_dt, tvb, offset, 1, ENC_BIG_ENDIAN ); + } + + /* 12 bits Property Index */ + if( offset + 2 > size ) + { + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? Property Index" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 12 bits" ); + error = 1; + offset = size; + } + else + { + cc = tvb_get_ntohs( tvb, offset ) & 0x0FFF; + col_append_fstr( cinfo, COL_INFO, " PX=%u", cc ); + proto_item_append_text( cemi_node, ", PX=%u", cc ); + proto_tree_add_item( cemi_list, hf_cemi_ext_px, tvb, offset, 2, ENC_BIG_ENDIAN ); + offset += 2; + } + + if( pa_flags ) /* AX_PropExtDescrResp */ + { + /* 4 bytes DPT (2 bytes DPT Major, 2 bytes DPT Minor) */ + if( offset + 4 > size ) + { + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? Data Point Type" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 4 bytes" ); + error = 1; + offset = size; + } + else + { + guint16 dpt_major = tvb_get_ntohs( tvb, offset ); + guint16 dpt_minor = tvb_get_ntohs( tvb, offset + 2 ); + + if( cemi_list ) + { + proto_item *dpt_node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, offset, 2, "Data Point Type: %u.%u", dpt_major, dpt_minor ); + proto_tree *dpt_list = proto_item_add_subtree( dpt_node, ett_cemi_dpt ); + proto_tree_add_item( dpt_list, hf_cemi_dpt_major, tvb, offset, 2, ENC_BIG_ENDIAN ); + proto_tree_add_item( dpt_list, hf_cemi_dpt_minor, tvb, offset + 2, 2, ENC_BIG_ENDIAN ); + } + + offset += 4; + + if( dpt_major || dpt_minor ) + { + col_append_fstr( cinfo, COL_INFO, " DPT=%u.%u", dpt_major, dpt_minor ); + proto_item_append_text( cemi_node, ", DPT=%u.%u", dpt_major, dpt_minor ); + } + } + + /* 1 byte PDT, 2 bytes Max Elements, 1 byte Access Levels */ + dissect_prop_descr( tvb, pinfo, cemi_node, cemi_list, &offset, size, &error ); + + /* No further trailing data */ + pa_flags = 0; + } + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +/* Dissect A_DataSecurity service */ +static void dissect_data_security_service( tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, proto_item* cemi_node, proto_tree* cemi_list, + guint16 source_addr, proto_item* source_node, guint16 dest_addr, proto_item* dest_node, guint8 unicast, + const gchar* name, gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + column_info* cinfo = pinfo->cinfo; + proto_tree* root_tree = tree; + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + proto_item* node; + proto_tree* list; + + // 1 byte SCF, 6 bytes SeqNr, ... + // and either another SeqNr for sync or Apci+Mac (2+4 bytes) for data. + if( offset + 13 > size ) + { + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? SCF, SeqNr, ..." ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: min 13 bytes" ); + error = 1; + offset = size; + } + else + { + /* 1 byte SCF */ + guint8 scf = tvb_get_guint8( tvb, offset ); + guint8 is_sync = (scf & 6) == 0x02; + guint8 is_sync_req = is_sync && (scf & 1) == 0; + guint8 is_sync_res = is_sync && !is_sync_req; + guint64 seq_nr; + + name = try_val_to_str( scf, scf_short_vals ); + if( !name ) name = "?"; + col_append_fstr( cinfo, COL_INFO, " %s", name ); + proto_item_append_text( cemi_node, ", %s", name ); + + node = proto_tree_add_item( cemi_list, hf_cemi_scf, tvb, offset, 1, ENC_BIG_ENDIAN ); + list = proto_item_add_subtree( node, ett_cemi_scf ); + node = proto_tree_add_item( list, hf_cemi_scf_t, tvb, offset, 1, ENC_BIG_ENDIAN ); + node = proto_tree_add_item( list, hf_cemi_scf_sai, tvb, offset, 1, ENC_BIG_ENDIAN ); + node = proto_tree_add_item( list, hf_cemi_scf_sbc, tvb, offset, 1, ENC_BIG_ENDIAN ); + node = proto_tree_add_item( list, hf_cemi_scf_svc, tvb, offset, 1, ENC_BIG_ENDIAN ); + + ++offset; + + /* 6 bytes SeqNr */ + name = is_sync_req ? "SeqNrLocal" : is_sync_res ? "Challenge" : "SeqNr"; + seq_nr = tvb_get_ntoh48( tvb, offset ); + proto_tree_add_data( cemi_list, tvb, offset, 6, cinfo, cemi_node, name, NULL, is_sync_res ? NULL : ", SeqNrLocal=$" ); + offset += 6; + + if( is_sync ) + { + /* 6 bytes SyncReq SerNr or SyncRes SeqNrRemote */ + name = is_sync_req ? "SerNr" : "SeqNrRemote"; + proto_tree_add_data( cemi_list, tvb, offset, 6, cinfo, cemi_node, name, NULL, is_sync_res ? ", SeqNrRemote=$" : NULL ); + offset += 6; + + /* 6 bytes SyncReq Challenge or SyncRes SeqNrLocal */ + name = is_sync_req ? "Challenge" : "SeqNrLocal"; + if( offset + 6 > size ) + { + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "%s", name ); + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 6 bytes" ); + error = 1; + offset = size; + } + else + { + proto_tree_add_data( cemi_list, tvb, offset, 6, NULL, NULL, name, NULL, NULL ); + offset += 6; + + if( offset < size ) + { + /* 4 bytes MAC */ + node = proto_tree_add_data( cemi_list, tvb, offset, size - offset, NULL, NULL, "Message Authentication Code", NULL, NULL ); + if( offset + 4 != size ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 4 bytes" ); + error = 1; + } + offset = size; + } + } + } + else // Data + { + struct data_security_info info; + struct knx_keyring_ia_seqs* ia_seq; + const guint8* cemi; + const guint8* encrypted; + gint encrypted_size; + const guint8* decrypted; + proto_item* item; + + info.source = source_addr; + info.dest = dest_addr; + info.multicast = !unicast; + info.seq_nr = seq_nr; + *info.output_text = '\0'; + + if( !unicast ) // multicast or broadcast + { + // Check sending IA + guint8 ga_found = 0; + guint8 ia_ok = 0; + struct knx_keyring_ga_senders* ga_sender = knx_keyring_ga_senders; + for( ; ga_sender; ga_sender = ga_sender->next ) + { + if( ga_sender->ga == dest_addr ) + { + ga_found = 1; + + if( ga_sender->ia == source_addr ) + { + ia_ok = 1; + break; + } + } + } + + if( !ia_ok ) + { + if( ga_found ) + { + expert_add_info_format( pinfo, source_node, KIP_ERROR, "Unknown sender" ); + error = 1; + } + else + { + expert_add_info_format( pinfo, dest_node, KIP_WARNING, "Unknown group address" ); + } + } + } + + // Check SeqNr + for( ia_seq = knx_keyring_ia_seqs; ia_seq; ia_seq = ia_seq->next ) + { + if( ia_seq->ia == source_addr ) + { + if( ia_seq->seq > seq_nr ) + { + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: min $%012" G_GINT64_MODIFIER "X", ia_seq->seq ); + break; + } + } + } + + // Get encrypted data. + cemi = tvb_get_ptr( tvb, 0, size ); + encrypted = cemi + offset; + encrypted_size = size - offset; + + // Decrypt. + decrypted = decrypt_data_security_data( encrypted, encrypted_size, cemi, size, &info ); + + if( decrypted ) + { + tvbuff_t* tvb2 = tvb_new_child_real_data( tvb, decrypted, encrypted_size, encrypted_size ); + gint size2 = encrypted_size - 4; // > 0, guaranteed by decrypt_data_security_data + proto_item_append_text( cemi_node, ", MAC OK" ); + //tvb_set_free_cb(tvb2, wmem_free); + add_new_data_source( pinfo, tvb2, "Decrypted" ); + + item = proto_tree_add_none_format( cemi_list, hf_folder, tvb2, 0, encrypted_size, "Decrypted" ); + tree = proto_item_add_subtree( item, ett_cemi_decrypted ); + + if( *info.output_text ) + { + proto_item_append_text( item, " (%s)", info.output_text ); + } + + node = proto_tree_add_data( tree, tvb2, 0, size2, NULL, NULL, "Embedded APDU", NULL, NULL ); + node = proto_tree_add_data( tree, tvb2, size2, 4, NULL, NULL, "Message Authentication Code", NULL, NULL ); + + /* Dissect embedded APDU */ + { + // Hack: To save us from splitting another sub dissector which only + // dissects the Apci+Apdu + // we synthesize a telegram from the outer ApciSec telegram fields and the inner + // decrypted apci+apdu and then we dissect this as a new cEMI frame. + gint innerTelegramSize = size - 13; // > 0, already checked above + gint additionalInfoLength = cemi[ 1 ]; // cemi size > 13, already checked above + gint offsetToApci = additionalInfoLength + 9; + if( offsetToApci < size ) + { + if( offsetToApci + size2 <= innerTelegramSize ) + { + guint8* innerTelegram = (guint8*) wmem_alloc( wmem_packet_scope(), innerTelegramSize ); + + memcpy( innerTelegram, cemi, offsetToApci ); + memcpy( innerTelegram + offsetToApci, decrypted, size2 ); + innerTelegram[ additionalInfoLength + 8 ] = (guint8) (size2 - 1); + + tvbuff_t* tvb3 = tvb_new_child_real_data( tvb, innerTelegram, innerTelegramSize, innerTelegramSize ); + //tvb_set_free_cb(tvb3, wmem_free); + add_new_data_source( pinfo, tvb3, "Inner Decrypted Telegram" ); + + dissector_handle_t cemi_handle = find_dissector( "cemi" ); + if( cemi_handle ) + { + call_dissector( cemi_handle, tvb3, pinfo, root_tree ); + } + } + } + } + } + else + { + // Could not be decrypted. + proto_item_append_text( cemi_node, ", Could not be decrypted" ); + + if( *info.output_text ) + { + proto_item_append_text( cemi_node, " (%s)", info.output_text ); + } + } + + offset = size; + } + } + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +/* Dissect extended AL service (10 bit AL service code) +*/ +static void dissect_extended_app_service( tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, proto_item* cemi_node, proto_tree* cemi_list, + guint16 source_addr, proto_item* source_node, guint16 dest_addr, proto_item* dest_node, guint8 unicast, + guint16 ax, const gchar* name, + gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + column_info* cinfo = pinfo->cinfo; + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + proto_item* node = NULL; + proto_tree* list = NULL; + + col_append_fstr( cinfo, COL_INFO, " %s", name ); + + if( tree ) + { + proto_item_append_text( cemi_node, ", %s", name ); + node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, offset, 2, "APCI: %s", name ); + list = proto_item_add_subtree( node, ett_cemi_apci ); + proto_tree_add_item( list, hf_cemi_ax, tvb, offset, 2, ENC_BIG_ENDIAN ); + } + + offset += 2; + + pa_flags = PA_RESPONSE | PA_DATA; + + switch( ax ) + { + case AX_UserMemRead: + case AX_MemExtRead: + case AX_RoutingTableRead: + case AX_RouterMemRead: + case AX_PropValueRead: + case AX_PropDescrRead: + case AX_IndAddrSerNumRead: + case AX_DomAddrSerNumRead: + case AX_PropExtValueRead: + case AX_PropExtDescrRead: + pa_flags = 0; + break; + } + + switch( ax ) + { + case AX_MemExtRead: + case AX_MemExtReadResp: + case AX_MemExtWrite: + case AX_MemExtWriteResp: + dissect_memory_ext_service( tvb, pinfo, cemi_node, cemi_list, ax, &offset, size, &pa_flags, &error ); + break; + + case AX_UserMemRead: + case AX_UserMemResp: + case AX_UserMemWrite: + case AX_UserMemBitWrite: + dissect_user_memory_service( tvb, pinfo, tree, cemi_node, cemi_list, &offset, size, &pa_flags, &error ); + break; + + case AX_FuncPropCmd: + case AX_FuncPropRead: + case AX_FuncPropResp: + dissect_function_property_service( tvb, pinfo, cemi_node, cemi_list, &offset, size, &error ); + break; + + case AX_RoutingTableRead: + case AX_RouterMemRead: + case AX_RoutingTableResp: + case AX_RoutingTableWrite: + case AX_RouterMemResp: + case AX_RouterMemWrite: + case AX_MemBitWrite: + dissect_router_service( tvb, pinfo, tree, cemi_node, cemi_list, &offset, size, &pa_flags, &error ); + break; + + case AX_AuthReq: + case AX_AuthResp: + case AX_KeyWrite: + case AX_KeyResp: + dissect_authenticate_service( tvb, pinfo, tree, cemi_node, cemi_list, ax, &offset, size, &pa_flags, &error ); + break; + + case AX_PropValueRead: + case AX_PropValueResp: + case AX_PropValueWrite: + dissect_property_value_service( tvb, pinfo, cemi_node, cemi_list, &offset, size, &pa_flags, &error ); + break; + + case AX_PropDescrRead: + case AX_PropDescrResp: + dissect_property_description_service( tvb, pinfo, cemi_node, cemi_list, &offset, size, &pa_flags, &error ); + break; + + case AX_NwkParamRead: + case AX_NwkParamResp: + case AX_NwkParamWrite: + case AX_GroupPropValueRead: + case AX_GroupPropValueResp: + case AX_GroupPropValueWrite: + case AX_GroupPropValueInfo: + dissect_network_parameter_service( tvb, pinfo, cemi_node, cemi_list, &offset, size, &error ); + break; + + case AX_IndAddrSerNumRead: + case AX_DomAddrSerNumRead: + case AX_IndAddrSerNumResp: + case AX_IndAddrSerNumWrite: + case AX_DomAddrSerNumResp: + case AX_DomAddrSerNumWrite: + dissect_ia_serial_number_service( tvb, pinfo, cemi_node, cemi_list, &offset, size, &pa_flags, &error ); + break; + + case AX_SysNwkParamRead: + case AX_SysNwkParamResp: + case AX_SysNwkParamWrite: + dissect_system_network_parameter_service( tvb, pinfo, cemi_node, cemi_list, &offset, size, &pa_flags, &error ); + break; + + case AX_PropExtValueRead: + case AX_PropExtValueResp: + case AX_PropExtValueWriteCon: + case AX_PropExtValueWriteConRes: + case AX_PropExtValueWriteUnCon: + dissect_property_ext_value_service( tvb, pinfo, cemi_node, cemi_list, &offset, size, &pa_flags, &error ); + break; + + case AX_PropExtDescrRead: + case AX_PropExtDescrResp: + dissect_property_ext_description_service( tvb, pinfo, cemi_node, cemi_list, &offset, size, &pa_flags, &error ); + break; + + case AX_FuncPropExtCmd: + case AX_FuncPropExtRead: + case AX_FuncPropExtResp: + + /* 2 bytes OT, 12 bits OI, 12 bits PID */ + dissect_pid_ext( tvb, pinfo, cemi_node, cemi_list, &offset, size, &error ); + break; + + case AX_DataSec: + dissect_data_security_service( tvb, pinfo, tree, cemi_node, cemi_list, source_addr, source_node, dest_addr, dest_node, unicast, + name, &offset, size, &pa_flags, &error ); + break; + } + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +/* Dissect simple AL service (4 bit AL service code) +*/ +static void dissect_simple_app_service( tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, proto_item* cemi_node, proto_tree* cemi_list, + guint8 ac, guint8 ad, gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + column_info* cinfo = pinfo->cinfo; + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + proto_item* node = NULL; + proto_tree* list = NULL; + + guint8 c; + guint16 cc; + + const gchar* name = val_to_str( ac, ac_vals, "AC=%u" ); + col_append_fstr( cinfo, COL_INFO, " %s", name ); + if( tree ) + { + proto_item_append_text( cemi_node, ", %s", name ); + node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, offset, 2, "APCI: %s", name ); + list = proto_item_add_subtree( node, ett_cemi_apci ); + proto_tree_add_item( list, hf_cemi_ac, tvb, offset, 2, ENC_BIG_ENDIAN ); + } + + offset++; + + switch( ac ) + { + case AC_GroupValueRead: + case AC_MemRead: + case AC_AdcRead: + case AC_DevDescrRead: + pa_flags = 0; + break; + } + + switch( ac ) + { + case AC_GroupValueRead: + case AC_GroupValueResp: + case AC_GroupValueWrite: + case AC_Restart: + { + guint8 expected = ((pa_flags && offset + 1 >= size) || ac == AC_Restart); + + if( expected || ad != 0 ) + { + /* Show APCI 6-bit data + */ + if( !expected ) + { + error = 1; + } + else if( ad != 0 || ac != AC_Restart || offset + 1 < size ) + { + col_append_fstr( cinfo, COL_INFO, " $%02X", ad ); + proto_item_append_text( cemi_node, " $%02X", ad ); + } + + if( tree ) + { + node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, offset, 1, "Data: %02X", ad ); + list = proto_item_add_subtree( node, ett_cemi_apci ); + proto_tree_add_item( list, hf_cemi_ad, tvb, offset, 1, ENC_BIG_ENDIAN ); + + if( !expected ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 0x00" ); + } + } + } + } + break; + + case AC_MemRead: + case AC_MemResp: + case AC_MemWrite: + + /* 6 bits Memory Length, 2 bytes Memory Address */ + if( offset + 3 > size ) + { + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset + 1, size - offset - 1, NULL, "? Memory Address" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 2 bytes" ); + error = 1; + offset = size - 1; + } + else + { + cc = tvb_get_ntohs( tvb, offset + 1 ); + if( ad != 1 ) + col_append_fstr( cinfo, COL_INFO, " N=%u", ad ); + col_append_fstr( cinfo, COL_INFO, " X=$%04X", cc ); + if( tree ) + { + if( ad != 1 ) + proto_item_append_text( cemi_node, ", N=%u", ad ); + proto_item_append_text( cemi_node, ", X=$%04X", cc ); + node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, offset, 3, "Range: %u byte%s at address $%04X", ad, (ad == 1) ? "" : "s", cc ); + list = proto_item_add_subtree( node, ett_cemi_range ); + proto_tree_add_item( list, hf_cemi_ad_memory_length, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( list, hf_cemi_memory_address, tvb, offset + 1, 2, ENC_BIG_ENDIAN ); + } + offset += 2; + } + break; + + case AC_AdcRead: + case AC_AdcResp: + + /* 6 bits Channel */ + col_append_fstr( cinfo, COL_INFO, " #%u", ad ); + if( tree ) + { + proto_item_append_text( cemi_node, " #%u", ad ); + node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, offset, 1, "Channel: %u", ad ); + list = proto_item_add_subtree( node, ett_cemi_apci ); + proto_tree_add_item( list, hf_cemi_ad_channel, tvb, offset, 1, ENC_BIG_ENDIAN ); + } + ++offset; + + /* 1 byte Count */ + if( offset >= size ) + { + node = proto_tree_add_debug_text( cemi_list, "? Count" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + error = 1; + --offset; + } + else + { + c = tvb_get_guint8( tvb, offset ); + if( c != 1 ) + { + col_append_fstr( cinfo, COL_INFO, " N=%u", c ); + proto_item_append_text( cemi_node, ", N=%u", c ); + } + node = proto_tree_add_item( cemi_list, hf_cemi_adc_count, tvb, offset, 1, ENC_BIG_ENDIAN ); + } + break; + + case AC_DevDescrRead: + case AC_DevDescrResp: + + /* 6 bits Descriptor Type */ + if( ad != 0 ) + col_append_fstr( cinfo, COL_INFO, " #%u", ad ); + if( tree ) + { + if( ad != 0 ) + proto_item_append_text( cemi_node, " #%u", ad ); + node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, offset, 1, "Descriptor Type: %u", ad ); + list = proto_item_add_subtree( node, ett_cemi_apci ); + proto_tree_add_item( list, hf_cemi_ad_type, tvb, offset, 1, ENC_BIG_ENDIAN ); + } + break; + + case AC_UserMsg: + case AC_Escape: + + /* 6 bits Data */ + col_append_fstr( cinfo, COL_INFO, " #%u", ad ); + if( tree ) + { + proto_item_append_text( cemi_node, " #%u", ad ); + proto_item_append_text( node, " $%02X", ad ); + proto_tree_add_item( list, hf_cemi_ad, tvb, offset, 1, ENC_BIG_ENDIAN ); + } + break; + } + + offset++; + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +/* Dissect cEMI Application Layer +*/ +static void dissect_cemi_app_layer( tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, proto_item* cemi_node, proto_tree* cemi_list, + guint16 source_addr, proto_item* source_node, guint16 dest_addr, proto_item* dest_node, guint8 unicast, + gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + /* 10 bits APCI + */ + if( offset + 1 >= size ) + { + proto_item* node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? APCI" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 2 bytes" ); + error = 1; + offset = size; + } + else + { + /* Extract and split AL service code */ + guint8 tb = tvb_get_guint8( tvb, offset ); + guint8 ab = tvb_get_guint8( tvb, offset + 1 ); + + /* 4 bits simple AL service code */ + guint8 ac = ((tb & 0x03) << 2) | ((ab & 0xC0) >> 6); + + /* 6 bits data */ + guint8 ad = ab & 0x3F; + + /* 10 = 4 + 6 bits extended AL service code */ + guint16 ax = (ac << 6) | ad; + + const gchar* name = try_val_to_str( ax, ax_vals ); + + if( name ) /* Extended AL code (10 bits) */ + { + dissect_extended_app_service( tvb, pinfo, tree, cemi_node, cemi_list, source_addr, source_node, dest_addr, dest_node, unicast, + ax, name, &offset, size, &pa_flags, &error ); + } + else /* Simple AL code (4 bits) followed by data (6 bits) */ + { + dissect_simple_app_service( tvb, pinfo, tree, cemi_node, cemi_list, ac, ad, &offset, size, &pa_flags, &error ); + } + } + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +/* Dissect cEMI Transport Layer +*/ +static void dissect_cemi_transport_layer( tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, proto_item* cemi_node, proto_tree* cemi_list, + guint8 is_tdata, guint16 source_addr, proto_item* source_node, guint16 dest_addr, proto_item* dest_node, guint8 unicast, + gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + column_info* cinfo = pinfo->cinfo; + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + proto_item* node; + const gchar* name; + gchar text[ 128 ]; + guint8 c; + + /* 6 bits TPCI */ + if( offset >= size ) + { + node = proto_tree_add_debug_text( cemi_list, "? TPCI" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + error = 1; + } + else + { + guint8 tb = tvb_get_guint8( tvb, offset ); + proto_item *tpci_node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, offset, 1, "TPCI" ); + proto_tree *tpci_list = proto_item_add_subtree( tpci_node, ett_cemi_tpci ); + guint8 tpci_error = 0; + + node = proto_tree_add_item( tpci_list, hf_cemi_tpt, tvb, offset, 1, ENC_BIG_ENDIAN ); + if( is_tdata && (tb & 0x80) ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: zero" ); + tpci_error = 1; + } + + node = proto_tree_add_item( tpci_list, hf_cemi_tst, tvb, offset, 1, ENC_BIG_ENDIAN ); + if( is_tdata && (tb & 0x40) ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: zero" ); + tpci_error = 1; + } + + c = (tb & 0x3C) >> 2; + + if( c || tb & 0x40 ) /* Numbered Packet? */ + { + node = proto_tree_add_item( tpci_list, hf_cemi_num, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_item_append_text( tpci_node, ", SeqNum = %u", c ); + if( !(tb & 0x40) ) + { + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: zero" ); + tpci_error = 1; + } + } + + if( tb & 0x80 ) /* Control Packet */ + { + /* 2 bits TPCI Code */ + guint8 tc = tb & 0x03; + name = try_val_to_str( tc, tc_vals ); + if( !name ) + { + g_snprintf( text, sizeof text, "TC=%u", tc ); + name = text; + } + col_append_fstr( cinfo, COL_INFO, " %s", name ); + if( tree ) + { + proto_item_append_text( cemi_node, ", %s", name ); + proto_item_append_text( tpci_node, ": %s", name ); + proto_tree_add_item( tpci_list, hf_cemi_tc, tvb, offset, 1, ENC_BIG_ENDIAN ); + } + } + + if( tpci_error ) + { + proto_item_prepend_text( tpci_node, "? " ); + error = 1; + } + + if( tb & 0x80 ) /* Control Packet */ + { + pa_flags = 0; + offset++; + } + else /* Data Packet */ + { + /* APCI etc */ + dissect_cemi_app_layer( tvb, pinfo, tree, cemi_node, cemi_list, source_addr, source_node, dest_addr, dest_node, unicast, &offset, size, &pa_flags, &error ); + } + } + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +/* Dissect cEMI Link Layer + (typically L_Data or T_Data) +*/ +static void dissect_cemi_link_layer( tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, proto_item* cemi_node, proto_tree* cemi_list, guint8 mc, gint* p_offset, gint size, guint8* p_pa_flags, guint8* p_error ) +{ + column_info* cinfo = pinfo->cinfo; + gint offset = *p_offset; + guint8 pa_flags = *p_pa_flags; + guint8 error = *p_error; + + proto_item* node = NULL; + proto_tree* list = NULL; + + const gchar* name; + gchar text[ 128 ]; + guint8 c; + + guint8 is_tdata = 0; + guint8 is_ldata = 0; + guint16 source_addr = 0; + guint16 dest_addr = 0; + guint8 unicast = 0; + + proto_item* source_node = NULL; + proto_item* dest_node = NULL; + + proto_item* ai_node; + proto_tree* ai_list; + + if( size < 2 ) + { + ai_node = proto_tree_add_debug_text( cemi_list, "? Additional Info" ); + ai_list = proto_item_add_subtree( ai_node, ett_cemi_ai ); + node = proto_tree_add_debug_text( ai_list, "? Length" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + offset = size; + error = 1; + } + else + { + /* Additional Information */ + guint8 ai_len = tvb_get_guint8( tvb, 1 ); + gint ai_end = 2 + ai_len; + gint ai_size = ai_len; + + if( ai_end > size ) + { + error = 2; + ai_size = size - 2; + ai_end = size; + } + + ai_node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, 1, ai_size + 1, "Additional Info (%u bytes)", ai_len ); + ai_list = proto_item_add_subtree( ai_node, ett_cemi_ai ); + node = proto_tree_add_item( ai_list, hf_cemi_ai_length, tvb, 1, 1, ENC_BIG_ENDIAN ); + + if( error == 2 ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Available: %d bytes", ai_size ); + } + + offset = 2; + while( offset < ai_end ) + { + /* Additional Information Element */ + guint8 aie_type = tvb_get_guint8( tvb, offset ); + guint8 aie_len; + gint aie_size; + proto_item *aie_node; + proto_tree *aie_list; + + name = try_val_to_str( aie_type, aiet_vals ); + + if( offset + 1 >= ai_end ) + { + error = 3; + aie_len = 0; + aie_size = 1; + } + else + { + aie_len = tvb_get_guint8( tvb, offset + 1 ); + aie_size = ai_end - offset - 2; + if( aie_size < aie_len ) + { + error = 4; + } + else + { + aie_size = aie_len; + } + aie_size += 2; + } + + aie_node = proto_tree_add_none_format( ai_list, hf_folder, tvb, offset, aie_size, "Additional Info: %s", name ? name : "?" ); + aie_list = proto_item_add_subtree( aie_node, ett_cemi_aie ); + node = proto_tree_add_item( aie_list, hf_cemi_aie_type, tvb, offset, 1, ENC_BIG_ENDIAN ); + if( name ) proto_item_append_text( node, " = %s", name ); + offset++; + + if( error == 3 ) + { + proto_item_prepend_text( aie_node, "? " ); + node = proto_tree_add_debug_text( aie_list, "? Length" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + break; + } + + proto_item_append_text( aie_node, " (%u bytes)", aie_len ); + node = proto_tree_add_item( aie_list, hf_cemi_aie_length, tvb, offset, 1, ENC_BIG_ENDIAN ); + offset++; + + if( error == 4 ) + { + proto_item_prepend_text( aie_node, "? " ); + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Available: %d bytes", aie_size - 2 ); + break; + } + + if( aie_len > 0 ) + { + proto_tree_add_data( aie_list, tvb, offset, aie_len, NULL, NULL, "Data", NULL, NULL ); + offset += aie_len; + } + else + { + proto_item_prepend_text( aie_node, "? " ); + proto_item_append_text( node, " (?)" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: >= 1 byte(s)" ); + error = 5; + } + } + + if( error >= 2 ) + { + proto_item_prepend_text( ai_node, "? " ); + } + + offset = ai_end; + } + + switch( mc ) + { + case CEMI_L_BUSMON_IND: + case CEMI_L_RAW_IND: + case CEMI_L_RAW_REQ: + case CEMI_L_RAW_CON: + break; + + default: + + switch( mc ) + { + case CEMI_L_DATA_REQ: + case CEMI_L_DATA_CON: + case CEMI_L_DATA_IND: + is_ldata = 1; + break; + + case CEMI_T_DATA_INDIVIDUAL_REQ: + case CEMI_T_DATA_INDIVIDUAL_IND: + case CEMI_T_DATA_CONNECTED_REQ: + case CEMI_T_DATA_CONNECTED_IND: + is_tdata = 1; + break; + } + + if( is_tdata ) + { + gint length = (size >= offset + 6) ? 6 : size - offset; + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, length, NULL, "Reserved" ); + if( length < 6 ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 6 bytes" ); + error = 1; + } + else + { + gint pos = 0; + for( ; pos < 6; pos++ ) + { + if( tvb_get_guint8( tvb, offset + pos ) != 0 ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: zero" ); + error = 1; + break; + } + } + } + + is_tdata = 1; + offset += length; + } + else + { + /* 1 byte Control Field 1 */ + if( offset >= size ) + { + node = proto_tree_add_debug_text( cemi_list, "? Ctrl1" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + error = 1; + } + else + { + if( tree ) + { + c = tvb_get_guint8( tvb, offset ); + proto_item_append_text( cemi_node, ", " ); + node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, offset, 1, "Ctrl1: " ); + if( !(c & 0x80) ) + { + proto_item_append_text( cemi_node, "X " ); + proto_item_append_text( node, "Extended, " ); + } + if( !(c & 0x20) ) + { + proto_item_append_text( cemi_node, "R " ); + proto_item_append_text( node, "Repeat On Error, " ); + } + if( !(c & 0x10) ) + { + proto_item_append_text( cemi_node, "B " ); + proto_item_append_text( node, "System Broadcast, " ); + } + if( c & 0x02 ) + { + proto_item_append_text( cemi_node, "A " ); + proto_item_append_text( node, "Ack Wanted, " ); + } + if( c & 0x01 ) + { + proto_item_append_text( cemi_node, "C " ); + proto_item_append_text( node, "Unconfirmed, " ); + } + + name = try_val_to_str( (c & 0x0C) >> 2, prio_vals ); + if( !name ) + name = "?"; + proto_item_append_text( cemi_node, "P=%s", name ); + proto_item_append_text( node, "Prio = %s", name ); + list = proto_item_add_subtree( node, ett_cemi_ctrl1 ); + proto_tree_add_item( list, hf_cemi_ft, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( list, hf_cemi_rep, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( list, hf_cemi_bt, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( list, hf_cemi_prio, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( list, hf_cemi_ack, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( list, hf_cemi_ce, tvb, offset, 1, ENC_BIG_ENDIAN ); + } + + offset++; + } + + /* 1 byte Control Field 2 */ + if( offset >= size ) + { + node = proto_tree_add_debug_text( cemi_list, "? Ctrl2" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + error = 1; + } + else + { + c = tvb_get_guint8( tvb, offset ); + + unicast = !(c & 0x80); /* Address Type (IA or GA) */ + + if( tree ) + { + guint8 hc = (c & 0x70) >> 4; /* Hop Count */ + guint8 eff = c & 0x0F; /* Extended Frame Format (0 = standard) */ + + g_snprintf( text, sizeof text, "%u", (c & 0x70) >> 4 ); /* hop count */ + proto_item_append_text( cemi_node, ", H=%u", hc ); + node = proto_tree_add_none_format( cemi_list, hf_folder, tvb, offset, 1, "Ctrl2: Hops = %u", hc ); + if( eff ) + { + proto_item_append_text( cemi_node, " F=%u", eff ); + proto_item_append_text( cemi_node, " Frame = %u", eff ); + } + list = proto_item_add_subtree( node, ett_cemi_ctrl2 ); + proto_tree_add_item( list, hf_cemi_at, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( list, hf_cemi_hc, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( list, hf_cemi_eff, tvb, offset, 1, ENC_BIG_ENDIAN ); + } + + offset++; + } + + /* 2 bytes Source Address */ + if( offset + 1 >= size ) + { + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? Source" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 2 bytes" ); + error = 1; + offset = size; + } + else + { + source_addr = tvb_get_ntohs( tvb, offset ); + g_snprintf( text, sizeof text, "%u.%u.%u", (source_addr >> 12) & 0xF, (source_addr >> 8) & 0xF, source_addr & 0xFF ); + col_append_fstr( cinfo, COL_INFO, " %s", text ); + if( tree ) + { + proto_item_append_text( cemi_node, ", Src=%s", text ); + source_node = proto_tree_add_item( cemi_list, hf_cemi_sa, tvb, offset, 2, ENC_BIG_ENDIAN ); + proto_item_append_text( source_node, " = %s", text ); + } + + offset += 2; + } + + /* 2 bytes Destination Address */ + if( offset + 1 >= size ) + { + node = proto_tree_add_bytes_format( cemi_list, hf_bytes, tvb, offset, size - offset, NULL, "? Destination" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 2 bytes" ); + error = 1; + offset = size; + } + else + { + dest_addr = tvb_get_ntohs( tvb, offset ); + + if( unicast ) + { + /* Individual Address */ + g_snprintf( text, sizeof text, "%u.%u.%u", (dest_addr >> 12) & 0xF, (dest_addr >> 8) & 0xF, dest_addr & 0xFF ); + } + else + { + /* Group Address */ + g_snprintf( text, sizeof text, "%u/%u/%u", (dest_addr >> 11) & 0x1F, (dest_addr >> 8) & 0x7, dest_addr & 0xFF ); + } + + col_append_fstr( cinfo, COL_INFO, "->%s", text ); + + if( tree ) + { + proto_item_append_text( cemi_node, ", Dst=%s", text ); + dest_node = proto_tree_add_item( cemi_list, hf_cemi_da, tvb, offset, 2, ENC_BIG_ENDIAN ); + proto_item_append_text( dest_node, " = %s", text ); + } + + offset += 2; + } + } + + if( is_ldata || is_tdata ) + { + /* 1 byte NPDU Length */ + if( offset >= size ) + { + node = proto_tree_add_debug_text( cemi_list, "? Length" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + error = 1; + } + else + { + guint8 data_len = tvb_get_guint8( tvb, offset ); + node = proto_tree_add_item( cemi_list, hf_cemi_len, tvb, offset, 1, ENC_BIG_ENDIAN ); + + if( offset + 2 + data_len != size ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Available: %d bytes", size - offset - 2 ); + error = 1; + } + + offset++; + } + + /* TPCI etc */ + dissect_cemi_transport_layer( tvb, pinfo, tree, cemi_node, cemi_list, is_tdata, source_addr, source_node, dest_addr, dest_node, unicast, &offset, size, &pa_flags, &error ); + } + + break; + } + + *p_offset = offset; + *p_pa_flags = pa_flags; + *p_error = error; +} + +static gint dissect_cemi( tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, void* data _U_ ) +{ + gint offset = 0; + gint size = tvb_captured_length_remaining( tvb, 0 ); + guint8 error = 0; + column_info* cinfo = pinfo->cinfo; + + /* cEMI node in tree view */ + proto_item* cemi_node = proto_tree_add_item( tree, proto_cemi, tvb, 0, size, ENC_BIG_ENDIAN ); + + /* Subnodes of cEMI node */ + proto_tree* cemi_list = proto_item_add_subtree( cemi_node, ett_cemi ); + + guint8 pa_flags = PA_DATA; + + /* Only add cEMI information to the info column (not replacing it). + This means that we do not have to clear that column here, but + are adding a seperator here. + */ + col_append_str( cinfo, COL_INFO, " " ); + + /* Replace long name "Common External Message Interface" by short name "cEMI" */ + proto_item_set_text( cemi_node, "cEMI" ); + + if( size <= 0 ) + { + expert_add_info_format( pinfo, cemi_node, KIP_ERROR, "Expected: min 1 byte" ); + error = 1; + } + else + { + /* 1 byte cEMI Message Code */ + guint8 mc = tvb_get_guint8( tvb, 0 ); + const gchar* name = try_val_to_str( mc, mc_vals ); + + if( !name ) + { + /* Unknown Message Code */ + col_append_str( cinfo, COL_INFO, "cEMI" ); + pa_flags = 0; + } + else + { + /* Add cEMI message code to info column */ + col_append_str( cinfo, COL_INFO, name ); + + /* Show MC in cEMI node, and more detailed in a subnode */ + proto_item_append_text( cemi_node, " %s", name ); + proto_tree_add_item( cemi_list, hf_cemi_mc, tvb, 0, 1, ENC_BIG_ENDIAN ); + + offset = 1; + + if( mc >= 0xF0 ) + { + /* cEMI Management packet */ + dissect_cemi_mgmt_packet( tvb, pinfo, cemi_node, cemi_list, mc, &offset, size, &pa_flags, &error ); + } + else + { + /* cEMI Link Layer packet */ + dissect_cemi_link_layer( tvb, pinfo, tree, cemi_node, cemi_list, mc, &offset, size, &pa_flags, &error ); + } + } + } + + if( offset < size ) + { + /* Trailing data */ + proto_item* node = proto_tree_add_data( cemi_list, tvb, offset, size - offset, cinfo, cemi_node, "Data", " $", ", $" ); + + if( !pa_flags ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Unexpected" ); + error = 1; + } + + offset = size; + } + + if( error ) + { + /* If not already done */ + if( !knxip_error ) + { + knxip_error = 1; + col_prepend_fstr( cinfo, COL_INFO, "? " ); + } + + proto_item_prepend_text( cemi_node, "? " ); + } + + return size; +} + +void proto_register_cemi( void ) +{ + /* Header fields */ + static hf_register_info hf[] = { + { &hf_bytes, { "Data", "cemi.data", FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_folder, { "Folder", "cemi.folder", FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_cemi_mc, { "Message Code", "cemi.mc", FT_UINT8, BASE_HEX, VALS( mc_vals ), 0, NULL, HFILL } }, + { &hf_cemi_error, { "Error", "cemi.e", FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_cemi_ai_length, { "Additional Information Length", "cemi.ai.n", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_cemi_aie_type, { "Additional Information Element Type", "cemi.ait.n", FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_cemi_aie_length, { "Additional Information Element Length", "cemi.aie.n", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_cemi_ot, { "Object Type", "cemi.ot", FT_UINT16, BASE_DEC, VALS( ot_vals ), 0, NULL, HFILL } }, + { &hf_cemi_oi, { "Object Instance", "cemi.oi", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_cemi_ox, { "Object Index", "cemi.ox", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_cemi_px, { "Property Index", "cemi.px",FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_cemi_pid, { "Property ID", "cemi.pid", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_cemi_ne, { "Count", "cemi.n", FT_UINT8, BASE_DEC, NULL, 0xF0, NULL, HFILL } }, + { &hf_cemi_sx, { "Index", "cemi.x", FT_UINT16, BASE_DEC, NULL, 0x0FFF, NULL, HFILL } }, + { &hf_cemi_ft, { "Frame Type", "cemi.ft", FT_UINT8, BASE_DEC, VALS( ft_vals ), 0x80, NULL, HFILL } }, + { &hf_cemi_rep, { "Repeat On Error", "cemi.rep", FT_UINT8, BASE_DEC, VALS( rep_vals ), 0x20, NULL, HFILL } }, + { &hf_cemi_bt, { "Broadcast Type", "cemi.bt", FT_UINT8, BASE_DEC, VALS( bt_vals ), 0x10, NULL, HFILL } }, + { &hf_cemi_prio, { "Priority", "cemi.prio", FT_UINT8, BASE_DEC, VALS( prio_vals ), 0x0C, NULL, HFILL } }, + { &hf_cemi_ack, { "Ack Wanted", "cemi.ack", FT_UINT8, BASE_DEC, VALS( ack_vals ), 0x02, NULL, HFILL } }, + { &hf_cemi_ce, { "Confirmation Error", "cemi.ce", FT_UINT8, BASE_DEC, VALS( ce_vals ), 0x01, NULL, HFILL } }, + { &hf_cemi_at, { "Address Type", "cemi.at", FT_UINT8, BASE_DEC, VALS( at_vals ), 0x80, NULL, HFILL } }, + { &hf_cemi_hc, { "Hop Count", "cemi.hc", FT_UINT8, BASE_DEC, NULL, 0x70, NULL, HFILL } }, + { &hf_cemi_eff, { "Extended Frame Format", "cemi.eff", FT_UINT8, BASE_HEX, NULL, 0x0F, NULL, HFILL } }, + { &hf_cemi_sa, { "Source", "cemi.sa", FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_cemi_da, { "Destination", "cemi.da", FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_cemi_len, { "Length", "cemi.len", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_cemi_tpt, { "Packet Type", "cemi.tpt", FT_UINT8, BASE_DEC, VALS( pt_vals ), 0x80, NULL, HFILL } }, + { &hf_cemi_tst, { "Sequence Type", "cemi.st", FT_UINT8, BASE_DEC, VALS( st_vals ), 0x40, NULL, HFILL } }, + { &hf_cemi_num, { "Sequence Number", "cemi.num", FT_UINT8, BASE_DEC, NULL, 0x3C, NULL, HFILL } }, + { &hf_cemi_tc, { "Service", "cemi.tc", FT_UINT8, BASE_HEX, VALS( tc_vals ), 0x03, NULL, HFILL } }, + { &hf_cemi_ac, { "Service", "cemi.ac", FT_UINT16, BASE_HEX, VALS( ac_vals ), 0x03C0, NULL, HFILL } }, + { &hf_cemi_ad, { "Data", "cemi.ad", FT_UINT8, BASE_HEX, NULL, 0x3F, NULL, HFILL } }, + { &hf_cemi_ad_memory_length, { "Memory Length", "cemi.ad", FT_UINT8, BASE_HEX, NULL, 0x3F, NULL, HFILL } }, + { &hf_cemi_ad_channel, { "Channel", "cemi.ad", FT_UINT8, BASE_HEX, NULL, 0x3F, NULL, HFILL } }, + { &hf_cemi_ad_type, { "Data", "cemi.ad", FT_UINT8, BASE_HEX, NULL, 0x3F, NULL, HFILL } }, + { &hf_cemi_ax, { "Service", "cemi.ax", FT_UINT16, BASE_HEX, VALS( ax_vals ), 0x03FF, NULL, HFILL } }, + { &hf_cemi_pw, { "Writable", "cemi.pw", FT_UINT8, BASE_DEC, NULL, 0x80, NULL, HFILL } }, + { &hf_cemi_pdt, { "Property Data Type", "cemi.pdt", FT_UINT8, BASE_HEX, VALS( pdt_vals ), 0x3F, NULL, HFILL } }, + { &hf_cemi_me, { "Max Elements", "cemi.me", FT_UINT16, BASE_DEC, NULL, 0x0FFF, NULL, HFILL } }, + { &hf_cemi_ra, { "Read Access", "cemi.ra", FT_UINT8, BASE_DEC, NULL, 0xF0, NULL, HFILL } }, + { &hf_cemi_wa, { "Write Access", "cemi.wa", FT_UINT8, BASE_DEC, NULL, 0x0F, NULL, HFILL } }, + { &hf_cemi_ext_oi, { "Object Instance", "cemi.oi", FT_UINT16, BASE_DEC, NULL, 0xFFF0, NULL, HFILL } }, + { &hf_cemi_ext_pid, { "Property ID", "cemi.pid", FT_UINT16, BASE_DEC, NULL, 0x0FFF, NULL, HFILL } }, + { &hf_cemi_ext_ne, { "Count", "cemi.n", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_cemi_ext_sx, { "Index", "cemi.x", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_cemi_ext_dt, { "Description Type", "cemi.dt", FT_UINT8, BASE_DEC, NULL, 0xF0, NULL, HFILL } }, + { &hf_cemi_ext_px, { "Property Index", "cemi.px", FT_UINT16, BASE_DEC, NULL, 0x0FFF, NULL, HFILL } }, + { &hf_cemi_ext_memory_length, { "Memory Length", "cemi.n", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_cemi_ext_memory_address, { "Memory Address", "cemi.x", FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_cemi_memory_length, { "Memory Length", "cemi.n", FT_UINT8, BASE_DEC, NULL, 0x0F, NULL, HFILL } }, + { &hf_cemi_memory_address, { "Memory Address", "cemi.x", FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_cemi_memory_address_ext, { "Memory Address Extension", "cemi.xx", FT_UINT8, BASE_HEX, NULL, 0xF0, NULL, HFILL } }, + { &hf_cemi_level, { "Level", "cemi.level", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_cemi_snp_pid, { "Property ID", "cemi.pid", FT_UINT16, BASE_DEC, NULL, 0xFFF0, NULL, HFILL } }, + { &hf_cemi_snp_reserved, { "Reserved", "cemi.reserved", FT_UINT16, BASE_DEC, NULL, 0x0F, NULL, HFILL } }, + { &hf_cemi_dpt_major, { "Data Point Type Major", "cemi.pdt.major", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_cemi_dpt_minor, { "Data Point Type Minor", "cemi.pdt.minor", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_cemi_scf, { "Security Control Field", "cemi.scf", FT_UINT8, BASE_HEX, VALS( scf_vals ), 0, NULL, HFILL } }, + { &hf_cemi_scf_t, { "Tool Access", "cemi.scf.t", FT_UINT8, BASE_DEC, NULL, 0x80, NULL, HFILL } }, + { &hf_cemi_scf_sai, { "Security Algorithm Identifier", "cemi.scf.sai", FT_UINT8, BASE_HEX, VALS( scf_sai_vals ), 0x70, NULL, HFILL } }, + { &hf_cemi_scf_sbc, { "System Broadcast", "cemi.scf.sbc", FT_UINT8, BASE_DEC, NULL, 0x08, NULL, HFILL } }, + { &hf_cemi_scf_svc, { "Service", "cemi.scf.sai", FT_UINT8, BASE_HEX, VALS( scf_svc_vals ), 0x07, NULL, HFILL } }, + { &hf_cemi_adc_count, { "Count", "cemi.adc.n", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + }; + + /* Subtrees */ + static gint *ett[] = { + &ett_cemi, + &ett_cemi_ai, + &ett_cemi_aie, + &ett_cemi_ctrl1, + &ett_cemi_ctrl2, + &ett_cemi_tpci, + &ett_cemi_apci, + &ett_cemi_range, + &ett_cemi_pd, + &ett_cemi_dpt, + &ett_cemi_scf, + &ett_cemi_decrypted + }; + + proto_cemi = proto_register_protocol( "Common External Message Interface", "cEMI", "cemi" ); + + proto_register_field_array( proto_cemi, hf, array_length( hf ) ); + proto_register_subtree_array( ett, array_length( ett ) ); + + register_dissector( "cemi", dissect_cemi, proto_cemi ); +} + +void proto_reg_handoff_cemi( void ) +{ +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 2 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=2 tabstop=8 expandtab: + * :indentSize=2:tabSize=8:noTabs=true: + */ diff --git a/epan/dissectors/packet-knxip.c b/epan/dissectors/packet-knxip.c new file mode 100644 index 0000000000..21f9e86b69 --- /dev/null +++ b/epan/dissectors/packet-knxip.c @@ -0,0 +1,4136 @@ +/* packet-knxip.c + * Routines for KNXnet/IP dissection + * By Jan Kessler <kessler@ise.de> + * Copyright 2004, Jan Kessler <kessler@ise.de> + * + * Ethereal - Network traffic analyzer + * By Gerald Combs <gerald@ethereal.com> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "packet-knxip.h" +#include <epan/strutil.h> + +#define ECDH_PUBLIC_VALUE_SIZE 32 + + /* The following service families are defined for the + version 1.0 KNXnet/IP implementation of the eFCP protocol + */ +#define KIP_SERVICE_CORE 0x02 +#define KIP_SERVICE_MANAGEMENT 0x03 +#define KIP_SERVICE_TUNNELING 0x04 +#define KIP_SERVICE_ROUTING 0x05 +#define KIP_SERVICE_REMOTE_LOGGING 0x06 +#define KIP_SERVICE_REMOTE_DIAG_AND_CONFIG 0x07 +#define KIP_SERVICE_OBJECT_SERVER 0x08 +#define KIP_SERVICE_SECURITY 0x09 + + /* The service codes for the core services (device discovery, + self description and connection management) as defined in + chapter 2 of the KNXnet/IP system specification + */ +#define KIP_SEARCH_REQUEST 0x0201 +#define KIP_SEARCH_RESPONSE 0x0202 +#define KIP_DESCRIPTION_REQUEST 0x0203 +#define KIP_DESCRIPTION_RESPONSE 0x0204 +#define KIP_CONNECT_REQUEST 0x0205 +#define KIP_CONNECT_RESPONSE 0x0206 +#define KIP_CONNECTIONSTATE_REQUEST 0x0207 +#define KIP_CONNECTIONSTATE_RESPONSE 0x0208 +#define KIP_DISCONNECT_REQUEST 0x0209 +#define KIP_DISCONNECT_RESPONSE 0x020A +#define KIP_SEARCH_REQUEST_EXT 0x020B +#define KIP_SEARCH_RESPONSE_EXT 0x020C + + /* The service codes for the device management services + (tunneling of cEMI local management procedures) as + defined in chapter 3 of the KNXnet/IP system specification + */ +#define KIP_CONFIGURATION_REQUEST 0x0310 +#define KIP_CONFIGURATION_ACK 0x0311 + + /* The service codes for the tunneling services + (transport of cEMI frames from service interface) as + defined in chapter 4 of the KNXnet/IP system specification + */ +#define KIP_TUNNELING_REQUEST 0x0420 +#define KIP_TUNNELING_ACK 0x0421 +#define KIP_TUNNELING_FEATURE_GET 0x0422 +#define KIP_TUNNELING_FEATURE_RESPONSE 0x0423 +#define KIP_TUNNELING_FEATURE_SET 0x0424 +#define KIP_TUNNELING_FEATURE_INFO 0x0425 + + /* The service codes for the routing services + (transport of cEMI frames between EIB couplers) as + defined in chapter 5 of the KNXnet/IP system specification + */ +#define KIP_ROUTING_INDICATION 0x0530 +#define KIP_ROUTING_LOST_MESSAGE 0x0531 +#define KIP_ROUTING_BUSY 0x0532 +#define KIP_ROUTING_SYSTEM_BROADCAST 0x0533 + + /* The service codes for RemoteDiagAndConfig + */ +#define KIP_REMOTE_DIAG_REQUEST 0x0740 +#define KIP_REMOTE_DIAG_RESPONSE 0x0741 +#define KIP_REMOTE_CONFIG_REQUEST 0x0742 +#define KIP_REMOTE_RESET_REQUEST 0x0743 + + /* The service codes for KNX-IP Secure + */ +#define KIP_SECURE_WRAPPER 0x0950 +#define KIP_SESSION_REQUEST 0x0951 +#define KIP_SESSION_RESPONSE 0x0952 +#define KIP_SESSION_AUTHENTICATE 0x0953 +#define KIP_SESSION_STATUS 0x0954 +#define KIP_TIMER_NOTIFY 0x0955 + + /* KNXnet/IP host protocols */ +#define KIP_IPV4_UDP 0x01 +#define KIP_IPV4_TCP 0x02 + + /* The different types of DIBs (Description Information Blocks) + for the KNXnet/IP Core Discovery and Description services + as defined in chapter 1 of the KNXnet/IP system specification + */ +#define KIP_DIB_DEVICE_INFO 0x01 +#define KIP_DIB_SUPP_SVC_FAMILIES 0x02 +#define KIP_DIB_IP_CONFIG 0x03 +#define KIP_DIB_CUR_CONFIG 0x04 +#define KIP_DIB_KNX_ADDRESSES 0x05 +#define KIP_DIB_SECURED_SERVICE_FAMILIES 0x06 +#define KIP_DIB_TUNNELING_INFO 0x07 +#define KIP_DIB_EXTENDED_DEVICE_INFO 0x08 +#define KIP_DIB_MFR_DATA 0xFE + + /* The different types of SRPs (Search Request Parameter Blocks) + for the KNXnet/IP Core Discovery and Description services + */ +#define KIP_SRP_BY_PROGMODE 0x01 +#define KIP_SRP_BY_MACADDR 0x02 +#define KIP_SRP_BY_SERVICE 0x03 +#define KIP_SRP_REQUEST_DIBS 0x04 + + /* The different KNX medium types for the hardware (device info) + DIB as defined in AN033 Common EMI Specification + */ +#define KIP_KNXTYPE_TP0 0x01 +#define KIP_KNXTYPE_TP1 0x02 +#define KIP_KNXTYPE_PL110 0x04 +#define KIP_KNXTYPE_PL132 0x08 +#define KIP_KNXTYPE_RF 0x10 +#define KIP_KNXTYPE_IP 0x20 + + /* KNXnet/IP connection types */ +#define KIP_DEVICE_MGMT_CONNECTION 0x03 +#define KIP_TUNNEL_CONNECTION 0x04 +#define KIP_REMLOG_CONNECTION 0x06 +#define KIP_REMCONF_CONNECTION 0x07 +#define KIP_OBJSVR_CONNECTION 0x08 + + /* Tunneling v2 feature ids */ +#define KIP_TUNNELING_FEATURE_ID_SUPPORTED_EMI_TYPE 0x01 +#define KIP_TUNNELING_FEATURE_ID_HOST_DEVICE_DEVICE_DESCRIPTOR_TYPE_0 0x02 +#define KIP_TUNNELING_FEATURE_ID_BUS_CONNECTION_STATUS 0x03 +#define KIP_TUNNELING_FEATURE_ID_KNX_MANUFACTURER_CODE 0x04 +#define KIP_TUNNELING_FEATURE_ID_ACTIVE_EMI_TYPE 0x05 +#define KIP_TUNNELING_FEATURE_ID_INDIVIDUAL_ADDRESS 0x06 +#define KIP_TUNNELING_FEATURE_ID_MAX_APDU_LENGTH 0x07 +#define KIP_TUNNELING_FEATURE_ID_INFO_SERVICE_ENABLE 0x08 + + /* KNXnet/IP tunnel types */ +#define TUNNEL_LINKLAYER 0x02 +#define TUNNEL_RAW 0x04 +#define TUNNEL_BUSMONITOR 0x80 + + /* KNXnet/IP error codes */ +#define KIP_E_NO_ERROR 0x00 +#define KIP_E_CONNECTION_ID 0x21 +#define KIP_E_CONNECTION_TYPE 0x22 +#define KIP_E_CONNECTION_OPTION 0x23 +#define KIP_E_NO_MORE_CONNECTIONS 0x24 +#define KIP_E_NO_MORE_UNIQUE_CONNECTIONS 0x25 +#define KIP_E_DATA_CONNECTION 0x26 +#define KIP_E_KNX_CONNECTION 0x27 +#define KIP_E_TUNNELING_LAYER 0x29 + +/* KNXnet/IP remote selection types */ +#define SELECT_PROGMODE 0x01 +#define SELECT_MACADDRESS 0x02 + +/* SESSION_STATUS codes */ +#define SESSION_STATUS_AUTHENTICATION_SUCCESS 0x00 +#define SESSION_STATUS_AUTHENTICATION_FAILED 0x01 +#define SESSION_STATUS_UNAUTHENTICATED 0x02 +#define SESSION_STATUS_TIMEOUT 0x03 +#define SESSION_STATUS_KEEPALIVE 0x04 +#define SESSION_STATUS_CLOSE 0x05 + +/* Initialize the protocol identifier that is needed for the + protocol hook and to register the fields in the protocol tree +*/ +static gint proto_knxip = -1; + +/* Initialize the registered fields identifiers. These fields + will be registered with the protocol during initialization. + Protocol fields are like type definitions. The protocol dissector + later on adds items of these types to the protocol tree. +*/ +static gint hf_bytes = -1; +static gint hf_folder = -1; +static gint hf_knxip_header_length = -1; +static gint hf_knxip_protocol_version = -1; +static gint hf_knxip_service_id = -1; +static gint hf_knxip_service_family = -1; +static gint hf_knxip_service_type = -1; +static gint hf_knxip_total_length = -1; +static gint hf_knxip_structure_length = -1; +static gint hf_knxip_host_protocol = -1; +static gint hf_knxip_ip_address = -1; +static gint hf_knxip_port = -1; +static gint hf_knxip_description_type = -1; +static gint hf_knxip_knx_medium = -1; +static gint hf_knxip_device_status = -1; +static gint hf_knxip_program_mode = -1; +static gint hf_knxip_knx_address = -1; +static gint hf_knxip_project_id = -1; +static gint hf_knxip_project_number = -1; +static gint hf_knxip_installation_number = -1; +static gint hf_knxip_serial_number = -1; +static gint hf_knxip_multicast_address = -1; +static gint hf_knxip_mac_address = -1; +static gint hf_knxip_friendly_name = -1; +static gint hf_knxip_service_version = -1; +static gint hf_knxip_security_version = -1; +static gint hf_knxip_manufacturer_code = -1; +static gint hf_knxip_connection_type = -1; +static gint hf_knxip_knx_layer = -1; +static gint hf_knxip_reserved = -1; +static gint hf_knxip_channel = -1; +static gint hf_knxip_status = -1; +static gint hf_knxip_seq_counter = -1; +static gint hf_knxip_ip_subnet = -1; +static gint hf_knxip_ip_gateway = -1; +static gint hf_knxip_ip_assign = -1; +static gint hf_knxip_ip_caps = -1; +static gint hf_knxip_ip_dhcp = -1; +static gint hf_knxip_tunnel_feature = -1; +static gint hf_knxip_routing_loss = -1; +static gint hf_knxip_busy_time = -1; +static gint hf_knxip_busy_control = -1; +static gint hf_knxip_selector = -1; +static gint hf_knxip_max_apdu_length = -1; +static gint hf_knxip_medium_status = -1; +static gint hf_knxip_mask_version = -1; +static gint hf_knxip_srp_mandatory = -1; +static gint hf_knxip_srp_type = -1; +static gint hf_knxip_reset_command = -1; +static gint hf_knxip_session = -1; +static gint hf_knxip_tag = -1; +static gint hf_knxip_user = -1; +static gint hf_knxip_session_status = -1; + +/* Initialize the subtree pointers. These pointers are needed to + display the protocol in a structured tree. Subtrees hook on + already defined fields or (the topmost) on the protocol itself +*/ +static gint ett_kip = -1; +static gint ett_efcp = -1; +static gint ett_service = -1; +static gint ett_hpai = -1; +static gint ett_dib = -1; +static gint ett_medium = -1; +static gint ett_status = -1; +static gint ett_projectid = -1; +static gint ett_service_family = -1; +static gint ett_ip_assignment = -1; +static gint ett_cri = -1; +static gint ett_crd = -1; +static gint ett_cnhdr = -1; +static gint ett_loss = -1; +static gint ett_busy = -1; +static gint ett_selector = -1; +static gint ett_decrypted = -1; +static gint ett_tunnel = -1; + +/* Set up the value_string tables for the service families + and the service types (note that the service types in KNXnet/IP + version 1.0 are unique even across service families...) +*/ +static const value_string knxip_service_family_vals[] = { + { KIP_SERVICE_CORE, "Core" }, + { KIP_SERVICE_MANAGEMENT, "Device Management" }, + { KIP_SERVICE_TUNNELING, "Tunneling" }, + { KIP_SERVICE_ROUTING, "Routing" }, + { KIP_SERVICE_REMOTE_LOGGING, "Remote Logging" }, + { KIP_SERVICE_REMOTE_DIAG_AND_CONFIG, "Remote Diag And Config" }, + { KIP_SERVICE_OBJECT_SERVER, "Object Server" }, + { KIP_SERVICE_SECURITY, "Security" }, + { 0, NULL} +}; +static const value_string knxip_service_type_vals[] = { + { KIP_SEARCH_REQUEST, "Search Request" }, + { KIP_SEARCH_RESPONSE, "Search Response" }, + { KIP_DESCRIPTION_REQUEST, "Description Request" }, + { KIP_DESCRIPTION_RESPONSE, "Description Response" }, + { KIP_CONNECT_REQUEST, "Connect Request" }, + { KIP_CONNECT_RESPONSE, "Connect Response" }, + { KIP_CONNECTIONSTATE_REQUEST, "Connection State Request" }, + { KIP_CONNECTIONSTATE_RESPONSE, "Connection State Response" }, + { KIP_DISCONNECT_REQUEST, "Disconnect Request" }, + { KIP_DISCONNECT_RESPONSE, "Disconnect Response" }, + { KIP_SEARCH_REQUEST_EXT, "Search Request Extended" }, + { KIP_SEARCH_RESPONSE_EXT, "Search Response Extended" }, + { KIP_CONFIGURATION_REQUEST, "Configuration Request" }, + { KIP_CONFIGURATION_ACK, "Configuration Acknowledgement" }, + { KIP_TUNNELING_REQUEST, "Tunneling Request" }, + { KIP_TUNNELING_ACK, "Tunneling Acknowledgement" }, + { KIP_TUNNELING_FEATURE_GET, "Tunneling Feature Get" }, + { KIP_TUNNELING_FEATURE_RESPONSE, "Tunneling Feature Response" }, + { KIP_TUNNELING_FEATURE_SET, "Tunneling Feature Set" }, + { KIP_TUNNELING_FEATURE_INFO, "Tunneling Feature Info" }, + { KIP_ROUTING_INDICATION, "Routing Indication" }, + { KIP_ROUTING_LOST_MESSAGE, "Routing Loss" }, + { KIP_ROUTING_BUSY, "Routing Busy" }, + { KIP_ROUTING_SYSTEM_BROADCAST, "Routing System Broadcast" }, + { KIP_REMOTE_DIAG_REQUEST, "Remote Diagnostic Request" }, + { KIP_REMOTE_DIAG_RESPONSE, "Remote Diagnostic Response" }, + { KIP_REMOTE_CONFIG_REQUEST, "Remote Configuration Request" }, + { KIP_REMOTE_RESET_REQUEST, "Remote Reset Request" }, + { KIP_SECURE_WRAPPER, "Secure Wrapper" }, + { KIP_SESSION_REQUEST, "Session Request" }, + { KIP_SESSION_RESPONSE, "Session Response" }, + { KIP_SESSION_AUTHENTICATE, "Session Authenticate" }, + { KIP_SESSION_STATUS, "Session Status" }, + { KIP_TIMER_NOTIFY, "Timer Notify" }, + { 0, NULL} +}; +static const value_string svc_vals[] = { /* abbreviated service names */ + { KIP_SEARCH_REQUEST, "SearchReq" }, + { KIP_SEARCH_RESPONSE, "SearchResp" }, + { KIP_DESCRIPTION_REQUEST, "DescrReq" }, + { KIP_DESCRIPTION_RESPONSE, "DescrResp" }, + { KIP_CONNECT_REQUEST, "ConnectReq" }, + { KIP_CONNECT_RESPONSE, "ConnectResp" }, + { KIP_CONNECTIONSTATE_REQUEST, "ConnStateReq" }, + { KIP_CONNECTIONSTATE_RESPONSE, "ConnStateResp" }, + { KIP_DISCONNECT_REQUEST, "DisconnectReq" }, + { KIP_DISCONNECT_RESPONSE, "DisconnectResp" }, + { KIP_SEARCH_REQUEST_EXT, "SearchReqExt" }, + { KIP_SEARCH_RESPONSE_EXT, "SearchRespExt" }, + { KIP_CONFIGURATION_REQUEST, "ConfigReq" }, + { KIP_CONFIGURATION_ACK, "ConfigAck" }, + { KIP_TUNNELING_REQUEST, "TunnelReq" }, + { KIP_TUNNELING_ACK, "TunnelAck" }, + { KIP_TUNNELING_FEATURE_GET, "TunnelFeatureGet" }, + { KIP_TUNNELING_FEATURE_RESPONSE, "TunnelFeatureResp" }, + { KIP_TUNNELING_FEATURE_SET, "TunnelFeatureSet" }, + { KIP_TUNNELING_FEATURE_INFO, "TunnelFeatureInfo" }, + { KIP_ROUTING_INDICATION, "RoutingInd" }, + { KIP_ROUTING_LOST_MESSAGE, "RoutingLoss" }, + { KIP_ROUTING_BUSY, "RoutingBusy" }, + { KIP_ROUTING_SYSTEM_BROADCAST, "RoutingSBC" }, + { KIP_REMOTE_DIAG_REQUEST, "RemoteDiagReq" }, + { KIP_REMOTE_DIAG_RESPONSE, "RemoteDiagResp" }, + { KIP_REMOTE_CONFIG_REQUEST, "RemoteConfigReq" }, + { KIP_REMOTE_RESET_REQUEST, "RemoteResetReq" }, + { KIP_SECURE_WRAPPER, "SecureWrapper" }, + { KIP_SESSION_REQUEST, "SessionReq" }, + { KIP_SESSION_RESPONSE, "SessionResp" }, + { KIP_SESSION_AUTHENTICATE, "SessionAuth" }, + { KIP_SESSION_STATUS, "SessionStatus" }, + { KIP_TIMER_NOTIFY, "TimerNotify" }, + { 0, NULL} +}; +static const value_string host_protocol_vals[] = { + { KIP_IPV4_UDP, "IPv4 UDP" }, + { KIP_IPV4_TCP, "IPv4 TCP" }, + { 0, NULL} +}; +static const value_string description_type_vals[] = { + { KIP_DIB_DEVICE_INFO, "Device Information" }, + { KIP_DIB_SUPP_SVC_FAMILIES, "Supported Service Families" }, + { KIP_DIB_IP_CONFIG, "IP Configuration" }, + { KIP_DIB_CUR_CONFIG, "Current Configuration" }, + { KIP_DIB_KNX_ADDRESSES, "KNX Addresses" }, + { KIP_DIB_SECURED_SERVICE_FAMILIES, "Secured Service Families" }, + { KIP_DIB_TUNNELING_INFO, "Tunneling Information" }, + { KIP_DIB_EXTENDED_DEVICE_INFO, "Extended Device Information" }, + { KIP_DIB_MFR_DATA, "Manufacturer Data" }, + { 0, NULL} +}; +static const value_string descr_type_vals[] = { /* abbreviated DIB names */ + { KIP_DIB_DEVICE_INFO, "DevInfo" }, + { KIP_DIB_SUPP_SVC_FAMILIES, "SuppSvc" }, + { KIP_DIB_IP_CONFIG, "IpConfig" }, + { KIP_DIB_CUR_CONFIG, "CurConfig" }, + { KIP_DIB_KNX_ADDRESSES, "KnxAddr" }, + { KIP_DIB_SECURED_SERVICE_FAMILIES, "SecSvcFam" }, + { KIP_DIB_TUNNELING_INFO, "TunnelInfo" }, + { KIP_DIB_EXTENDED_DEVICE_INFO, "ExtDevInfo" }, + { KIP_DIB_MFR_DATA, "MfrData" }, + { 0, NULL} +}; +#if 0 +static const value_string search_request_parameter_type_vals[] = { + { KIP_SRP_BY_PROGMODE, "By programming mode" }, + { KIP_SRP_BY_MACADDR, "By MAC address" }, + { KIP_SRP_BY_SERVICE, "By service" }, + { KIP_SRP_REQUEST_DIBS, "Request DIBs" }, + { 0, NULL } +}; +#endif +static const value_string srp_type_vals[] = { /* abbreviated SRP names */ + { KIP_SRP_BY_PROGMODE, "ProgMode" }, + { KIP_SRP_BY_MACADDR, "MacAddr" }, + { KIP_SRP_BY_SERVICE, "Service" }, + { KIP_SRP_REQUEST_DIBS, "Dibs" }, + { 0, NULL } +}; +static const value_string medium_type_vals[] = { + { KIP_KNXTYPE_TP0, "TP0" }, + { KIP_KNXTYPE_TP1, "TP1" }, + { KIP_KNXTYPE_PL110, "PL110" }, + { KIP_KNXTYPE_PL132, "PL132" }, + { KIP_KNXTYPE_RF, "RF" }, + { KIP_KNXTYPE_IP, "IP" }, + { 0, NULL} +}; +static const value_string connection_type_vals[] = { + { KIP_DEVICE_MGMT_CONNECTION, "Device Management Connection" }, + { KIP_TUNNEL_CONNECTION, "Tunneling Connection" }, + { KIP_REMLOG_CONNECTION, "Remote Logging Connection" }, + { KIP_REMCONF_CONNECTION, "Remote Configuration Connection" }, + { KIP_OBJSVR_CONNECTION, "Object Server Connection" }, + { 0, NULL} +}; +static const value_string conn_type_vals[] = { + { KIP_DEVICE_MGMT_CONNECTION, "Config" }, + { KIP_TUNNEL_CONNECTION, "Tunnel" }, + { KIP_REMLOG_CONNECTION, "RemoteLogging" }, + { KIP_REMCONF_CONNECTION, "RemoteConfig" }, + { KIP_OBJSVR_CONNECTION, "ObjectServer" }, + { 0, NULL } +}; +static const value_string tunneling_feature_id_vals[] = { + { KIP_TUNNELING_FEATURE_ID_SUPPORTED_EMI_TYPE, "SupportedEmiType" }, + { KIP_TUNNELING_FEATURE_ID_HOST_DEVICE_DEVICE_DESCRIPTOR_TYPE_0, "MaskVersion" }, + { KIP_TUNNELING_FEATURE_ID_BUS_CONNECTION_STATUS, "BusStatus" }, + { KIP_TUNNELING_FEATURE_ID_KNX_MANUFACTURER_CODE, "Manufacturer" }, + { KIP_TUNNELING_FEATURE_ID_ACTIVE_EMI_TYPE, "ActiveEmiType" }, + { KIP_TUNNELING_FEATURE_ID_INDIVIDUAL_ADDRESS, "IndividualAddress" }, + { KIP_TUNNELING_FEATURE_ID_MAX_APDU_LENGTH, "MaxApduLength" }, + { KIP_TUNNELING_FEATURE_ID_INFO_SERVICE_ENABLE, "InfoServiceEnable" }, + { 0, NULL } +}; +static const value_string knx_layer_vals[] = { + { TUNNEL_LINKLAYER, "LinkLayer" }, + { TUNNEL_RAW, "Raw" }, + { TUNNEL_BUSMONITOR, "Busmonitor" }, + { 0, NULL} +}; +static const value_string error_vals[] = { + { KIP_E_NO_ERROR, "OK" }, + { KIP_E_CONNECTION_ID, "E_CONNECTION_ID" }, + { KIP_E_CONNECTION_TYPE, "E_CONNECTION_TYPE" }, + { KIP_E_CONNECTION_OPTION, "E_CONNECTION_OPTION" }, + { KIP_E_NO_MORE_CONNECTIONS, "E_NO_MORE_CONNECTIONS" }, + { KIP_E_NO_MORE_UNIQUE_CONNECTIONS, "E_NO_MORE_UNIQUE_CONNECTIONS" }, + { KIP_E_DATA_CONNECTION, "E_DATA_CONNECTION" }, + { KIP_E_KNX_CONNECTION, "E_KNX_CONNECTION" }, + { KIP_E_TUNNELING_LAYER, "E_TUNNELING_LAYER" }, + { 0, NULL} +}; +static const value_string session_status_vals[] = { + { SESSION_STATUS_AUTHENTICATION_SUCCESS, "STATUS_AUTHENTICATION_SUCCESS" }, + { SESSION_STATUS_AUTHENTICATION_FAILED, "STATUS_AUTHENTICATION_FAILED" }, + { SESSION_STATUS_UNAUTHENTICATED, "STATUS_UNAUTHENTICATED" }, + { SESSION_STATUS_TIMEOUT, "STATUS_TIMEOUT" }, + { SESSION_STATUS_KEEPALIVE, "STATUS_KEEPALIVE" }, + { SESSION_STATUS_CLOSE, "STATUS_CLOSE" }, + { 0, NULL } +}; + +guint8 knxip_error; +guint8 knxip_host_protocol; + +expert_field ei_knxip_error = EI_INIT; +expert_field ei_knxip_warning = EI_INIT; + +static const gchar* pref_key_texts[ MAX_KNX_DECRYPTION_KEYS ]; +//static const gchar* authentication_code_text; +//static const gchar* password_hash_text; +static const gchar* pref_key_file_name; +static const gchar* pref_key_file_pwd; +static const gchar* pref_key_info_file_name; + +/* KNX decryption keys +*/ +guint8 knx_decryption_keys[ MAX_KNX_DECRYPTION_KEYS ][ KNX_KEY_LENGTH ]; +guint8 knx_decryption_key_count; + +/* Forward declarations +*/ +static void dissect_knxip( guint8 level, tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree ); +void proto_reg_handoff_knxip( void ); + +/* Add raw data to list view, tree view, and parent folder +*/ +static proto_item* knxip_tree_add_data( proto_tree* tree, tvbuff_t* tvb, gint offset, gint length, column_info* cinfo, proto_item* item, + const gchar* name, const gchar* text1, const gchar* text2 ) +{ + proto_item* new_item = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, length, NULL, "%s: $", name ); + if( text1 ) col_append_str( cinfo, COL_INFO, text1 ); + if( text2 ) proto_item_append_text( item, "%s", text2 ); + + while( length > 0 ) + { + guint8 value = tvb_get_guint8( tvb, offset ); + if( text1 ) col_append_fstr( cinfo, COL_INFO, "%02X", value ); + if( text2 ) proto_item_append_text( item, "%02X", value ); + proto_item_append_text( new_item, " %02X", value ); + offset++; + length--; + } + + return new_item; +} + +/* Show unknown or unexpected data +*/ +static proto_item* knxip_tree_add_unknown_data( proto_tree* tree, tvbuff_t* tvb, gint offset, gint length ) +{ + return proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, length, NULL, "? Unknown data (%d bytes)", length ); +} + +static guint8 hex_to_knx_key( const gchar* text, guint8 key[ KNX_KEY_LENGTH ] ) +{ + size_t n_bytes = 0; + guint8* bytes = convert_string_to_hex( text, &n_bytes ); + if( bytes == NULL ) + { + n_bytes = 0; + } + else + { + if( n_bytes ) + { + if( n_bytes > KNX_KEY_LENGTH ) n_bytes = KNX_KEY_LENGTH; + if( n_bytes ) memcpy( key, bytes, n_bytes ); + while( n_bytes < KNX_KEY_LENGTH ) key[ n_bytes++ ] = 0; + } + g_free( bytes ); + } + return n_bytes != 0; +} + +static proto_item* knxip_tree_add_status( proto_tree* tree, tvbuff_t* tvb, gint offset ) +{ + return proto_tree_add_item( tree, hf_knxip_status, tvb, offset, 1, ENC_BIG_ENDIAN ); +} + +static proto_item* knxip_tree_add_reserved( proto_tree* tree, tvbuff_t* tvb, gint offset, packet_info* pinfo, guint8* p_ok ) +{ + proto_item* new_item = proto_tree_add_item( tree, hf_knxip_reserved, tvb, offset, 1, ENC_BIG_ENDIAN ); + if( tvb_get_guint8( tvb, offset ) ) + { + proto_item_prepend_text( new_item, "? " ); + expert_add_info_format( pinfo, new_item, KIP_ERROR, "Expected: 0x00" ); + if( p_ok ) *p_ok = 0; + } + return new_item; +} + +static proto_item* knxip_tree_add_missing_reserved( proto_tree* tree, packet_info* pinfo ) +{ + proto_item* new_item = proto_tree_add_debug_text( tree, "? Reserved" ); + expert_add_info_format( pinfo, new_item, KIP_ERROR, "Expected: 1 byte" ); + return new_item; +} + +static proto_item* knxip_tree_add_length( proto_tree* tree, tvbuff_t* tvb, gint offset, gint value ) +{ + return proto_tree_add_uint_format_value( tree, hf_knxip_structure_length, tvb, offset, 1, value, "%u bytes", value ); +} + +static void knxip_item_illegal_length( proto_item* length_item, packet_info* pinfo, const gchar* info ) +{ + proto_item_prepend_text( length_item, "? " ); + expert_add_info_format( pinfo, length_item, KIP_ERROR, "%s", info ); +} + +static proto_item* knxip_tree_add_ip_address( proto_tree* tree, tvbuff_t* tvb, gint offset, gchar* output, gint output_max ) +{ + if( output ) + { + const guint8* ipa = tvb_get_ptr( tvb, offset, 4 ); + g_snprintf( output, output_max, "%u.%u.%u.%u", ipa[ 0 ], ipa[ 1 ], ipa[ 2 ], ipa[ 3 ] ); + } + return proto_tree_add_item( tree, hf_knxip_ip_address, tvb, offset, 4, ENC_BIG_ENDIAN ); +} + +static proto_item* knxip_tree_add_knx_address( proto_tree* tree, gint hfindex, tvbuff_t* tvb, gint offset, gchar* output, gint output_max ) +{ + guint16 value = tvb_get_ntohs( tvb, offset ); + gchar text[ 32 ]; + g_snprintf( text, sizeof text, "%u.%u.%u", (value >> 12) & 0xF, (value >> 8) & 0xF, value & 0xFF ); + if( output ) g_snprintf( output, output_max, "%s", text ); + proto_item* new_item = proto_tree_add_item( tree, hfindex, tvb, offset, 2, ENC_BIG_ENDIAN ); + proto_item_append_text( new_item, " = %s", text ); + return new_item; +} + +static proto_item* knxip_tree_add_bit( proto_tree* tree, tvbuff_t* tvb, gint offset, gint bitpos, const gchar* name, gchar* output, gint output_max ) +{ + gchar format[ 32 ] = ".... .... = %s: %d"; + guint8 value = (tvb_get_guint8( tvb, offset ) >> bitpos) & 1; + format[ 7 - bitpos + (bitpos < 4) ] = '0' + value; + + if( value && output ) + { + if( *output ) + { + do { ++output; --output_max; } while( *output ); + g_snprintf( output, output_max, " | " ); + while( *output ) { ++output; --output_max; } + } + + g_snprintf( output, output_max, "%s", name ); + } + + return proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, 1, NULL, format, name, value ); +} + +static proto_item* knxip_tree_add_ip_assignment( proto_tree* tree, gint hfindex, tvbuff_t* tvb, gint offset, guint8 manual ) +{ + proto_item* node = proto_tree_add_item( tree, hfindex, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree* list = proto_item_add_subtree( node, ett_ip_assignment ); + gchar output[ 128 ]; + *output = '\0'; + knxip_tree_add_bit( list, tvb, offset, 2 + manual, "AutoIP", output, sizeof output ); + knxip_tree_add_bit( list, tvb, offset, 1 + manual, "DHCP", output, sizeof output ); + knxip_tree_add_bit( list, tvb, offset, 0 + manual, "BootP", output, sizeof output ); + if( manual ) knxip_tree_add_bit( list, tvb, offset, 0, "manual", output, sizeof output ); + if( *output ) proto_item_append_text( node, " = %s", output ); + return node; +} + +/* Dissect HPAI field +*/ +static guint8 dissect_hpai( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset, guint8* p_ok, gchar* name, guint8 check_protocol ) +{ + guint8 ok = 1; + gint offset = *p_offset; + gint remaining_len = tvb_captured_length_remaining( tvb, offset ); + guint8 struct_len = (remaining_len <= 0) ? 0 : tvb_get_guint8( tvb, offset ); + gint eff_struct_len = (struct_len <= remaining_len) ? struct_len : remaining_len; + + proto_item* hpai_item = proto_tree_add_none_format( tree, hf_folder, tvb, offset, eff_struct_len, "HPAI %s Endpoint", name ); + + gchar info[ 80 ]; + gchar* output = info; + gint output_max = sizeof info; + g_snprintf( info, sizeof info, "???" ); + + if( struct_len <= 0 ) + { + proto_item_prepend_text( hpai_item, "Missing " ); + expert_add_info_format( pinfo, hpai_item, KIP_ERROR, "Expected: 8 bytes" ); + ok = 0; + } + else + { + /* 1 byte Structure Length */ + proto_tree* hpai_tree = proto_item_add_subtree( hpai_item, ett_hpai ); + proto_item* length_item = knxip_tree_add_length( hpai_tree, tvb, offset, struct_len ); + proto_item* node; + + gint end_pos = offset + eff_struct_len; + offset++; + + if( struct_len != 8 ) + { + knxip_item_illegal_length( length_item, pinfo, "Expected: 8 bytes" ); + ok = 0; + } + + if( struct_len > remaining_len ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Available: %u bytes", remaining_len ); + struct_len = (guint8) remaining_len; + + if( ok ) + { + proto_item_prepend_text( length_item, "? " ); + ok = 0; + } + } + else if( struct_len < 2 ) + { + expert_add_info_format( pinfo, hpai_item, KIP_ERROR, "Missing 1 byte Host Protocol" ); + ok = 0; + } + else + { + /* 1 byte Host Protocol */ + guint8 host_protocol = tvb_get_guint8( tvb, offset ); + const gchar* host_protocol_name = "???"; + guint8 protocol_error = 0; + + node = proto_tree_add_item( hpai_tree, hf_knxip_host_protocol, tvb, offset, 1, ENC_BIG_ENDIAN ); + + if( host_protocol == KIP_IPV4_UDP ) + { + host_protocol_name = "UDP"; + if( check_protocol ) + { + if( knxip_host_protocol != IP_PROTO_UDP && knxip_host_protocol != IP_PROTO_UDPLITE ) + { + protocol_error = 1; + } + } + } + else if( host_protocol == KIP_IPV4_TCP ) + { + host_protocol_name = "TCP"; + if( check_protocol ) + { + if( knxip_host_protocol != IP_PROTO_TCP ) + { + protocol_error = 1; + } + } + } + else + { + protocol_error = 2; + } + + if( protocol_error ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, (protocol_error == 1) ? "Wrong Host Protocol" : "Expected: 0x01 or 0x02" ); + ok = 0; + } + + offset++; + + if( struct_len < 6 ) + { + expert_add_info_format( pinfo, hpai_item, KIP_ERROR, "Missing 4 bytes IP Address" ); + ok = 0; + } + else + { + /* 4 bytes IP Address */ + node = knxip_tree_add_ip_address( hpai_tree, tvb, offset, output, output_max ); + + if( host_protocol == KIP_IPV4_TCP && strcmp( output, "0.0.0.0" ) != 0 ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 0.0.0.0" ); + ok = 0; + } + + offset += 4; + + while( *output ) { ++output; --output_max; } + if( output_max > 1 ) { *output++ = ':'; --output_max; } + g_snprintf( output, output_max, "???" ); + + if( struct_len < 8 ) + { + expert_add_info_format( pinfo, hpai_item, KIP_ERROR, "Missing 2 bytes Port Number" ); + ok = 0; + } + else + { + /* 2 bytes Port Number */ + guint16 port = tvb_get_ntohs( tvb, offset ); + + g_snprintf( output, output_max, "%u", port ); + while( *output ) { ++output; --output_max; } + + node = proto_tree_add_item( hpai_tree, hf_knxip_port, tvb, offset, 2, ENC_BIG_ENDIAN ); + + if( host_protocol == KIP_IPV4_TCP && port != 0 ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 0" ); + ok = 0; + } + + offset += 2; + } + } + + if( offset < end_pos ) + { + knxip_tree_add_unknown_data( hpai_tree, tvb, offset, end_pos - offset ); + ok = 0; + } + + proto_item_append_text( hpai_item, ": %s %s", info, host_protocol_name ); + } + } + + col_append_fstr( pinfo->cinfo, COL_INFO, " @%s", info ); + proto_item_append_text( item, ", %s @ %s", name, info ); + + if( !ok ) + { + proto_item_prepend_text( hpai_item, "? " ); + if( p_ok ) *p_ok = 0; + } + + *p_offset += struct_len; + return struct_len; +} + +/* Dissect CRI (= Connection Request Information) +*/ +static guint8 dissect_cri( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset, guint8* p_ok ) +{ + gint offset = *p_offset; + gint remaining_len = tvb_captured_length_remaining( tvb, offset ); + guint8 struct_len = (remaining_len <= 0) ? 0 : tvb_get_guint8( tvb, offset ); + gint eff_struct_len = (struct_len <= remaining_len) ? struct_len : remaining_len; + + proto_item* cri_item = proto_tree_add_none_format( tree, hf_folder, tvb, offset, eff_struct_len, "CRI" ); + + guint8 conn_type = 0; + const gchar* conn_type_name = NULL; + guint8 ok = 0; + gchar extra_text[ 32 ]; + *extra_text = '\0'; + + if( struct_len <= 0 ) + { + proto_item_prepend_text( cri_item, "Missing " ); + expert_add_info_format( pinfo, cri_item, KIP_ERROR, "Expected: min 2 bytes" ); + //ok = 0; + } + else + { + proto_tree* cri_tree = proto_item_add_subtree( cri_item, ett_cri ); + proto_item* length_item = knxip_tree_add_length( cri_tree, tvb, offset, struct_len ); + proto_item* type_item = NULL; + guint8 length_ok = 1; + + if( struct_len > remaining_len ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Available: %u bytes", remaining_len ); + struct_len = (guint8) remaining_len; + //ok = 0; + length_ok = 0; + } + + if( struct_len < 2 ) + { + expert_add_info_format( pinfo, cri_item, KIP_ERROR, "Missing 1 byte Connection Type" ); + //ok = 0; + } + else + { + conn_type = tvb_get_guint8( tvb, offset + 1 ); + type_item = proto_tree_add_item( cri_tree, hf_knxip_connection_type, tvb, offset + 1, 1, ENC_BIG_ENDIAN ); + conn_type_name = try_val_to_str( conn_type, connection_type_vals ); + if( !conn_type_name ) + { + proto_item_prepend_text( type_item, "? " ); + expert_add_info_format( pinfo, type_item, KIP_ERROR, "Unknown" ); + //ok = 0; + + if( struct_len > 2 ) + { + knxip_tree_add_unknown_data( cri_tree, tvb, offset + 2, struct_len - 2 ); + } + } + else + { + proto_item_append_text( cri_item, " %s", conn_type_name ); + ok = 1; + + switch( conn_type ) + { + case KIP_DEVICE_MGMT_CONNECTION: + case KIP_REMLOG_CONNECTION: + case KIP_REMCONF_CONNECTION: + case KIP_OBJSVR_CONNECTION: + if( struct_len > 2 ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Expected: 2 bytes" ); + length_ok = 0; + + knxip_tree_add_unknown_data( cri_tree, tvb, offset + 2, struct_len - 2 ); + ok = 0; + } + break; + + case KIP_TUNNEL_CONNECTION: + if( (struct_len != 4) && (struct_len != 6) ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Expected: 4 or 6 bytes" ); + length_ok = 0; + ok = 0; + } + if( struct_len >= 3 ) + { + guint8 knx_layer = tvb_get_guint8( tvb, offset + 2 ); + const gchar* knx_layer_name = try_val_to_str( knx_layer, knx_layer_vals ); + proto_item* layer_item = proto_tree_add_item( cri_tree, hf_knxip_knx_layer, tvb, offset + 2, 1, ENC_BIG_ENDIAN ); + proto_item_append_text( cri_item, ", Layer: %s", knx_layer_name ? knx_layer_name : "Unknown" ); + if( !knx_layer_name ) + { + proto_item_prepend_text( layer_item, "? " ); + expert_add_info_format( pinfo, layer_item, KIP_ERROR, "Expected: 0x02" ); + ok = 0; + } + + if( struct_len < 4 ) + { + expert_add_info_format( pinfo, cri_item, KIP_ERROR, "Missing Reserved byte" ); + ok = 0; + } + else + { + knxip_tree_add_reserved( cri_tree, tvb, offset + 3, pinfo, &ok ); + } + if( struct_len >= 6 ) + { + knxip_tree_add_knx_address( cri_tree, hf_knxip_knx_address, tvb, offset + 4, extra_text, sizeof extra_text ); + } + if( struct_len > 6 ) + { + knxip_tree_add_unknown_data( cri_tree, tvb, offset + 6, struct_len - 6 ); + ok = 0; + } + } + break; + } + } + } + + if( !length_ok ) + { + proto_item_prepend_text( length_item, "? " ); + } + } + + conn_type_name = try_val_to_str( conn_type, conn_type_vals ); + if( !conn_type_name ) + { + ok = 0; + } + else + { + if( pinfo ) + { + column_info* cinfo = pinfo->cinfo; + col_prepend_fstr( cinfo, COL_INFO, "%s ", conn_type_name ); + if( *extra_text ) + { + col_append_fstr( cinfo, COL_INFO, ", %s", extra_text ); + } + } + + proto_item_append_text( item, ", %s", conn_type_name ); + if( *extra_text ) + { + proto_item_append_text( item, ", %s", extra_text ); + } + } + + if( !ok ) + { + proto_item_prepend_text( cri_item, "? " ); + if( p_ok ) *p_ok = 0; + } + + *p_offset += struct_len; + return struct_len; +} + +/* Dissect CRD (= Connection Response Data) +*/ +static guint8 dissect_crd( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset, guint8* p_ok ) +{ + gint offset = *p_offset; + gint remaining_len = tvb_captured_length_remaining( tvb, offset ); + guint8 struct_len = (remaining_len <= 0) ? 0 : tvb_get_guint8( tvb, offset ); + gint eff_struct_len = (struct_len <= remaining_len) ? struct_len : remaining_len; + + proto_item* crd_item = proto_tree_add_none_format( tree, hf_folder, tvb, offset, eff_struct_len, "CRD" ); + + guint8 conn_type = 0; + const gchar* conn_type_name = NULL; + guint8 ok = 0; + + if( struct_len <= 0 ) + { + proto_item_prepend_text( crd_item, "Missing " ); + expert_add_info_format( pinfo, crd_item, KIP_ERROR, "Expected: min 2 bytes" ); + //ok = 0; + } + else + { + proto_tree* crd_tree = proto_item_add_subtree( crd_item, ett_crd ); + proto_item* length_item = knxip_tree_add_length( crd_tree, tvb, offset, struct_len ); + proto_item* type_item = NULL; + guint8 length_ok = 1; + + if( struct_len > remaining_len ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Available: %u bytes", remaining_len ); + struct_len = (guint8) remaining_len; + //ok = 0; + length_ok = 0; + } + + if( struct_len < 2 ) + { + expert_add_info_format( pinfo, crd_item, KIP_ERROR, "Missing 1 byte Connection Type" ); + //ok = 0; + } + else + { + conn_type = tvb_get_guint8( tvb, offset + 1 ); + type_item = proto_tree_add_item( crd_tree, hf_knxip_connection_type, tvb, offset + 1, 1, ENC_BIG_ENDIAN ); + conn_type_name = try_val_to_str( conn_type, connection_type_vals ); + if( !conn_type_name ) + { + proto_item_prepend_text( type_item, "? " ); + expert_add_info_format( pinfo, type_item, KIP_ERROR, "Unknown" ); + //ok = 0; + + if( struct_len > 2 ) + { + knxip_tree_add_unknown_data( crd_tree, tvb, offset + 2, struct_len - 2 ); + } + } + else + { + proto_item_append_text( crd_item, " %s", conn_type_name ); + ok = 1; + + switch( conn_type ) + { + case KIP_DEVICE_MGMT_CONNECTION: + case KIP_REMLOG_CONNECTION: + case KIP_REMCONF_CONNECTION: + case KIP_OBJSVR_CONNECTION: + if( struct_len > 2 ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Expected: 2 bytes" ); + knxip_tree_add_unknown_data( crd_tree, tvb, offset + 2, struct_len - 2 ); + ok = 0; + length_ok = 0; + } + break; + + case KIP_TUNNEL_CONNECTION: + if( struct_len != 4 ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Expected: 4 bytes" ); + ok = 0; + length_ok = 0; + } + + if( struct_len < 4 ) + { + expert_add_info_format( pinfo, crd_item, KIP_ERROR, "Missing 2 bytes KNX Address" ); + //ok = 0; + if( struct_len > 2 ) + { + knxip_tree_add_unknown_data( crd_tree, tvb, offset + 2, struct_len - 2 ); + } + } + else + { + gchar output[ 40 ]; + knxip_tree_add_knx_address( crd_tree, hf_knxip_knx_address, tvb, offset + 2, output, sizeof output ); + proto_item_append_text( crd_item, ", KNX Address: %s", output ); + if( pinfo ) + { + col_append_fstr( pinfo->cinfo, COL_INFO, ", %s", output ); + } + if( item ) + { + proto_item_append_text( item, ", %s", output ); + } + if( struct_len > 4 ) + { + knxip_tree_add_unknown_data( crd_tree, tvb, offset + 4, struct_len - 4 ); + //ok = 0; + } + } + break; + } + } + } + + if( !length_ok ) + { + proto_item_prepend_text( length_item, "? " ); + } + } + + conn_type_name = try_val_to_str( conn_type, conn_type_vals ); + if( pinfo && conn_type_name ) col_prepend_fstr( pinfo->cinfo, COL_INFO, "%s ", conn_type_name ); + proto_item_append_text( item, ", %s", conn_type_name ? conn_type_name : "???" ); + + if( !ok ) + { + proto_item_prepend_text( crd_item, "? " ); + if( p_ok ) *p_ok = 0; + } + + *p_offset += struct_len; + return struct_len; +} + +/* Dissect Connection Header +*/ +static guint8 dissect_cnhdr( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset, guint8* p_ok, guint8 response ) +{ + gint offset = *p_offset; + gint remaining_len = tvb_captured_length_remaining( tvb, offset ); + guint8 struct_len = (remaining_len <= 0) ? 0 : tvb_get_guint8( tvb, offset ); + gint eff_struct_len = (struct_len <= remaining_len) ? struct_len : remaining_len; + + proto_item* cnhdr_item = proto_tree_add_none_format( tree, hf_folder, tvb, offset, eff_struct_len, "Connection Header" ); + + guint8 ok = 0; + gchar info[ 100 ]; + gint output_max = sizeof info; + gchar* output = info; + + *output++ = '#'; + output_max--; + g_snprintf( output, output_max, "???" ); + + if( struct_len <= 0 ) + { + proto_item_prepend_text( cnhdr_item, "Missing " ); + expert_add_info_format( pinfo, cnhdr_item, KIP_ERROR, "Expected: 4 bytes" ); + } + else + { + proto_tree* cnhdr_tree = proto_item_add_subtree( cnhdr_item, ett_cnhdr ); + proto_item* length_item = knxip_tree_add_length( cnhdr_tree, tvb, offset, struct_len ); + + gint end_pos = offset + eff_struct_len; + offset++; + + if( struct_len == 4 ) + { + ok = 1; + } + else + { + knxip_item_illegal_length( length_item, pinfo, "Expected: 4 bytes" ); + } + + if( struct_len > remaining_len ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Available: %u bytes", remaining_len ); + struct_len = (guint8) remaining_len; + + if( ok ) + { + proto_item_prepend_text( length_item, "? " ); + ok = 0; + } + } + + if( struct_len < 2 ) + { + expert_add_info_format( pinfo, cnhdr_item, KIP_ERROR, "Missing 1 byte Channel" ); + //ok = 0; + } + else + { + g_snprintf( output, output_max, "%02X:", tvb_get_guint8( tvb, offset ) ); + while( *output ) { ++output; --output_max; } + g_snprintf( output, output_max, "???" ); + + proto_tree_add_item( cnhdr_tree, hf_knxip_channel, tvb, offset, 1, ENC_BIG_ENDIAN ); + offset++; + + if( struct_len < 3 ) + { + expert_add_info_format( pinfo, cnhdr_item, KIP_ERROR, "Missing 1 byte Sequence Counter" ); + //ok = 0; + } + else + { + g_snprintf( output, output_max, "%u", tvb_get_guint8( tvb, offset ) ); + while( *output ) { ++output; --output_max; } + + proto_tree_add_item( cnhdr_tree, hf_knxip_seq_counter, tvb, offset, 1, ENC_BIG_ENDIAN ); + offset++; + + if( response ) + { + if( output_max > 1 ) + { + *output++ = ' '; + output_max--; + g_snprintf( output, output_max, "???" ); + } + } + + if( struct_len < 4 ) + { + expert_add_info_format( pinfo, cnhdr_item, KIP_ERROR, "Missing 1 byte %s", response ? "Status" : "Reserved" ); + //ok = 0; + } + else + { + if( response ) + { + g_snprintf( output, output_max, "%s", val_to_str( tvb_get_guint8( tvb, offset ), error_vals, "Error 0x%02x" ) ); + knxip_tree_add_status( cnhdr_tree, tvb, offset ); + } + else + { + knxip_tree_add_reserved( cnhdr_tree, tvb, offset, pinfo, &ok ); + } + + offset++; + } + } + + if( offset < end_pos ) + { + knxip_tree_add_unknown_data( cnhdr_tree, tvb, offset, end_pos - offset ); + //ok = 0; + } + + proto_item_append_text( cnhdr_item, ": %s", info ); + } + } + + if( pinfo ) col_append_fstr( pinfo->cinfo, COL_INFO, " %s", info ); + proto_item_append_text( item, ", %s", info ); + + if( !ok ) + { + proto_item_prepend_text( cnhdr_item, "? " ); + if( p_ok ) *p_ok = 0; + } + + *p_offset += struct_len; + return struct_len; +} + +/* Dissect tunneling feature frames. +*/ +static void dissect_tunneling_feature( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset, guint8* p_ok, guint16 service ) +{ + column_info* cinfo = pinfo->cinfo; + gint offset = *p_offset; + gint remaining_len; + proto_item* node; + guint8 c; + const gchar* name; + guint8 ok = 1; + guint8 isResponse = (service == KIP_TUNNELING_FEATURE_RESPONSE); + guint8 status = 0; + + /* Connection Header */ + dissect_cnhdr( tvb, pinfo, item, tree, &offset, &ok, FALSE ); + + remaining_len = tvb_captured_length_remaining( tvb, offset ); + + /* 1 byte Feature Identifier */ + if( remaining_len <= 0 ) + { + node = proto_tree_add_debug_text( tree, "? Feature Identifier" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + ok = 0; + } + else + { + c = tvb_get_guint8( tvb, offset ); + name = try_val_to_str( c, tunneling_feature_id_vals ); + if( !name ) name = "Unknown"; + node = proto_tree_add_item( tree, hf_knxip_tunnel_feature, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_item_append_text( node, " = %s", name ); + proto_item_append_text( item, " %s", name ); + col_append_fstr( cinfo, COL_INFO, " %s", name ); + + ++offset; + --remaining_len; + } + + /* 1 byte Return Code / Reserved */ + name = isResponse ? "Status" : "Reserved"; + if( remaining_len <= 0 ) + { + node = proto_tree_add_debug_text( tree, "? %s", name ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + ok = 0; + } + else + { + status = tvb_get_guint8( tvb, offset ); + proto_tree_add_item( tree, isResponse ? hf_knxip_status : hf_knxip_reserved, tvb, offset, 1, ENC_BIG_ENDIAN ); + + if( isResponse && (status != 0 || remaining_len == 1) ) + { + proto_item_append_text( item, " E=$%02X", status ); + col_append_fstr( cinfo, COL_INFO, " E=$%02X", status ); + } + + ++offset; + --remaining_len; + } + + /* Feature Value */ + if( remaining_len <= 0 ) + { + if( service != KIP_TUNNELING_FEATURE_GET && status == 0 ) + { + node = proto_tree_add_debug_text( tree, "? Feature Value" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Missing" ); + ok = 0; + } + } + else + { + node = knxip_tree_add_data( tree, tvb, offset, remaining_len, cinfo, item, "Feature Value", " $", " $" ); + if( service == KIP_TUNNELING_FEATURE_GET ) + { + expert_add_info_format( pinfo, node, KIP_ERROR, "Unexpected" ); + ok = 0; + } + offset += remaining_len; + } + + *p_offset = offset; + + if( p_ok && !ok ) *p_ok = 0; +} + +/* Dissect cEMI +*/ +static void dissect_cemi( tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, gint* p_offset ) +{ + gint offset = *p_offset; + gint remaining_len = tvb_captured_length_remaining( tvb, offset ); + + /* Call the cEMI data dissector for the remaining bytes + */ + tvb = tvb_new_subset_remaining( tvb, offset ); + + dissector_handle_t cemi_handle = find_dissector( "cemi" ); + if( cemi_handle ) + { + call_dissector( cemi_handle, tvb, pinfo, tree ); + } + + + *p_offset = offset + remaining_len; +} + +/* Dissect ROUTING_LOSS +*/ +static guint8 dissect_routing_loss( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset ) +{ + gint offset = *p_offset; + gint remaining_len = tvb_captured_length_remaining( tvb, offset ); + guint8 struct_len = (remaining_len <= 0) ? 0 : tvb_get_guint8( tvb, offset ); + gint eff_struct_len = (struct_len <= remaining_len) ? struct_len : remaining_len; + guint8 ok = 0; + + proto_item* info_item = proto_tree_add_none_format( tree, hf_folder, tvb, offset, struct_len, "Loss Info" ); + + gchar info[ 16 ]; + g_snprintf( info, sizeof info, "???" ); + + if( struct_len <= 0 ) + { + proto_item_prepend_text( info_item, "Missing " ); + expert_add_info_format( pinfo, info_item, KIP_ERROR, "Expected: 4 bytes" ); + } + else + { + proto_tree* info_tree = proto_item_add_subtree( info_item, ett_loss ); + proto_item* length_item = knxip_tree_add_length( info_tree, tvb, offset, struct_len ); + + gint end_pos = offset + eff_struct_len; + offset++; + + if( struct_len == 4 ) + { + ok = 1; + } + else + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Expected: 4 bytes" ); + } + + if( struct_len > remaining_len ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Available: %u bytes", remaining_len ); + struct_len = (guint8) remaining_len; + ok = 0; + } + + if( !ok ) + { + proto_item_prepend_text( length_item, "? " ); + } + + if( struct_len >= 2 ) + { + knxip_tree_add_status( info_tree, tvb, offset ); + offset++; + + /* 2 bytes Lost Messages */ + if( struct_len >= 4 ) + { + guint16 loss = tvb_get_ntohs( tvb, offset ); + g_snprintf( info, sizeof info, "%u", loss ); + proto_tree_add_item( info_tree, hf_knxip_routing_loss, tvb, offset, 2, ENC_BIG_ENDIAN ); + offset += 2; + } + + if( offset < end_pos ) + { + knxip_tree_add_unknown_data( info_tree, tvb, offset, end_pos - offset ); + } + + proto_item_append_text( info_item, ": %s", info ); + } + } + + if( pinfo ) col_append_fstr( pinfo->cinfo, COL_INFO, ": %s", info ); + proto_item_append_text( item, ": %s", info ); + + if( !ok ) + { + proto_item_prepend_text( info_item, "? " ); + } + + *p_offset += struct_len; + return ok; +} + +/* Dissect ROUTING_BUSY +*/ +static guint8 dissect_routing_busy( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset ) +{ + gint offset = *p_offset; + gint remaining_len = tvb_captured_length_remaining( tvb, offset ); + guint8 struct_len = (remaining_len <= 0) ? 0 : tvb_get_guint8( tvb, offset ); + gint eff_struct_len = (struct_len <= remaining_len) ? struct_len : remaining_len; + guint8 ok = 0; + + proto_item* info_item = proto_tree_add_none_format( tree, hf_folder, tvb, offset, eff_struct_len, "Busy Info" ); + + gchar info[ 16 ]; + g_snprintf( info, sizeof info, "???" ); + + if( struct_len <= 0 ) + { + proto_item_prepend_text( info_item, "Missing " ); + expert_add_info_format( pinfo, info_item, KIP_ERROR, "Expected: 6 bytes" ); + } + else + { + proto_tree* info_tree = proto_item_add_subtree( info_item, ett_loss ); + proto_item* length_item = knxip_tree_add_length( info_tree, tvb, offset, struct_len ); + + gint end_pos = offset + eff_struct_len; + offset++; + + if( struct_len == 6 ) + { + ok = 1; + } + else + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Expected: 6 bytes" ); + } + + if( struct_len > remaining_len ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Available: %u bytes", remaining_len ); + struct_len = (guint8) remaining_len; + ok = 0; + } + + if( !ok ) + { + proto_item_prepend_text( length_item, "? " ); + } + + if( struct_len >= 2 ) + { + knxip_tree_add_status( info_tree, tvb, offset ); + offset++; + + if( struct_len >= 4 ) + { + /* 2 bytes Wait Time (ms) */ + proto_item* new_item = proto_tree_add_item( info_tree, hf_knxip_busy_time, tvb, offset, 2, ENC_BIG_ENDIAN ); + proto_item_append_text( new_item, " ms" ); + g_snprintf( info, sizeof info, "%u ms", tvb_get_ntohs( tvb, offset ) ); + offset += 2; + + if( struct_len >= 6 ) + { + /* 2 bytes Control */ + proto_tree_add_item( info_tree, hf_knxip_busy_control, tvb, offset, 2, ENC_BIG_ENDIAN ); + offset += 2; + } + } + + if( offset < end_pos ) + { + knxip_tree_add_unknown_data( info_tree, tvb, offset, end_pos - offset ); + } + + proto_item_append_text( info_item, ": %s", info ); + } + } + + if( pinfo ) col_append_fstr( pinfo->cinfo, COL_INFO, ": %s", info ); + proto_item_append_text( item, ": %s", info ); + + if( !ok ) + { + proto_item_prepend_text( info_item, "? " ); + } + + *p_offset += struct_len; + return ok; +} + +/* Dissect SELECTOR field +*/ +static guint8 dissect_selector( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset, guint8* p_ok ) +{ + gint offset = *p_offset; + gint remaining_len = tvb_captured_length_remaining( tvb, offset ); + guint8 struct_len = (remaining_len <= 0) ? 0 : tvb_get_guint8( tvb, offset ); + gint eff_struct_len = (struct_len <= remaining_len) ? struct_len : remaining_len; + guint8 ok = 0; + + proto_item* info_item = proto_tree_add_none_format( tree, hf_folder, tvb, offset, eff_struct_len, "Selector" ); + + gchar info[ 40 ]; + g_snprintf( info, sizeof info, "???" ); + + if( struct_len <= 0 ) + { + proto_item_prepend_text( info_item, "Missing " ); + expert_add_info_format( pinfo, info_item, KIP_ERROR, "Expected: min 2 bytes" ); + //ok = 0; + } + else + { + proto_tree* info_tree = proto_item_add_subtree( info_item, ett_loss ); + proto_item* length_item = knxip_tree_add_length( info_tree, tvb, offset, struct_len ); + guint8 length_ok = 1; + + gint end_pos = offset + eff_struct_len; + offset++; + + if( struct_len > remaining_len ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Available: %u bytes", remaining_len ); + //ok = 0; + length_ok = 0; + struct_len = (guint8) remaining_len; + } + + if( struct_len < 2 ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Expected: min 2 bytes" ); + //ok = 0; + length_ok = 0; + } + else + { + /* 1 byte Selection Type */ + guint8 sel = tvb_get_guint8( tvb, offset ); + proto_item* type_item = proto_tree_add_item( info_tree, hf_knxip_selector, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_item_append_text( type_item, " = %s", (sel == SELECT_PROGMODE) ? "ProgMode" : (sel == SELECT_MACADDRESS) ? "MAC" : "Unknown" ); + offset++; + ok = 1; + + if( sel == SELECT_PROGMODE ) + { + g_snprintf( info, sizeof info, "ProgMode" ); + + if( struct_len != 2 ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Expected: 2 bytes" ); + ok = 0; + length_ok = 0; + } + } + else if( sel == SELECT_MACADDRESS ) + { + gchar* output = info; + gint output_max = sizeof info; + g_snprintf( output, output_max, "MAC=" ); + while( *output ) { ++output; --output_max; } + g_snprintf( output, output_max, "???" ); + + if( struct_len != 8 ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Expected: 8 bytes" ); + ok = 0; + length_ok = 0; + } + + if( struct_len >= 8 ) + { + /* 6 bytes MAC Address */ + guint8 mac[ 6 ]; + tvb_memcpy( tvb, mac, offset, 6 ); + g_snprintf( output, output_max, "%02x:%02x:%02x:%02x:%02x:%02x", mac[ 0 ], mac[ 1 ], mac[ 2 ], mac[ 3 ], mac[ 4 ], mac[ 5 ] ); + proto_tree_add_item( info_tree, hf_knxip_mac_address, tvb, offset, 6, ENC_NA ); + offset += 6; + } + } + else + { + proto_item_prepend_text( type_item, "? " ); + expert_add_info_format( pinfo, type_item, KIP_ERROR, "Unknown" ); + ok = 0; + } + + if( offset < end_pos ) + { + knxip_tree_add_unknown_data( info_tree, tvb, offset, end_pos - offset ); + ok = 0; + } + + proto_item_append_text( info_item, ": %s", info ); + } + + if( !length_ok ) + { + proto_item_prepend_text( length_item, "? " ); + } + } + + if( pinfo ) col_append_fstr( pinfo->cinfo, COL_INFO, " %s", info ); + proto_item_append_text( item, ", %s", info ); + + if( !ok ) + { + proto_item_prepend_text( info_item, "? " ); + if( p_ok ) *p_ok = 0; + } + + *p_offset += struct_len; + if( p_ok && !ok ) *p_ok = 0; + return struct_len; +} + +/* Dissect DevInfo DIB +*/ +static guint8 dissect_dib_devinfo( tvbuff_t* tvb, packet_info* pinfo, + proto_item* dib_item, proto_tree* dib_tree, proto_item* length_item, guint8 length_ok, + gint* p_offset, guint8 struct_len, gchar* output, gint output_max ) +{ + gint offset = *p_offset; + gchar* info = output; + guint8 prog_mode = 0; + guint8 ok = 1; + + if( struct_len != 54 ) + { + if( length_ok ) knxip_item_illegal_length( length_item, pinfo, "Expected: 54 bytes" ); + ok = 0; + } + + if( struct_len >= 3 ) + { + /* 1 byte KNX Medium */ + guint8 knx_medium = tvb_get_guint8( tvb, offset ); + proto_item* item = proto_tree_add_item( dib_tree, hf_knxip_knx_medium, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree* tree = proto_item_add_subtree( item, ett_medium ); + knxip_tree_add_bit( tree, tvb, offset, 5, "IP", NULL, 0 ); + knxip_tree_add_bit( tree, tvb, offset, 4, "RF", NULL, 0 ); + knxip_tree_add_bit( tree, tvb, offset, 3, "PL132", NULL, 0 ); + knxip_tree_add_bit( tree, tvb, offset, 2, "PL110", NULL, 0 ); + knxip_tree_add_bit( tree, tvb, offset, 1, "TP1", NULL, 0 ); + knxip_tree_add_bit( tree, tvb, offset, 0, "TP0", NULL, 0 ); + + /* Check for missing or multiple medium */ + { + guint8 data = knx_medium; + guint8 media = 0; + while( data ) + { + if( data & 1 ) + { + media++; + } + data >>= 1;; + } + + if( media != 1 ) + { + expert_add_info_format( pinfo, item, KIP_WARNING, media ? "Multiple" : "Missing" ); + } + } + + offset++; + + if( struct_len >= 4 ) + { + /* 1 byte Device Status */ + guint8 status = tvb_get_guint8( tvb, offset ); + item = proto_tree_add_item( dib_tree, hf_knxip_device_status, tvb, offset, 1, ENC_BIG_ENDIAN ); + tree = proto_item_add_subtree( item, ett_status ); + proto_tree_add_item( tree, hf_knxip_program_mode, tvb, offset, 1, ENC_BIG_ENDIAN ); + + if( status & 0x01 ) + { + proto_item_append_text( item, " (ProgMode)" ); + prog_mode = 1; + } + + offset++; + + if( struct_len >= 6 ) + { + /* 2 bytes KNX Address */ + item = knxip_tree_add_knx_address( dib_tree, hf_knxip_knx_address, tvb, offset, output, output_max ); + + if( output ) + { + while( *output ) { output++; output_max--; } + } + + offset += 2; + + if( struct_len >= 8 ) + { + /* 2 bytes Project Installation Identifier */ + guint16 project_id = tvb_get_ntohs( tvb, offset ); + item = proto_tree_add_item( dib_tree, hf_knxip_project_id, tvb, offset, 2, ENC_BIG_ENDIAN ); + tree = proto_item_add_subtree( item, ett_projectid ); + proto_tree_add_item( tree, hf_knxip_project_number, tvb, offset, 2, ENC_BIG_ENDIAN ); + proto_tree_add_item( tree, hf_knxip_installation_number, tvb, offset, 2, ENC_BIG_ENDIAN ); + proto_item_append_text( item, " (%u:%u)", project_id / 16, project_id % 16 ); + + offset += 2; + + if( struct_len >= 14 ) + { + /* 6 bytes KNX Serial Number */ + proto_tree_add_item( dib_tree, hf_knxip_serial_number, tvb, offset, 6, ENC_BIG_ENDIAN ); + offset += 6; + } + + if( struct_len >= 18 ) + { + /* 4 bytes Routing Multicast Address */ + proto_tree_add_item( dib_tree, hf_knxip_multicast_address, tvb, offset, 4, ENC_BIG_ENDIAN ); + offset += 4; + + if( struct_len >= 24 ) + { + /* 6 bytes MAC Address */ + proto_tree_add_item( dib_tree, hf_knxip_mac_address, tvb, offset, 6, ENC_NA ); + offset += 6; + + if( struct_len >= 54 ) + { + /* 30 bytes Friendly Name */ + proto_tree_add_item( dib_tree, hf_knxip_friendly_name, tvb, offset, 30, ENC_ASCII | ENC_NA ); + + if( output ) + { + if( output_max > 33 ) + { + *output++ = ' '; + *output++ = '"'; + output_max -= 2; + tvb_get_nstringz( tvb, offset, 30, (guint8 *) output ); + output[ 30 ] = '\0'; + while( *output ) { output++; output_max--; } + *output++ = '"'; + output_max--; + } + } + + offset += 30; + } + } + } + } + } + } + } + + if( output ) + { + *output = '\0'; + + if( output == info ) + { + g_snprintf( output, output_max, "???" ); + while( *output ) { output++; output_max--; } + } + + if( prog_mode ) + { + g_snprintf( output, output_max, " PROGMODE" ); + } + + proto_item_append_text( dib_item, ": %s", info ); + } + + *p_offset = offset; + return ok; +} + +/* Dissect SuppSvc DIB +*/ +static guint8 dissect_dib_suppsvc( tvbuff_t* tvb, packet_info* pinfo, + proto_item* dib_item, proto_tree* dib_tree, proto_item* length_item, guint8 length_ok, + gint* p_offset, guint8 struct_len ) +{ + gint offset = *p_offset; + gint end_pos = offset - 2 + struct_len; + guint8 ok = 1; + gchar separator = ':'; + guint8 sf_count[ 8 ] = { 0 }; + + if( struct_len & 1 ) + { + if( length_ok ) knxip_item_illegal_length( length_item, pinfo, "Expected: even number" ); + ok = 0; + } + + while( offset + 2 <= end_pos ) + { + guint8 service_family = tvb_get_guint8( tvb, offset ); + guint8 version = tvb_get_guint8( tvb, offset + 1 ); + const gchar* service_family_name = try_val_to_str( service_family, knxip_service_family_vals ); + proto_item* item = proto_tree_add_none_format( dib_tree, hf_folder, tvb, offset, 2, "KNXnet/IP %s v%u", + service_family_name ? service_family_name : "Unknown Service Family", version ); + proto_tree* tree = proto_item_add_subtree( item, ett_service_family ); + + /* 1 byte Service Family ID */ + proto_tree_add_item( tree, hf_knxip_service_family, tvb, offset, 1, ENC_BIG_ENDIAN ); + + /* 1 byte Service Family Version */ + proto_tree_add_item( tree, hf_knxip_service_version, tvb, offset + 1, 1, ENC_BIG_ENDIAN ); + + if( service_family >= KIP_SERVICE_TUNNELING && service_family_name ) + { + proto_item_append_text( dib_item, "%c %s", separator, service_family_name ); + separator = ','; + } + + if( service_family < 8 ) + { + ++sf_count[ service_family ]; + } + + offset += 2; + } + + if( !sf_count[ KIP_SERVICE_CORE ] ) + { + expert_add_info_format( pinfo, dib_item, KIP_WARNING, "Missing: Core (0x02)" ); + } + if( !sf_count[ KIP_SERVICE_MANAGEMENT ] ) + { + expert_add_info_format( pinfo, dib_item, KIP_WARNING, "Missing: Device Management (0x03)" ); + } + + *p_offset = offset; + return ok; +} + +/* Dissect IpConfig DIB +*/ +static guint8 dissect_dib_ipconfig( tvbuff_t* tvb, packet_info* pinfo, + proto_item* dib_item, proto_tree* dib_tree, proto_item* length_item, guint8 length_ok, + gint* p_offset, guint8 struct_len, gchar* output, gint output_max ) +{ + gint offset = *p_offset; + guint8 ok = 1; + gchar text[ 32 ]; + + if( struct_len != 16 ) + { + if( length_ok ) knxip_item_illegal_length( length_item, pinfo, "Expected: 16 bytes" ); + ok = 0; + } + + if( struct_len < 6 ) + { + g_snprintf( text, sizeof text, "???" ); + } + else + { + /* 4 bytes IP Address */ + knxip_tree_add_ip_address( dib_tree, tvb, offset, text, sizeof text ); + offset += 4; + + if( struct_len >= 10 ) + { + /* 4 bytes Subnet Mask */ + proto_tree_add_item( dib_tree, hf_knxip_ip_subnet, tvb, offset, 4, ENC_BIG_ENDIAN ); + offset += 4; + + if( struct_len >= 14 ) + { + /* 4 bytes Default Gateway */ + proto_tree_add_item( dib_tree, hf_knxip_ip_gateway, tvb, offset, 4, ENC_BIG_ENDIAN ); + offset += 4; + + if( struct_len >= 15 ) + { + /* 1 byte IP Capabilities */ + knxip_tree_add_ip_assignment( dib_tree, hf_knxip_ip_caps, tvb, offset, 0 ); + offset++; + + if( struct_len >= 16 ) + { + /* 1 byte IP Assignment Method */ + knxip_tree_add_ip_assignment( dib_tree, hf_knxip_ip_assign, tvb, offset, 1 ); + offset++; + } + } + } + } + } + + proto_item_append_text( dib_item, ": %s", text ); + if( output ) g_snprintf( output, output_max, "%s", text ); + + *p_offset = offset; + return ok; +} + +/* Dissect CurConfig DIB +*/ +static guint8 dissect_dib_curconfig( tvbuff_t* tvb, packet_info* pinfo, + proto_item* dib_item, proto_tree* dib_tree, proto_item* length_item, guint8 length_ok, + gint* p_offset, guint8 struct_len, gchar* output, gint output_max ) +{ + gint offset = *p_offset; + guint8 ok = 1; + gchar text[ 32 ]; + + if( struct_len != 20 ) + { + if( length_ok ) knxip_item_illegal_length( length_item, pinfo, "Expected: 20 bytes" ); + ok = 0; + } + + if( struct_len < 6 ) + { + g_snprintf( text, sizeof text, "???" ); + } + else + { + /* 4 bytes IP Address */ + knxip_tree_add_ip_address( dib_tree, tvb, offset, text, sizeof text ); + offset += 4; + + if( struct_len >= 10 ) + { + /* 4 bytes Subnet Mask */ + proto_tree_add_item( dib_tree, hf_knxip_ip_subnet, tvb, offset, 4, ENC_BIG_ENDIAN ); + offset += 4; + + if( struct_len >= 14 ) + { + /* 4 bytes Default Gateway */ + proto_tree_add_item( dib_tree, hf_knxip_ip_gateway, tvb, offset, 4, ENC_BIG_ENDIAN ); + offset += 4; + + if( struct_len >= 18 ) + { + /* 4 bytes DHCP Server */ + proto_tree_add_item( dib_tree, hf_knxip_ip_dhcp, tvb, offset, 4, ENC_BIG_ENDIAN ); + offset += 4; + + if( struct_len >= 19 ) + { + /* IP Assignment Method */ + knxip_tree_add_ip_assignment( dib_tree, hf_knxip_ip_assign, tvb, offset, 1 ); + offset++; + + if( struct_len >= 20 ) + { + /* Reserved Byte */ + knxip_tree_add_reserved( dib_tree, tvb, offset, pinfo, &ok ); + offset++; + } + } + } + } + } + } + + proto_item_append_text( dib_item, ": %s", text ); + if( output ) g_snprintf( output, output_max, "%s", text ); + + *p_offset = offset; + return ok; +} + +/* Dissect KnxAddr DIB +*/ +static guint8 dissect_dib_knxaddr( tvbuff_t* tvb, packet_info* pinfo, + proto_item* dib_item, proto_tree* dib_tree, proto_item* length_item, guint8 length_ok, + gint* p_offset, guint8 struct_len, gchar* output, gint output_max ) +{ + gint offset = *p_offset; + guint8 ok = 1; + gchar text1[ 32 ]; + gchar text2[ 32 ]; + + if( struct_len < 4 ) + { + if( length_ok ) knxip_item_illegal_length( length_item, pinfo, "Expected: >= 4 bytes" ); + g_snprintf( text1, sizeof text1, "???" ); + ok = 0; + } + else + { + gint end_pos = offset - 2 + struct_len; + + if( struct_len & 1 ) + { + if( length_ok ) knxip_item_illegal_length( length_item, pinfo, "Expected: even number" ); + ok = 0; + } + + /* 2 bytes KNX Address */ + knxip_tree_add_knx_address( dib_tree, hf_knxip_knx_address, tvb, offset, text1, sizeof text1 ); + proto_item_append_text( dib_item, ": %s", text1 ); + offset += 2; + + while( offset + 2 <= end_pos ) + { + /* 2 bytes Additional KNX Address */ + knxip_tree_add_knx_address( dib_tree, hf_knxip_knx_address, tvb, offset, text2, sizeof text2 ); + proto_item_append_text( dib_item, ", %s", text2 ); + offset += 2; + } + } + + if( output ) g_snprintf( output, output_max, "%s", text1 ); + + *p_offset = offset; + return ok; +} + +/* Dissect SecuredServices DIB +*/ +static guint8 dissect_dib_secured_service_families( tvbuff_t* tvb, packet_info* pinfo, + proto_item* dib_item, proto_tree* dib_tree, proto_item* length_item, guint8 length_ok, + gint* p_offset, guint8 struct_len ) +{ + gint offset = *p_offset; + gint end_pos = offset - 2 + struct_len; + guint8 ok = 1; + gchar separator = ':'; + + if( struct_len & 1 ) + { + if( length_ok ) knxip_item_illegal_length( length_item, pinfo, "Expected: even number" ); + ok = 0; + } + + while( offset + 2 <= end_pos ) + { + guint8 service_family = tvb_get_guint8( tvb, offset ); + guint8 version = tvb_get_guint8( tvb, offset + 1 ); + const gchar* service_family_name = try_val_to_str( service_family, knxip_service_family_vals ); + proto_item* item = proto_tree_add_none_format( dib_tree, hf_folder, tvb, offset, 2, "KNXnet/IP %s v%u", + service_family_name ? service_family_name : "Unknown Service Family", version ); + proto_tree* tree = proto_item_add_subtree( item, ett_service_family ); + + /* 1 byte Service Family ID */ + proto_tree_add_item( tree, hf_knxip_service_family, tvb, offset, 1, ENC_BIG_ENDIAN ); + + /* 1 byte Security Version */ + proto_tree_add_item( tree, hf_knxip_security_version, tvb, offset + 1, 1, ENC_BIG_ENDIAN ); + + if( service_family_name ) + { + proto_item_append_text( dib_item, "%c %s", separator, service_family_name ); + separator = ','; + } + + offset += 2; + } + + *p_offset = offset; + return ok; +} + +/* Dissect TunnelingInfo DIB +*/ +static guint8 dissect_dib_tunneling_info( tvbuff_t* tvb, packet_info* pinfo, + proto_item* dib_item, proto_tree* dib_tree, proto_item* length_item, guint8 length_ok, + gint* p_offset, guint8 struct_len ) +{ + gint offset = *p_offset; + guint8 ok = 1; + + if( struct_len < 4 ) + { + if( length_ok ) + { + knxip_item_illegal_length( length_item, pinfo, "Expected: >= 4 bytes" ); + ok = 0; + } + } + else + { + gint end_pos = offset - 2 + struct_len; + gchar separator = ':'; + + /* 2 bytes Max APDU Length */ + proto_tree_add_item( dib_tree, hf_knxip_max_apdu_length, tvb, offset, 2, ENC_BIG_ENDIAN ); + offset += 2; + + if( struct_len & 3 ) + { + if( length_ok ) + { + knxip_item_illegal_length( length_item, pinfo, "Expected: 4 + n * 4 bytes" ); + ok = 0; + } + } + + while( offset + 4 <= end_pos ) + { + guint8 flags = tvb_get_guint8( tvb, offset + 3 ); + guint8 is_free = flags & 1; + gchar text[ 32 ]; + proto_item* node; + proto_tree* list; + + node = proto_tree_add_none_format( dib_tree, hf_folder, tvb, offset, 4, "Tunneling Slot" ); + list = proto_item_add_subtree( node, ett_tunnel ); + + /* 2 bytes KNX Address, 1 byte reserved */ + knxip_tree_add_knx_address( list, hf_knxip_knx_address, tvb, offset, text, sizeof text ); + proto_item_append_text( node, ": %s Free=%u", text, is_free ); + offset += 3; + + /* 1 byte flags */ + knxip_tree_add_bit( list, tvb, offset, 2, "Usable", NULL, 0 ); + knxip_tree_add_bit( list, tvb, offset, 1, "Authorized", NULL, 0 ); + knxip_tree_add_bit( list, tvb, offset, 0, "Free", NULL, 0 ); + offset++; + + if( !is_free ) + { + proto_item_append_text( dib_item, "%c %s", separator, text ); + separator = ','; + } + } + } + + *p_offset = offset; + return ok; +} + +/* Dissect ExtDevInfo DIB +*/ +static guint8 dissect_dib_extdevinfo( tvbuff_t* tvb, packet_info* pinfo, + proto_item* dib_item, proto_tree* dib_tree, proto_item* length_item, guint8 length_ok, + gint* p_offset, guint8 struct_len, gchar* output, gint output_max ) +{ + gint offset = *p_offset; + guint8 status = 0; + guint8 ok = 1; + + if( struct_len != 8 ) + { + if( length_ok ) knxip_item_illegal_length( length_item, pinfo, "Expected: 8 bytes" ); + ok = 0; + } + + if( struct_len >= 3 ) + { + /* 1 byte Medium Status */ + status = tvb_get_guint8( tvb, offset ); + proto_tree_add_item( dib_tree, hf_knxip_medium_status, tvb, offset, 1, ENC_BIG_ENDIAN ); + if( status ) + { + proto_item_append_text( dib_item, ": MediumStatus=$%02X", status ); + if( output ) g_snprintf( output, output_max, "MediumStatus=$%02X", status ); + } + + offset++; + + if( struct_len >= 4 ) + { + /* 1 byte reserved */ + knxip_tree_add_reserved( dib_tree, tvb, offset, pinfo, &ok ); + offset++; + + if( struct_len >= 6 ) + { + /* 2 bytes Max APDU Length */ + proto_tree_add_item( dib_tree, hf_knxip_max_apdu_length, tvb, offset, 2, ENC_BIG_ENDIAN ); + offset += 2; + + if( struct_len >= 8 ) + { + /* 2 bytes Mask Version */ + proto_tree_add_item( dib_tree, hf_knxip_mask_version, tvb, offset, 2, ENC_BIG_ENDIAN ); + offset += 2; + } + } + } + } + + *p_offset = offset; + return ok; +} + +/* Dissect MfrData DIB +*/ +static guint8 dissect_dib_mfrdata( tvbuff_t* tvb, packet_info* pinfo, + proto_item* dib_item, proto_tree* dib_tree, proto_item* length_item, guint8 length_ok, + gint* p_offset, guint8 struct_len, gchar* output, gint output_max ) +{ + gint offset = *p_offset; + guint8 ok = 1; + gchar text[ 32 ]; + + if( struct_len < 4 ) + { + if( length_ok ) knxip_item_illegal_length( length_item, pinfo, "Expected: >= 4 bytes" ); + g_snprintf( text, sizeof text, "???" ); + ok = 0; + } + else + { + proto_tree_add_item( dib_tree, hf_knxip_manufacturer_code, tvb, offset, 2, ENC_BIG_ENDIAN ); + g_snprintf( text, sizeof text, "0x%04x", tvb_get_ntohs( tvb, offset ) ); + offset += 2; + } + + proto_item_append_text( dib_item, ": %s", text ); + if( output ) g_snprintf( output, output_max, "%s", text ); + + *p_offset = offset; + return ok; +} + +/* Dissect DIB +*/ +static guint8 dissect_dib( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, + gint* p_offset, gchar** p_output, gint* p_output_max, gchar separator, guint8* p_count, guint8* p_ok ) +{ + gint offset = *p_offset; + gint remaining_len = tvb_captured_length_remaining( tvb, offset ); + guint8 struct_len = (remaining_len <= 0) ? 0 : tvb_get_guint8( tvb, offset ); + if( struct_len > 0 ) + { + gint eff_struct_len = (struct_len <= remaining_len) ? struct_len : remaining_len; + gint end_pos = offset + eff_struct_len; + const gchar* dib_name = NULL; + guint8 dib_type = 0; + guint8 ok = 1; + guint8 length_ok = 1; + + proto_item* dib_item = proto_tree_add_none_format( tree, hf_folder, tvb, offset, eff_struct_len, "DIB" ); + proto_tree* dib_tree = proto_item_add_subtree( dib_item, ett_dib ); + proto_item* length_item = knxip_tree_add_length( dib_tree, tvb, offset, struct_len ); + + offset++; + + if( struct_len > remaining_len ) + { + proto_item_prepend_text( length_item, "? " ); + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Available: %u bytes", remaining_len ); + struct_len = (guint8) remaining_len; + ok = 0; + length_ok = 0; + } + + if( eff_struct_len < 2 ) + { + expert_add_info_format( pinfo, dib_item, KIP_ERROR, "Missing 1 byte Description Type" ); + ok = 0; + } + else + { + proto_item* type_item = proto_tree_add_item( dib_tree, hf_knxip_description_type, tvb, offset, 1, ENC_BIG_ENDIAN ); + gchar info[ 80 ]; + + dib_type = tvb_get_guint8( tvb, offset ); + dib_name = try_val_to_str( dib_type, descr_type_vals ); + offset++; + + if( !dib_name ) + { + proto_item_append_text( dib_item, " ???" ); + proto_item_append_text( type_item, " (Unknown)" ); + } + else + { + proto_item_append_text( dib_item, " %s", dib_name ); + } + + if( p_count ) + { + ++p_count[ dib_type ]; + } + + switch( dib_type ) + { + case KIP_DIB_DEVICE_INFO: + ok &= dissect_dib_devinfo( tvb, pinfo, dib_item, dib_tree, length_item, length_ok, &offset, struct_len, info, sizeof info ); + if( p_output ) + { + gchar* output = *p_output; + int output_max = *p_output_max; + g_snprintf( output, output_max, "%s", info ); + while( *output ) { ++output; --output_max; } + *p_output = output; + *p_output_max = output_max; + } + break; + + case KIP_DIB_SUPP_SVC_FAMILIES: + ok &= dissect_dib_suppsvc( tvb, pinfo, dib_item, dib_tree, length_item, length_ok, &offset, struct_len ); + break; + + case KIP_DIB_IP_CONFIG: + ok &= dissect_dib_ipconfig( tvb, pinfo, dib_item, dib_tree, length_item, length_ok, &offset, struct_len, info, sizeof info ); + break; + + case KIP_DIB_CUR_CONFIG: + ok &= dissect_dib_curconfig( tvb, pinfo, dib_item, dib_tree, length_item, length_ok, &offset, struct_len, info, sizeof info ); + break; + + case KIP_DIB_KNX_ADDRESSES: + ok &= dissect_dib_knxaddr( tvb, pinfo, dib_item, dib_tree, length_item, length_ok, &offset, struct_len, info, sizeof info ); + break; + + case KIP_DIB_SECURED_SERVICE_FAMILIES: + ok &= dissect_dib_secured_service_families( tvb, pinfo, dib_item, dib_tree, length_item, length_ok, &offset, struct_len ); + break; + + case KIP_DIB_TUNNELING_INFO: + ok &= dissect_dib_tunneling_info( tvb, pinfo, dib_item, dib_tree, length_item, length_ok, &offset, struct_len ); + break; + + case KIP_DIB_EXTENDED_DEVICE_INFO: + ok &= dissect_dib_extdevinfo( tvb, pinfo, dib_item, dib_tree, length_item, length_ok, &offset, struct_len, info, sizeof info ); + break; + + case KIP_DIB_MFR_DATA: + ok &= dissect_dib_mfrdata( tvb, pinfo, dib_item, dib_tree, length_item, length_ok, &offset, struct_len, info, sizeof info ); + break; + + default: + expert_add_info_format( pinfo, type_item, KIP_WARNING, "Unknown DIB Type" ); + break; + } + + if( offset < end_pos ) + { + knxip_tree_add_unknown_data( dib_tree, tvb, offset, end_pos - offset ); + offset = end_pos; + } + } + + if( !p_output ) + { + if( pinfo ) + { + column_info* cinfo = pinfo->cinfo; + col_append_fstr( cinfo, COL_INFO, "%c ", separator ); + + if( !dib_name ) + { + col_append_str( cinfo, COL_INFO, "???" ); + } + else + { + if( !ok ) col_append_str( cinfo, COL_INFO, "? " ); + col_append_str( cinfo, COL_INFO, dib_name ); + } + } + + if( item ) + { + proto_item_append_text( item, "%c ", separator ); + + if( !dib_name ) + { + proto_item_append_text( item, "???" ); + } + else + { + if( !ok ) proto_item_append_text( item, "? " ); + proto_item_append_text( item, "%s", dib_name ); + } + } + } + + if( !ok ) + { + proto_item_prepend_text( dib_item, "? " ); + if( p_ok ) *p_ok = 0; + } + + *p_offset = offset; + } + + return struct_len; +} + +/* Dissect sequence of DIBs +*/ +static gchar dissect_dibs( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset, gchar** p_output, gint* p_output_max, gchar separator, guint8* p_count, guint8* p_ok ) +{ + while( dissect_dib( tvb, pinfo, item, tree, p_offset, p_output, p_output_max, separator, p_count, p_ok ) ) + { + separator = ','; + } + + return separator; +} + +/* Dissect SRP +*/ +static guint8 dissect_srp( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset, guint8* p_ok ) +{ + gint offset = *p_offset; + gint remaining_len = tvb_captured_length_remaining( tvb, offset ); + guint8 struct_len = (remaining_len <= 0) ? 0 : tvb_get_guint8( tvb, offset ); + if( struct_len > 0 ) + { + gint eff_struct_len = (struct_len <= remaining_len) ? struct_len : remaining_len; + gint end_pos = offset + eff_struct_len; + column_info* cinfo = pinfo ? pinfo->cinfo : NULL; + proto_item* srp_item = proto_tree_add_none_format( tree, hf_folder, tvb, offset, eff_struct_len, "SRP" ); + proto_tree* srp_tree = proto_item_add_subtree( srp_item, ett_dib ); + proto_item* length_item = knxip_tree_add_length( srp_tree, tvb, offset, struct_len ); + guint8 ok = 1; + guint8 length_ok = 1; + + offset++; + + if( struct_len > remaining_len ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Available: %u bytes", remaining_len ); + ok = 0; + length_ok = 0; + } + + if( eff_struct_len < 2 ) + { + expert_add_info_format( pinfo, srp_item, KIP_ERROR, "Missing 1 byte SRP Type" ); + ok = 0; + } + else + { + /* 1 bit Mandatory */ + proto_tree_add_item( srp_tree, hf_knxip_srp_mandatory, tvb, offset, 1, ENC_BIG_ENDIAN ); + + /* 7 bits SRP Type */ + guint8 srp_type = tvb_get_guint8( tvb, offset ) & 0x7F; + const gchar* srp_name = try_val_to_str( srp_type, srp_type_vals ); + proto_item* type_item = proto_tree_add_item( srp_tree, hf_knxip_srp_type, tvb, offset, 1, ENC_BIG_ENDIAN ); + guint8 expected_len = 0; + guint8 unknown = !srp_name; + if( unknown ) + { + expert_add_info_format( pinfo, type_item, KIP_WARNING, "Unknown SRP Type" ); + srp_name = "???"; + } + + proto_item_append_text( srp_item, " %s", srp_name ? srp_name : "???" ); + proto_item_append_text( type_item, " = %s", srp_name ? srp_name : "???" ); + + if( !unknown ) + { + col_append_fstr( cinfo, COL_INFO, " %s", srp_name ); + proto_item_append_text( item, ", %s", srp_name ); + } + + switch( srp_type ) + { + case 1: + expected_len = 2; + break; + case 2: + expected_len = 8; + break; + case 3: + expected_len = 4; + break; + } + + if( expected_len ) + { + if( struct_len != expected_len ) + { + expert_add_info_format( pinfo, length_item, KIP_ERROR, "Expected: %u bytes", expected_len ); + ok = 0; + length_ok = 0; + } + } + offset++; + + if( offset < end_pos ) + { + knxip_tree_add_data( srp_tree, tvb, offset, end_pos - offset, srp_name ? cinfo : NULL, item, "Data", "=$", " = $" ); + + proto_item_append_text( srp_item, ": $" ); + while( offset < end_pos ) + { + proto_item_append_text( srp_item, " %02X", tvb_get_guint8( tvb, offset ) ); + ++offset; + } + + //offset = end_pos; + } + } + + if( !ok ) + { + proto_item_prepend_text( srp_item, "? " ); + if( p_ok ) *p_ok = 0; + } + + if( !length_ok ) + { + proto_item_prepend_text( length_item, "? " ); + } + + *p_offset += struct_len; + } + + return struct_len; +} + +/* Dissect sequence of SRPs +*/ +static void dissect_srps( tvbuff_t *tvb, packet_info *pinfo, proto_item *item, proto_tree *tree, gint *p_offset, guint8* p_ok ) +{ + while( dissect_srp( tvb, pinfo, item, tree, p_offset, p_ok ) ); +} + +/* Dissect RESET command +*/ +static guint8 dissect_resetter( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset ) +{ + guint8 ok = 0; + gint offset = *p_offset; + gint remaining_len = tvb_captured_length_remaining( tvb, offset ); + guint8 struct_len = ((guint) remaining_len < 2) ? (guint8) remaining_len : 2; + guint8 mode = (struct_len <= 0) ? 0 : tvb_get_guint8( tvb, offset ); + const gchar* mode_name = (mode == 0x01) ? "Restart" : (mode == 0x02) ? "Master Reset" : NULL; + const gchar* mode_info = mode_name ? mode_name : "???"; + proto_item* node; + + if( struct_len <= 0 ) + { + node = proto_tree_add_debug_text( tree, "? Command, Reserved" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 2 bytes" ); + } + else + { + /* 1 byte Reset Command */ + node = proto_tree_add_item( tree, hf_knxip_reset_command, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_item_append_text( node, " = %s", mode_info ); + + if( !mode_name ) + { + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 0x01 or 0x02" ); + } + else + { + ok = 1; + } + + if( struct_len != 2 ) + { + node = proto_tree_add_debug_text( tree, "? Reserved" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + ok = 0; + } + else + { + /* 1 byte Reserved */ + knxip_tree_add_reserved( tree, tvb, offset + 1, pinfo, &ok ); + } + } + + if( pinfo ) col_append_fstr( pinfo->cinfo, COL_INFO, ", %s", mode_info ); + proto_item_append_text( item, ", %s", mode_info ); + + *p_offset += struct_len; + return ok; +} + +/* Decrypt SECURE_WRAPPER. Returns decrypted part if MAC matches +*/ +static guint8* decrypt_secure_wrapper( const guint8* key, const guint8* data, gint h_length, gint p_length ) +{ + guint8 header_length = *data; + gint a_length = header_length + 2; + if( a_length > h_length ) + { + a_length = h_length; + } + + if( h_length >= header_length + 16 && p_length >= 16 ) + { + const guint8* nonce = data + a_length; + guint8* decrypted = knxip_ccm_decrypt( NULL, key, data + h_length, p_length, nonce, 14 ); + + if( decrypted ) + { + /* Calculate MAC */ + guint8 mac[ 16 ]; + p_length -= 16; + + knxip_ccm_calc_cbc_mac( mac, key, data, a_length, decrypted, p_length, nonce, 14 ); + + /* Check MAC */ + if( memcmp( decrypted + p_length, mac, 16 ) != 0 ) + { + wmem_free( wmem_packet_scope(), decrypted ); + decrypted = NULL; + } + } + + return decrypted; + } + + return NULL; +} + +static void make_key_info( gchar* text, gint text_max, const guint8* key, const gchar* context ) +{ + guint8 count; + + if( !key ) + { + g_snprintf( text, text_max, "without key" ); + } + else + { + if( context ) + { + g_snprintf( text, text_max, "with %s key", context ); + } + else + { + g_snprintf( text, text_max, "with key" ); + } + + for( count = 16; count; --count ) + { + while( *text ) { ++text; --text_max; } + g_snprintf( text, text_max, " %02X", *key++ ); + } + } +} + +/* Dissect SECURE_WRAPPER +*/ +static guint8 dissect_secure_wrapper( guint8 header_length, tvbuff_t* tvb, packet_info* pinfo, proto_tree* root, proto_item* item, proto_tree* tree, gint* p_offset ) +{ + guint8 ok = 1; + gint offset = *p_offset; + gint size = tvb_captured_length_remaining( tvb, offset ); + column_info* cinfo = pinfo->cinfo; + const guint8* dest_addr = (pinfo->dst.type == AT_IPv4) ? (const guint8*) pinfo->dst.data : NULL; + proto_item* node; + + /* 2 bytes Session ID */ + if( size < 2 ) + { + node = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, size, NULL, "? Session" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 2 bytes" ); + ok = 0; + } + else + { + guint16 session = tvb_get_ntohs( tvb, offset ); + node = proto_tree_add_item( tree, hf_knxip_session, tvb, offset, 2, ENC_BIG_ENDIAN ); + + if( session ) + { + col_append_fstr( cinfo, COL_INFO, " #%04X", session ); + proto_item_append_text( item, ", Session: $%04X", session ); + } + + offset += 2; + size -= 2; + + /* 6 bytes Sequence Nr */ + if( size < 6 ) + { + node = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, size, NULL, "? Sequence Number" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 6 bytes" ); + ok = 0; + } + else + { + node = knxip_tree_add_data( tree, tvb, offset, 6, cinfo, item, "Sequence Number", " $", ", Seq Nr: $" ); + offset += 6; + size -= 6; + + /* 6 bytes Serial Nr */ + if( size < 6 ) + { + node = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, size, NULL, "? Serial Number" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 6 bytes" ); + ok = 0; + } + else + { + node = knxip_tree_add_data( tree, tvb, offset, 6, cinfo, item, "Serial Number", ".", ", Ser Nr: $" ); + offset += 6; + size -= 6; + + /* 2 bytes Tag */ + if( size < 2 ) + { + node = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, size, NULL, "? Tag" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 2 bytes" ); + ok = 0; + } + else + { + guint16 tag = tvb_get_ntohs( tvb, offset ); + node = proto_tree_add_item( tree, hf_knxip_tag, tvb, offset, 2, ENC_BIG_ENDIAN ); + col_append_fstr( cinfo, COL_INFO, ".%04X", tag ); + proto_item_append_text( item, ", Tag: $%04X", tag ); + offset += 2; + size -= 2; + + /* Encrypted part */ + if( size < 16 ) + { + node = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, size, NULL, "? Encrypted" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: min 16 bytes" ); + ok = 0; + } + else + { + const guint8* encrypted = tvb_get_ptr( tvb, offset, size - offset ); + const gint a_length = header_length + 16; // length of leading non-encrypted data + const guint8* a_data = encrypted - a_length; // ptr to KIP header + guint8* decrypted = NULL; + const guint8* key = NULL; + gchar decrypt_info[ 128 ]; + struct knx_keyring_mca_keys* mca_key; + guint8 key_index; + + node = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, size, encrypted, "Encrypted (%d bytes)", size ); + + *decrypt_info = '\0'; + + if( dest_addr ) + { + // Try keys associated with IP MCA in keyring.XML + for( mca_key = knx_keyring_mca_keys; mca_key; mca_key = mca_key->next ) + { + if( memcmp( mca_key->mca, dest_addr, 4 ) == 0 ) + { + key = mca_key->key; + decrypted = decrypt_secure_wrapper( key, a_data, a_length, size ); + if( decrypted ) + { + make_key_info( decrypt_info, sizeof decrypt_info, key, "MCA" ); + break; + } + } + } + } + + if( !decrypted ) + { + // Try explicitly specified keys + for( key_index = 0; key_index < knx_decryption_key_count; ++key_index ) + { + key = knx_decryption_keys[ key_index ]; + decrypted = decrypt_secure_wrapper( key, a_data, a_length, size ); + if( decrypted ) + { + make_key_info( decrypt_info, sizeof decrypt_info, key, NULL ); + break; + } + } + } + + if( !decrypted ) + { + const gchar* text = knx_decryption_key_count ? " (decryption failed)" : knx_keyring_mca_keys ? " (no key found)" : " (no key available)"; + proto_item_append_text( node, "%s", text ); + } + else + { + tvbuff_t* tvb2 = tvb_new_child_real_data( tvb, decrypted, size, size ); + gint size2 = size - 16; + proto_item_append_text( item, ", MAC OK" ); + //tvb_set_free_cb( tvb2, wmem_free ); + add_new_data_source( pinfo, tvb2, "Decrypted" ); + + item = proto_tree_add_none_format( root, hf_folder, tvb2, 0, size, "Decrypted" ); + tree = proto_item_add_subtree( item, ett_decrypted ); + + if( *decrypt_info ) + { + proto_item_append_text( item, " (%s)", decrypt_info ); + } + + /* Embedded KIP packet */ + node = knxip_tree_add_data( tree, tvb2, 0, size2, NULL, NULL, "Embedded KNXnet/IP packet", NULL, NULL ); + + /* MAC */ + node = knxip_tree_add_data( tree, tvb2, size2, 16, NULL, NULL, "Message Authentication Code", NULL, NULL ); + + /* Dissect embedded KIP packet */ + { + tvbuff_t* tvb3 = tvb_new_subset_length( tvb2, 0, size2 ); + dissect_knxip( 1, tvb3, pinfo, root ); + } + } + } + } + } + } + } + + *p_offset = offset + size; + return ok; +} + +/* Check encrypted MAC in TIMER_NOTIFY +*/ +static guint8 check_timer_sync_mac( const guint8* key, const guint8* data, gint header_length ) +{ + // Calculate and encrypt MAC + const guint8* nonce = data + header_length; + guint8 mac[ 16 ]; + knxip_ccm_calc_cbc_mac( mac, key, data, header_length, NULL, 0, nonce, 14 ); + knxip_ccm_encrypt( mac, key, NULL, 0, mac, nonce, 14 ); + + // Check MAC + return (memcmp( nonce + 14, mac, 16 ) == 0); +} + +/* Dissect TIMER_NOTIFY +*/ +static guint8 dissect_timer_notify( guint8 header_length, tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset ) +{ + guint8 ok = 1; + gint offset = *p_offset; + gint size = tvb_captured_length_remaining( tvb, offset ); + column_info* cinfo = pinfo->cinfo; + const guint8* dest_addr = (pinfo->dst.type == AT_IPv4) ? (const guint8*) pinfo->dst.data : NULL; + proto_item* node; + + /* 6 bytes Timestamp */ + if( size < 6 ) + { + node = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, size, NULL, "? Timestamp" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 6 bytes" ); + ok = 0; + } + else + { + node = knxip_tree_add_data( tree, tvb, offset, 6, cinfo, item, "Timestamp", " $", ", Timestamp: $" ); + offset += 6; + size -= 6; + + /* 6 bytes Serial Nr */ + if( size < 6 ) + { + node = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, size, NULL, "? Serial Number" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 6 bytes" ); + ok = 0; + } + else + { + node = knxip_tree_add_data( tree, tvb, offset, 6, cinfo, item, "Serial Number", ".", ", Ser Nr: $" ); + offset += 6; + size -= 6; + + /* 2 bytes Tag */ + if( size < 2 ) + { + node = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, size, NULL, "? Tag" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 2 bytes" ); + ok = 0; + } + else + { + guint16 tag = tvb_get_ntohs( tvb, offset ); + node = proto_tree_add_item( tree, hf_knxip_tag, tvb, offset, 2, ENC_BIG_ENDIAN ); + col_append_fstr( cinfo, COL_INFO, ".%04X", tag ); + proto_item_append_text( item, ", Tag: $%04X", tag ); + offset += 2; + size -= 2; + + /* 16 bytes MAC */ + if( size < 16 ) + { + node = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, size, NULL, "? Message Authentication Code" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 16 bytes" ); + ok = 0; + } + else + { + const gint a_length = header_length + 14; // length of leading non-encrypted data + const guint8* a_data = tvb_get_ptr( tvb, offset - a_length, a_length + 16 ); + const guint8* key = NULL; + guint8 mac_ok = 0; + guint8 mac_error = 0; + gchar mac_info[ 128 ]; + struct knx_keyring_mca_keys* mca_key; + guint8 key_index; + + node = knxip_tree_add_data( tree, tvb, offset, 16, NULL, NULL, "Message Authentication Code", NULL, NULL ); + + *mac_info = '\0'; + + if( dest_addr ) + { + // Try keys associated with IP MCA in keyring.XML + for( mca_key = knx_keyring_mca_keys; mca_key; mca_key = mca_key->next ) + { + if( memcmp( mca_key->mca, dest_addr, 4 ) == 0 ) + { + key = mca_key->key; + if( check_timer_sync_mac( key, a_data, header_length ) ) + { + mac_ok = 1; + make_key_info( mac_info, sizeof mac_info, key, "MCA" ); + break; + } + } + } + } + + if( !mac_ok ) + { + // Try explicitly specified keys + for( key_index = 0; key_index < knx_decryption_key_count; ++key_index ) + { + key = knx_decryption_keys[ key_index ]; + if( check_timer_sync_mac( key, a_data, header_length ) ) + { + mac_ok = 1; + make_key_info( mac_info, sizeof mac_info, key, NULL ); + break; + } + } + } + + if( mac_ok ) + { + node = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, size, NULL, "MAC OK" ); + col_append_str( cinfo, COL_INFO, " OK" ); + proto_item_append_text( item, ", MAC OK" ); + + if( *mac_info ) + { + proto_item_append_text( node, " (%s)", mac_info ); + } + + if( mac_error ) + { + expert_add_info_format( pinfo, node, KIP_WARNING, "OK with wrong key" ); + col_append_str( cinfo, COL_INFO, " (!)" ); + proto_item_append_text( item, " (!)" ); + } + } + + offset += 16; + size = 0; + } + } + } + } + + *p_offset = offset + size; + return ok; +} + +/* Dissect SESSION_REQUEST +*/ +static guint8 dissect_session_request( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset ) +{ + guint8 ok = 1; + gint offset = *p_offset; + + /* Control Endpoint HPAI */ + if( dissect_hpai( tvb, pinfo, item, tree, &offset, &ok, "Control", 1 ) ) + { + gint size = tvb_captured_length_remaining( tvb, offset ); + proto_item* node; + + /* DH Client Public Value */ + if( size <= 0 ) + { + node = proto_tree_add_debug_text( tree, "? DH Client Public Value" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Missing" ); + ok = 0; + } + else + { + node = knxip_tree_add_data( tree, tvb, offset, size, NULL, NULL, "DH Client Public Value", NULL, NULL ); + +#if ECDH_PUBLIC_VALUE_SIZE > 0 + if( size != ECDH_PUBLIC_VALUE_SIZE ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: %u bytes", ECDH_PUBLIC_VALUE_SIZE ); + ok = 0; + } +#endif + + offset += size; + } + } + + *p_offset = offset; + return ok; +} + +/* Dissect SESSION_RESPONSE +*/ +static guint8 dissect_session_response( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset ) +{ + guint8 ok = 1; + gint offset = *p_offset; + column_info* cinfo = pinfo->cinfo; + gint size = tvb_captured_length_remaining( tvb, offset ); + proto_item *node; + + /* 2 bytes Session ID */ + if( size < 2 ) + { + node = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, size, NULL, "? Session" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 2 bytes" ); + offset += size; + ok = 0; + } + else + { + guint16 session = tvb_get_ntohs( tvb, offset ); + col_append_fstr( cinfo, COL_INFO, " #%04X", session ); + proto_item_append_text( item, " #%04X", session ); + proto_tree_add_item( tree, hf_knxip_session, tvb, offset, 2, ENC_BIG_ENDIAN ); + offset += 2; + size -= 2; + + /* DH Server Public Value */ + { + gint size2 = size - 16; + if( size2 < 0 ) + { + size2 = 0; + } + + node = knxip_tree_add_data( tree, tvb, offset, size2, NULL, NULL, "DH Server Public Value", NULL, NULL ); + + if( size2 != ECDH_PUBLIC_VALUE_SIZE ) + { + proto_item_prepend_text( node, "? " ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: %u bytes", ECDH_PUBLIC_VALUE_SIZE ); + ok = 0; + } + + offset += size2; + size -= size2; + } + + /* 16 bytes MAC */ + if( size < 16 ) + { + node = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, size, NULL, "? Message Authentication Code" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 16 bytes" ); + offset += size; + ok = 0; + } + else + { + knxip_tree_add_data( tree, tvb, offset, 16, NULL, NULL, "Message Authentication Code", NULL, NULL ); + offset += 16; + } + } + + *p_offset = offset; + return ok; +} + +/* Dissect SESSION_AUTHENTICATE +*/ +static guint8 dissect_session_auth( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset ) +{ + guint8 ok = 1; + gint offset = *p_offset; + column_info* cinfo = pinfo->cinfo; + gint size = tvb_captured_length_remaining( tvb, offset ); + proto_item* node; + + /* 1 byte Reserved */ + if( size <= 0 ) + { + node = proto_tree_add_debug_text( tree, "? Reserved" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + ok = 0; + } + else + { + knxip_tree_add_reserved( tree, tvb, offset, pinfo, &ok ); + ++offset; + --size; + + /* 1 byte User ID */ + if( size <= 0 ) + { + node = proto_tree_add_debug_text( tree, "? User" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + ok = 0; + } + else + { + guint8 user_id = tvb_get_guint8( tvb, offset ); + col_append_fstr( cinfo, COL_INFO, " User=%u", user_id ); + proto_item_append_text( item, ", User = %u", user_id ); + proto_tree_add_item( tree, hf_knxip_user, tvb, offset, 1, ENC_BIG_ENDIAN ); + ++offset; + --size; + + /* 16 bytes MAC */ + if( size < 16 ) + { + node = proto_tree_add_bytes_format( tree, hf_bytes, tvb, offset, size, NULL, "? Message Authentication Code" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 16 bytes" ); + offset += size; + ok = 0; + } + else + { + knxip_tree_add_data( tree, tvb, offset, 16, NULL, NULL, "Message Authentication Code", NULL, NULL ); + offset += 16; + } + } + } + + *p_offset = offset; + return ok; +} + +/* Dissect SESSION_STATUS +*/ +static guint8 dissect_session_status( tvbuff_t* tvb, packet_info* pinfo, proto_item* item, proto_tree* tree, gint* p_offset ) +{ + guint8 ok = 1; + gint offset = *p_offset; + column_info* cinfo = pinfo->cinfo; + gint size = tvb_captured_length_remaining( tvb, offset ); + proto_item* node; + + /* 1 byte Status */ + if( size <= 0 ) + { + node = proto_tree_add_debug_text( tree, "? Status" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + ok = 0; + } + else + { + guint8 status = tvb_get_guint8( tvb, offset ); + col_append_fstr( cinfo, COL_INFO, " %u", status ); + proto_item_append_text( item, ": %u", status ); + proto_tree_add_item( tree, hf_knxip_session_status, tvb, offset, 1, ENC_BIG_ENDIAN ); + ++offset; + --size; + + /* 1 byte Reserved */ + if( size <= 0 ) + { + node = proto_tree_add_debug_text( tree, "? Reserved" ); + expert_add_info_format( pinfo, node, KIP_ERROR, "Expected: 1 byte" ); + ok = 0; + } + else + { + knxip_tree_add_reserved( tree, tvb, offset, pinfo, &ok ); + ++offset; + --size; + } + } + + *p_offset = offset; + return ok; +} + +/* Dissect KNX-IP data after KNX-IP header +*/ +static void dissect_knxip_data( guint8 header_length, guint8 protocol_version _U_, guint16 service, tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, proto_item* kip_item, proto_tree* kip_tree ) +{ + guint8 ok = 1; + guint8 service_family = (service >> 8); + const gchar* service_family_name = try_val_to_str( service_family, knxip_service_family_vals ); + const gchar* service_name = try_val_to_str( service, knxip_service_type_vals ); + const gchar* svc_name = try_val_to_str( service, svc_vals ); + gint offset = header_length; + gint remaining_len = tvb_captured_length_remaining( tvb, offset ); + column_info* cinfo = pinfo->cinfo; + gchar info[ 80 ]; + + /* Make sure that we cope with a well known service family + */ + if( service_family_name == NULL ) + { + col_add_str( cinfo, COL_INFO, "Unknown Service Family" ); + proto_item_append_text( kip_item, " Unknown Service Family" ); + ok = 0; + } + else + { + /* Make sure that we cope with a well known service type + */ + if( service_name == NULL ) + { + col_append_fstr( cinfo, COL_INFO, "%s: ? Unknown Service Type", service_family_name ); + proto_item_append_text( kip_item, " Unknown Service Type" ); + ok = 0; + } + else + { + col_append_str( cinfo, COL_INFO, svc_name ? svc_name : service_name ); + proto_item_append_text( kip_item, " %s", service_name ); + + /* Dissect according to Service Type + */ + switch( service ) + { + + /* CORE */ + + case KIP_SEARCH_REQUEST: + { + /* Discovery Endpoint HPAI */ + dissect_hpai( tvb, pinfo, kip_item, kip_tree, &offset, &ok, "Discovery", 1 ); + } + break; + + case KIP_SEARCH_REQUEST_EXT: + { + /* Discovery Endpoint HPAI */ + if( dissect_hpai( tvb, pinfo, kip_item, kip_tree, &offset, &ok, "Discovery", 0 ) ) + { + /* Search Request Parameters */ + dissect_srps( tvb, pinfo, kip_item, kip_tree, &offset, &ok ); + } + } + break; + + case KIP_SEARCH_RESPONSE: + case KIP_SEARCH_RESPONSE_EXT: + { + /* Control Endpoint HPAI */ + if( dissect_hpai( tvb, pinfo, kip_item, kip_tree, &offset, &ok, "Control", 0 ) ) + { + /* DIBs */ + guint8 dib_count[ 256 ] = { 0 }; + gchar* output = info; + gint output_max = sizeof info; + + *info = '\0'; + dissect_dibs( tvb, pinfo, kip_item, kip_tree, &offset, &output, &output_max, '\0', dib_count, &ok ); + if( *info ) + { + col_append_fstr( cinfo, COL_INFO, ", %s", info ); + proto_item_append_text( kip_item, ", %s", info ); + } + + if( service == KIP_SEARCH_RESPONSE ) + { + if( !dib_count[ KIP_DIB_DEVICE_INFO ] ) + { + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing DIB DevInfo" ); + ok = 0; + } + if( !dib_count[ KIP_DIB_SUPP_SVC_FAMILIES ] ) + { + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing DIB SuppSvc" ); + ok = 0; + } + } + } + } + break; + + case KIP_DESCRIPTION_REQUEST: + { + /* Control Endpoint HPAI */ + dissect_hpai( tvb, pinfo, kip_item, kip_tree, &offset, &ok, "Control", 1 ); + } + break; + + case KIP_DESCRIPTION_RESPONSE: + { + /* DIBs */ + guint8 dib_count[ 256 ] = { 0 }; + dissect_dibs( tvb, pinfo, kip_item, kip_tree, &offset, NULL, 0, ':', dib_count, &ok ); + if( !dib_count[ KIP_DIB_DEVICE_INFO ] ) + { + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing DIB DevInfo" ); + ok = 0; + } + if( !dib_count[ KIP_DIB_SUPP_SVC_FAMILIES ] ) + { + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing DIB SuppSvc" ); + ok = 0; + } + } + break; + + case KIP_CONNECT_REQUEST: + { + /* Control Endpoint HPAI */ + if( dissect_hpai( tvb, pinfo, kip_item, kip_tree, &offset, &ok, "Control", 1 ) ) + { + /* Data Endpoint HPAI */ + if( dissect_hpai( tvb, pinfo, kip_item, kip_tree, &offset, &ok, "Data", 1 ) ) + { + /* CRI */ + dissect_cri( tvb, pinfo, kip_item, kip_tree, &offset, &ok ); + } + } + } + break; + + case KIP_CONNECT_RESPONSE: + { + /* 1 byte Channel ID */ + if( remaining_len < 1 ) + { + col_append_fstr( cinfo, COL_INFO, " ???" ); + proto_item_append_text( kip_item, ", ???" ); + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing 1 byte Channel" ); + ok = 0; + } + else + { + guint8 channel = tvb_get_guint8( tvb, offset ); + proto_tree_add_item( kip_tree, hf_knxip_channel, tvb, offset, 1, ENC_BIG_ENDIAN ); + offset++; + + /* 1 byte Status */ + if( remaining_len < 2 ) + { + col_append_fstr( cinfo, COL_INFO, " ???" ); + proto_item_append_text( kip_item, ", ???" ); + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing 1 byte Status" ); + ok = 0; + } + else + { + guint8 status = tvb_get_guint8( tvb, offset ); + knxip_tree_add_status( kip_tree, tvb, offset ); + offset++; + + if( status == KIP_E_NO_ERROR ) + { + col_append_fstr( cinfo, COL_INFO, " #%02X", channel ); + proto_item_append_text( kip_item, ", Conn #%02X", channel ); + + /* Data Endpoint HPAI */ + if( dissect_hpai( tvb, pinfo, kip_item, kip_tree, &offset, &ok, "Data", 1 ) ) + { + /* CRD */ + dissect_crd( tvb, pinfo, kip_item, kip_tree, &offset, &ok ); + } + } + else + { + const gchar* status_info = val_to_str( status, error_vals, "Error 0x%02x" ); + col_append_fstr( cinfo, COL_INFO, " %s", status_info ); + proto_item_append_text( kip_item, ": %s", status_info ); + } + } + } + } + break; + + case KIP_CONNECTIONSTATE_REQUEST: + { + /* 1 byte Channel ID */ + col_append_fstr( cinfo, COL_INFO, " #" ); + proto_item_append_text( kip_item, ", Conn #" ); + + if( remaining_len < 1 ) + { + col_append_fstr( cinfo, COL_INFO, "???" ); + proto_item_append_text( kip_item, "???" ); + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing 1 byte Channel" ); + ok = 0; + } + else + { + guint8 channel = tvb_get_guint8( tvb, offset ); + col_append_fstr( cinfo, COL_INFO, "%02X", channel ); + proto_item_append_text( kip_item, "%02X", channel ); + proto_tree_add_item( kip_tree, hf_knxip_channel, tvb, offset, 1, ENC_BIG_ENDIAN ); + offset++; + + /* Reserved Byte */ + if( remaining_len < 2 ) + { + knxip_tree_add_missing_reserved( kip_tree, pinfo ); + ok = 0; + } + else + { + knxip_tree_add_reserved( kip_tree, tvb, offset, pinfo, &ok ); + offset++; + + /* Control Endpoint HPAI */ + dissect_hpai( tvb, pinfo, kip_item, kip_tree, &offset, &ok, "Control", 1 ); + } + } + } + break; + + case KIP_CONNECTIONSTATE_RESPONSE: + { + /* 1 byte Channel ID */ + col_append_fstr( cinfo, COL_INFO, " #" ); + proto_item_append_text( kip_item, ", Conn #" ); + + if( remaining_len < 1 ) + { + col_append_fstr( cinfo, COL_INFO, "???" ); + proto_item_append_text( kip_item, "???" ); + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing 1 byte Channel" ); + ok = 0; + } + else + { + guint8 channel = tvb_get_guint8( tvb, offset ); + col_append_fstr( cinfo, COL_INFO, "%02X ", channel ); + proto_item_append_text( kip_item, "%02X: ", channel ); + proto_tree_add_item( kip_tree, hf_knxip_channel, tvb, offset, 1, ENC_BIG_ENDIAN ); + offset++; + + /* 1 byte Status */ + if( remaining_len < 2 ) + { + col_append_fstr( cinfo, COL_INFO, "???" ); + proto_item_append_text( kip_item, "???" ); + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing 1 byte Status" ); + ok = 0; + } + else + { + guint8 status = tvb_get_guint8( tvb, offset ); + const gchar* status_info = val_to_str( status, error_vals, "Error 0x%02x" ); + col_append_fstr( cinfo, COL_INFO, "%s", status_info ); + proto_item_append_text( kip_item, "%s", status_info ); + knxip_tree_add_status( kip_tree, tvb, offset ); + offset++; + } + } + } + break; + + case KIP_DISCONNECT_REQUEST: + { + /* 1 byte Channel ID */ + col_append_fstr( cinfo, COL_INFO, " #" ); + proto_item_append_text( kip_item, ", Conn #" ); + + if( remaining_len < 1 ) + { + col_append_fstr( cinfo, COL_INFO, "???" ); + proto_item_append_text( kip_item, "???" ); + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing 1 byte Channel" ); + ok = 0; + } + else + { + guint8 channel = tvb_get_guint8( tvb, offset ); + col_append_fstr( cinfo, COL_INFO, "%02X", channel ); + proto_item_append_text( kip_item, "%02X", channel ); + proto_tree_add_item( kip_tree, hf_knxip_channel, tvb, offset, 1, ENC_BIG_ENDIAN ); + offset++; + + /* Reserved Byte */ + if( remaining_len < 2 ) + { + knxip_tree_add_missing_reserved( kip_tree, pinfo ); + ok = 0; + } + else + { + knxip_tree_add_reserved( kip_tree, tvb, offset, pinfo, &ok ); + offset++; + + /* Control Endpoint HPAI */ + dissect_hpai( tvb, pinfo, kip_item, kip_tree, &offset, &ok, "Control", 1 ); + } + } + } + break; + + case KIP_DISCONNECT_RESPONSE: + { + /* 1 byte Channel ID */ + col_append_fstr( cinfo, COL_INFO, " #" ); + proto_item_append_text( kip_item, ", Conn #" ); + + if( remaining_len < 1 ) + { + col_append_fstr( cinfo, COL_INFO, "???" ); + proto_item_append_text( kip_item, "???" ); + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing 1 byte Channel" ); + ok = 0; + } + else + { + guint8 channel = tvb_get_guint8( tvb, offset ); + col_append_fstr( cinfo, COL_INFO, "%02X ", channel ); + proto_item_append_text( kip_item, "%02X: ", channel ); + proto_tree_add_item( kip_tree, hf_knxip_channel, tvb, offset, 1, ENC_BIG_ENDIAN ); + offset++; + + /* 1 byte Status */ + if( remaining_len < 2 ) + { + col_append_fstr( cinfo, COL_INFO, "???" ); + proto_item_append_text( kip_item, "???" ); + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing 1 byte Status" ); + ok = 0; + } + else + { + guint8 status = tvb_get_guint8( tvb, offset ); + const gchar* status_info = val_to_str( status, error_vals, "Error 0x%02x" ); + col_append_fstr( cinfo, COL_INFO, "%s", status_info ); + proto_item_append_text( kip_item, "%s", status_info ); + knxip_tree_add_status( kip_tree, tvb, offset ); + offset++; + } + } + } + break; + + /* MANAGEMENT */ + + case KIP_CONFIGURATION_REQUEST: + { + /* Connection Header */ + if( dissect_cnhdr( tvb, pinfo, kip_item, kip_tree, &offset, &ok, FALSE ) ) + { + /* cEMI */ + dissect_cemi( tvb, pinfo, tree, &offset ); + } + } + break; + + case KIP_CONFIGURATION_ACK: + { + /* Connection Header */ + dissect_cnhdr( tvb, pinfo, kip_item, kip_tree, &offset, &ok, TRUE ); + } + break; + + /* TUNNELING */ + + case KIP_TUNNELING_REQUEST: + { + /* Connection Header */ + if( dissect_cnhdr( tvb, pinfo, kip_item, kip_tree, &offset, &ok, FALSE ) ) + { + /* cEMI */ + dissect_cemi( tvb, pinfo, tree, &offset ); + } + } + break; + + case KIP_TUNNELING_ACK: + { + /* Connection Header */ + dissect_cnhdr( tvb, pinfo, kip_item, kip_tree, &offset, &ok, TRUE ); + } + break; + + case KIP_TUNNELING_FEATURE_GET: + case KIP_TUNNELING_FEATURE_RESPONSE: + case KIP_TUNNELING_FEATURE_SET: + case KIP_TUNNELING_FEATURE_INFO: + { + /* Connection Header, 1 byte Feature ID, 1 byte Return Code, Feature Value */ + dissect_tunneling_feature( tvb, pinfo, kip_item, kip_tree, &offset, &ok, service ); + } + break; + + /* ROUTING */ + + case KIP_ROUTING_INDICATION: + { + /* cEMI */ + dissect_cemi( tvb, pinfo, tree, &offset ); + } + break; + + case KIP_ROUTING_LOST_MESSAGE: + { + /* Routing Loss */ + ok &= dissect_routing_loss( tvb, pinfo, kip_item, kip_tree, &offset ); + } + break; + + case KIP_ROUTING_BUSY: + { + /* Routing Busy */ + ok &= dissect_routing_busy( tvb, pinfo, kip_item, kip_tree, &offset ); + } + break; + + case KIP_ROUTING_SYSTEM_BROADCAST: + { + /* cEMI */ + dissect_cemi( tvb, pinfo, tree, &offset ); + } + break; + + /* REMOTE_DIAG_AND_CONFIG */ + + case KIP_REMOTE_DIAG_REQUEST: + { + /* Discovery Endpoint HPAI */ + if( dissect_hpai( tvb, pinfo, kip_item, kip_tree, &offset, &ok, "Discovery", 0 ) ) + { + /* Selector */ + dissect_selector( tvb, pinfo, kip_item, kip_tree, &offset, &ok ); + } + } + break; + + case KIP_REMOTE_DIAG_RESPONSE: + { + /* Selector */ + if( dissect_selector( tvb, pinfo, kip_item, kip_tree, &offset, &ok ) ) + { + /* DIBs */ + guint8 dib_count[ 256 ] = { 0 }; + dissect_dibs( tvb, pinfo, kip_item, kip_tree, &offset, NULL, 0, ',', dib_count, &ok ); + if( !dib_count[ KIP_DIB_IP_CONFIG ] ) + { + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing DIB IpConfig" ); + ok = 0; + } + if( !dib_count[ KIP_DIB_CUR_CONFIG ] ) + { + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing DIB CurConfig" ); + ok = 0; + } + if( !dib_count[ KIP_DIB_KNX_ADDRESSES ] ) + { + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Missing DIB KnxAddr" ); + ok = 0; + } + } + } + break; + + case KIP_REMOTE_CONFIG_REQUEST: + { + /* Discovery Endpoint HPAI */ + if( dissect_hpai( tvb, pinfo, kip_item, kip_tree, &offset, &ok, "Discovery", 0 ) ) + { + /* Selector */ + if( dissect_selector( tvb, pinfo, kip_item, kip_tree, &offset, &ok ) ) + { + /* DIBs */ + gint old_offset = offset; + dissect_dibs( tvb, pinfo, kip_item, kip_tree, &offset, NULL, 0, ',', NULL, &ok ); + if( offset <= old_offset ) + { + expert_add_info_format( pinfo, kip_item, KIP_WARNING, "Missing DIB" ); + } + } + } + } + break; + + case KIP_REMOTE_RESET_REQUEST: + { + /* Selector */ + if( dissect_selector( tvb, pinfo, kip_item, kip_tree, &offset, &ok ) ) + { + /* Reset Mode */ + ok &= dissect_resetter( tvb, pinfo, kip_item, kip_tree, &offset ); + } + } + break; + + case KIP_SECURE_WRAPPER: + ok &= dissect_secure_wrapper( header_length, tvb, pinfo, tree, kip_item, kip_tree, &offset ); + break; + + case KIP_TIMER_NOTIFY: + ok &= dissect_timer_notify( header_length, tvb, pinfo, kip_item, kip_tree, &offset ); + break; + + case KIP_SESSION_REQUEST: + ok &= dissect_session_request( tvb, pinfo, kip_item, kip_tree, &offset ); + break; + + case KIP_SESSION_RESPONSE: + ok &= dissect_session_response( tvb, pinfo, kip_item, kip_tree, &offset ); + break; + + case KIP_SESSION_AUTHENTICATE: + ok &= dissect_session_auth( tvb, pinfo, kip_item, kip_tree, &offset ); + break; + + case KIP_SESSION_STATUS: + ok &= dissect_session_status( tvb, pinfo, kip_item, kip_tree, &offset ); + break; + } + } + } + + if( offset >= 0 ) + { + remaining_len = tvb_captured_length_remaining( tvb, offset ); + if( remaining_len > 0 ) + { + if( tree ) + { + proto_item* unknown_item = knxip_tree_add_unknown_data( kip_tree, tvb, offset, remaining_len ); + expert_add_info_format( pinfo, unknown_item, KIP_ERROR, "Unexpexted trailing data" ); + } + + ok = 0; + } + } + + if( !ok ) + { + /* If not already done */ + if( !knxip_error ) + { + knxip_error = 1; + col_prepend_fstr( cinfo, COL_INFO, "? " ); + } + + proto_item_prepend_text( kip_item, "? " ); + } +} + +static void dissect_knxip( guint8 level, tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree ) +{ + gint offset = 0; + guint remaining_len = tvb_captured_length( tvb ); + guint8 header_len = 0; + guint8 protocol_version = 0; + guint16 service_id = 0; + guint16 total_len = 0; + guint8 error = 0; + + column_info* cinfo = pinfo->cinfo; + + proto_item* kip_item = NULL; + proto_tree* kip_tree = NULL; + proto_item* header_item = NULL; + proto_tree* header_tree = NULL; + proto_item* header_len_item = NULL; + proto_item* version_item = NULL; + proto_item* service_item = NULL; + proto_tree* service_tree = NULL; + proto_item* total_length_item = NULL; + + gchar version_info[ 16 ]; + + if( level == 0 ) + { + knxip_error = 0; + col_set_str( cinfo, COL_PROTOCOL, "KNXnet/IP" ); + col_clear( cinfo, COL_INFO ); + } + else + { + col_append_str( cinfo, COL_INFO, " " ); + } + + kip_item = proto_tree_add_item( tree, proto_knxip, tvb, offset, (remaining_len <= 0) ? 0 : -1, ENC_BIG_ENDIAN ); + kip_tree = proto_item_add_subtree( kip_item, ett_kip ); + + if( remaining_len <= 0 ) + { + /* This may happen if we are embedded in another KNXnet/IP frame (level != 0) + */ + proto_item_prepend_text( kip_item, "? " ); + expert_add_info_format( pinfo, kip_item, KIP_ERROR, "Expected: min 6 bytes" ); + col_append_str( cinfo, COL_INFO, "? empty" ); + + /* If not already done */ + if( !knxip_error ) + { + knxip_error = 1; + col_prepend_fstr( cinfo, COL_INFO, "? " ); + } + } + else + { + /* 1 byte Header Length */ + header_len = tvb_get_guint8( tvb, 0 ); + + if( tree ) + { + header_item = proto_tree_add_none_format( kip_tree, hf_folder, tvb, 0, + (header_len <= remaining_len) ? header_len : remaining_len, "KNX/IP Header" ); + header_tree = proto_item_add_subtree( header_item, ett_efcp ); + header_len_item = proto_tree_add_uint_format( header_tree, hf_knxip_header_length, + tvb, 0, 1, header_len, "Header Length: %u bytes", header_len ); + } + + if( header_len > remaining_len ) + { + proto_item_prepend_text( header_len_item, "? " ); + expert_add_info_format( pinfo, header_len_item, KIP_ERROR, "Available: %u bytes", remaining_len ); + error = 1; + header_len = (guint8) remaining_len; + } + else if( header_len != 0x06 ) + { + proto_item_prepend_text( header_len_item, "? " ); + expert_add_info_format( pinfo, header_len_item, KIP_ERROR, "Expected: 6 bytes" ); + error = 1; + } + + offset++; + + if( header_len >= 2 ) + { + /* 1 byte Protocol Version */ + protocol_version = tvb_get_guint8( tvb, 1 ); + g_snprintf( version_info, sizeof version_info, "%u.%u", hi_nibble( protocol_version ), lo_nibble( protocol_version ) ); + + if( tree ) + { + version_item = proto_tree_add_uint_format( header_tree, hf_knxip_protocol_version, + tvb, 1, 1, protocol_version, "Protocol Version: %s", version_info ); + } + + if( protocol_version != 0x10 ) + { + proto_item_prepend_text( version_item, "? " ); + expert_add_info_format( pinfo, version_item, KIP_ERROR, "Expected: Protocol Version 1.0" ); + error = 1; + } + + offset++; + + if( header_len >= 4 ) + { + /* 2 bytes Service ID */ + service_id = tvb_get_ntohs( tvb, 2 ); + + if( tree ) + { + const gchar* name = try_val_to_str( service_id, knxip_service_type_vals ); + proto_item_append_text( header_item, ": " ); + if( name ) + proto_item_append_text( header_item, "%s", name ); + else + proto_item_append_text( header_item, "Service = 0x%04x", service_id ); + service_item = proto_tree_add_item( header_tree, hf_knxip_service_id, tvb, 2, 2, ENC_BIG_ENDIAN ); + service_tree = proto_item_add_subtree( service_item, ett_service ); + proto_tree_add_item( service_tree, hf_knxip_service_family, tvb, 2, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( service_tree, hf_knxip_service_type, tvb, 2, 2, ENC_BIG_ENDIAN ); + } + + offset += 2; + + if( header_len >= 6 ) + { + /* 2 bytes Total Length */ + total_len = tvb_get_ntohs( tvb, 4 ); + + if( tree ) + { + total_length_item = proto_tree_add_uint_format( header_tree, hf_knxip_total_length, + tvb, 4, 2, total_len, "Total Length: %u bytes", total_len ); + } + + if( total_len < header_len ) + { + proto_item_prepend_text( total_length_item, "? " ); + expert_add_info_format( pinfo, total_length_item, KIP_ERROR, "Expected: >= Header Length" ); + error = 1; + } + else if( total_len > remaining_len ) + { + proto_item_prepend_text( total_length_item, "? " ); + expert_add_info_format( pinfo, total_length_item, KIP_ERROR, "Available: %u bytes", remaining_len ); + error = 1; + } + else if( total_len < remaining_len ) + { + proto_item_prepend_text( total_length_item, "? " ); + expert_add_info_format( pinfo, total_length_item, KIP_ERROR, "Available: %u bytes", remaining_len ); + error = 1; + } + + offset += 2; + } + } + } + + if( offset < header_len ) + { + knxip_tree_add_unknown_data( header_tree, tvb, offset, header_len - offset ); + } + + if( error ) + { + proto_item_prepend_text( header_item, "? " ); + + if( level == 0 ) + { + col_prepend_fstr( cinfo, COL_PROTOCOL, "? " ); + } + else + { + /* If not already done */ + if( !knxip_error ) + { + knxip_error = 1; + col_prepend_fstr( cinfo, COL_INFO, "? " ); + } + } + } + + dissect_knxip_data( header_len, protocol_version, service_id, tvb, pinfo, tree, kip_item, kip_tree ); + } +} + +static gint dissect_tcp_knxip( tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, void* udata _U_ ) +{ + knxip_host_protocol = IP_PROTO_TCP; + dissect_knxip( 0, tvb, pinfo, tree ); + return tvb_captured_length( tvb ); +} + +static gint dissect_udp_knxip( tvbuff_t* tvb, packet_info* pinfo, proto_tree* tree, void* udata _U_ ) +{ + knxip_host_protocol = IP_PROTO_UDP; + dissect_knxip( 0, tvb, pinfo, tree ); + return tvb_captured_length( tvb ); +} + +void proto_register_knxip( void ) +{ + /* Header fields */ + static hf_register_info hf[] = + { + { &hf_bytes, { "Data", "knxip.data", FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_folder, { "Folder", "knxip.folder", FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_knxip_header_length, { "Header Length", "knxip.headerlength", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_knxip_protocol_version, { "Protocol Version", "knxip.version", FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_service_id, { "Service Identifier", "knxip.service", FT_UINT16, BASE_HEX, VALS( knxip_service_type_vals ), 0, NULL, HFILL } }, + { &hf_knxip_service_family, { "Service Family", "knxip.service.family", FT_UINT8, BASE_HEX, VALS( knxip_service_family_vals ), 0, NULL, HFILL } }, + { &hf_knxip_service_type, { "Service Type", "knxip.service.type", FT_UINT16, BASE_HEX, VALS( knxip_service_type_vals ), 0, NULL, HFILL } }, + { &hf_knxip_total_length, { "Total Length", "knxip.totallength", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_knxip_structure_length, { "Structure Length", "knxip.struct.length", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_knxip_host_protocol, { "Host Protocol", "knxip.hostprotocol", FT_UINT8, BASE_HEX, VALS( host_protocol_vals ), 0, NULL, HFILL } }, + { &hf_knxip_ip_address, { "IP Address", "knxip.ipaddr", FT_IPv4, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_knxip_port, { "Port Number", "knxip.port", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_knxip_description_type, { "Description Type", "knxip.dibtype", FT_UINT8, BASE_HEX, VALS( description_type_vals ), 0, NULL, HFILL } }, + { &hf_knxip_knx_medium, { "KNX Medium", "knxip.medium", FT_UINT8, BASE_HEX, VALS( medium_type_vals ), 0, NULL, HFILL } }, + { &hf_knxip_device_status, { "Device Status", "knxip.device.status", FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_program_mode, { "Programming Mode", "knxip.progmode", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_knxip_knx_address, { "KNX Individual Address", "knxip.knxaddr", FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_project_id, { "Project Installation Identifier", "knxip.project", FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_project_number, { "Project Number", "knxip.project.nr", FT_UINT16, BASE_DEC, NULL, 0xFFF0, NULL, HFILL } }, + { &hf_knxip_installation_number, { "Installation Number", "knxip.project.installation", FT_UINT16, BASE_DEC, NULL, 0x000F, NULL, HFILL } }, + { &hf_knxip_serial_number, { "KNX Serial Number", "knxip.sernr", FT_UINT48, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_multicast_address, { "Multicast Address", "knxip.mcaddr", FT_IPv4, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_knxip_mac_address, { "MAC Address", "knxip.macaddr", FT_ETHER, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_knxip_friendly_name, { "Friendly Name", "knxip.device.name", FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_knxip_service_version, { "Service Version", "knxip.service.version", FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_security_version, { "Security Version", "knxip.security.version", FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_manufacturer_code, { "KNX Manufacturer Code", "knxip.manufacturer", FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_connection_type, { "Connection Type", "knxip.conn.type", FT_UINT8, BASE_HEX, VALS( connection_type_vals ), 0, NULL, HFILL } }, + { &hf_knxip_knx_layer, { "KNX Layer", "knxip.tunnel.layer", FT_UINT8, BASE_HEX, VALS( knx_layer_vals ), 0, NULL, HFILL } }, + { &hf_knxip_channel, { "Channel", "knxip.channel", FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_status, { "Status", "knxip.status", FT_UINT8, BASE_HEX, VALS( error_vals ), 0, NULL, HFILL } }, + { &hf_knxip_reserved, { "Reserved", "knxip.reserved", FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_seq_counter, { "Sequence Counter", "knxip.seqctr", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_knxip_ip_subnet, { "Subnet Mask", "knxip.subnet", FT_IPv4, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_knxip_ip_gateway, { "Default Gateway", "knxip.gateway", FT_IPv4, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_knxip_ip_assign, { "IP Assignment", "knxip.ipassign", FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_ip_caps, { "IP Capabilities", "knxip.ipcaps", FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_ip_dhcp, { "DHCP Server", "knxip.dhcp", FT_IPv4, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_knxip_tunnel_feature, { "Tunneling Feature Identifier", "knxip.tunnel.feature", FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_routing_loss, { "Lost Messages", "knxip.loss", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_knxip_busy_time, { "Wait Time", "knxip.busy.time", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_knxip_busy_control, { "Control", "knxip.busy.control", FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_selector, { "Selector", "knxip.selector", FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_max_apdu_length, { "Max APDU Length", "knxip.maxapdulength", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_knxip_medium_status, { "Medium Status", "knxip.medium.status", FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_mask_version, { "Mask Version", "knxip.mask.version", FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_srp_mandatory, { "Mandatory", "knxip.srp.mandatory", FT_UINT8, BASE_DEC, NULL, 0x80, NULL, HFILL } }, + { &hf_knxip_srp_type, { "SRP Type", "knxip.srp.type", FT_UINT8, BASE_HEX, NULL, 0x7F, NULL, HFILL } }, + { &hf_knxip_reset_command, { "Command", "knxip.reset.command", FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_session, { "Session", "knxip.session", FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_tag, { "Tag", "knxip.tag", FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_knxip_user, { "User", "knxip.user", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_knxip_session_status, { "Status", "knxip.session.status", FT_UINT8, BASE_HEX, VALS( session_status_vals ), 0, NULL, HFILL } }, + }; + + /* Subtrees */ + static gint *ett[] = + { + &ett_kip, + &ett_efcp, + &ett_service, + &ett_hpai, + &ett_dib, + &ett_medium, + &ett_status, + &ett_projectid, + &ett_service_family, + &ett_ip_assignment, + &ett_cri, + &ett_crd, + &ett_cnhdr, + &ett_loss, + &ett_busy, + &ett_selector, + &ett_decrypted, + &ett_tunnel, + }; + + static ei_register_info ei[] = + { + { &ei_knxip_error, { "knxip.error", PI_MALFORMED, PI_ERROR, "KNX/IP error", EXPFILL }}, + { &ei_knxip_warning, { "knxip.warning", PI_PROTOCOL, PI_WARN, "KNX/IP warning", EXPFILL }}, + }; + + expert_module_t* expert_knxip; + module_t* knxip_module; + guint8 x; + + proto_knxip = proto_register_protocol( "KNX/IP", "KNX/IP", "kip" ); + + proto_register_field_array( proto_knxip, hf, array_length( hf ) ); + proto_register_subtree_array( ett, array_length( ett ) ); + + register_dissector( "udp.knxip", dissect_udp_knxip, proto_knxip ); + register_dissector( "tcp.knxip", dissect_tcp_knxip, proto_knxip ); + + //register_dissector_table( "knxip.version", "KNXnet/IP Protocol Version", proto_knxip, FT_UINT8, BASE_HEX ); + + expert_knxip = expert_register_protocol( proto_knxip ); + expert_register_field_array( expert_knxip, ei, array_length( ei ) ); + + knxip_module = prefs_register_protocol( proto_knxip, proto_reg_handoff_knxip ); + + prefs_register_filename_preference( knxip_module, "key_file", "Key file", "Keyring.XML file (exported from ETS)", + &pref_key_file_name, FALSE ); + prefs_register_string_preference( knxip_module, "key_file_pwd", "Key file password", "Keyring password", + &pref_key_file_pwd ); + prefs_register_filename_preference( knxip_module, "key_info_file", "Key info output file", "Output file (- for stdout) for keys extracted from key file", + &pref_key_info_file_name, FALSE ); + + prefs_register_static_text_preference( knxip_module, "", "", NULL ); + + prefs_register_static_text_preference( knxip_module, "keys_0", + "KNX decryption keys", + NULL ); + prefs_register_static_text_preference( knxip_module, "keys_1", + "(KNX/IP multicast/group keys, KNX/IP unicast session keys, KNX data-security tool keys and link-table keys)", + NULL ); + prefs_register_static_text_preference( knxip_module, "keys_2", + "(format: 16 bytes as hex; example: A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF)", + NULL ); + + for( x = 1; x <= MAX_KNX_DECRYPTION_KEYS; ++x ) + { + gchar* name = wmem_strdup_printf( wmem_epan_scope(), "key_%u", x ); + gchar* title = wmem_strdup_printf( wmem_epan_scope(), "%u. key", x ); + prefs_register_string_preference( knxip_module, name, title, + "KNX decryption key (format: 16 bytes as hex; example: A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF)", + &pref_key_texts[ x - 1 ] ); + } +} + +void proto_reg_handoff_knxip( void ) +{ + dissector_handle_t knxip_handle; + guint8 x; + const gchar* text; + + knxip_handle = find_dissector( "udp.knxip" ); + dissector_add_uint( "udp.port", 3671, knxip_handle ); + dissector_add_uint( "udp.port", 3672, knxip_handle ); + dissector_add_uint( "udp.port", 40000, knxip_handle ); + + knxip_handle = find_dissector( "tcp.knxip" ); + dissector_add_uint( "tcp.port", 3671, knxip_handle ); + dissector_add_uint( "tcp.port", 3672, knxip_handle ); + dissector_add_uint( "tcp.port", 40000, knxip_handle ); + + /* Evaluate preferences + */ + if( pref_key_file_name ) + { + /* Read Keyring.XML file (containing decryption keys, exported from ETS) */ + read_knx_keyring_xml_file( pref_key_file_name, pref_key_file_pwd, pref_key_info_file_name ); + } + + knx_decryption_key_count = 0; + for( x = 0; x < MAX_KNX_DECRYPTION_KEYS && knx_decryption_key_count < MAX_KNX_DECRYPTION_KEYS; ++x ) + { + text = pref_key_texts[ x ]; + if( text ) + { + if( hex_to_knx_key( text, knx_decryption_keys[ knx_decryption_key_count ] ) ) + { + ++knx_decryption_key_count; + } + } + } +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 2 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=2 tabstop=8 expandtab: + * :indentSize=2:tabSize=8:noTabs=true: + */ diff --git a/epan/dissectors/packet-knxip.h b/epan/dissectors/packet-knxip.h new file mode 100644 index 0000000000..b5a04bdaa4 --- /dev/null +++ b/epan/dissectors/packet-knxip.h @@ -0,0 +1,56 @@ +/* packet-knxip.h + * Routines for KNXnet/IP dissection + * Copyright 2004, Jan Kessler <kessler@ise.de> + * + * Ethereal - Network traffic analyzer + * By Gerald Combs <gerald@ethereal.com> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef PACKET_KNXIP_H +#define PACKET_KNXIP_H + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <glib.h> +#include <epan/expert.h> +#include <epan/packet.h> +#include <epan/proto.h> +#include <epan/ipproto.h> +#include <epan/prefs.h> +#include <epan/tvbuff.h> + +#include "packet-knxip_decrypt.h" + +#define KIP_ERROR &ei_knxip_error +#define KIP_WARNING &ei_knxip_warning + +extern expert_field ei_knxip_error; +extern expert_field ei_knxip_warning; + +extern guint8 knxip_host_protocol; +extern guint8 knxip_error; + +#define MAX_KNX_DECRYPTION_KEYS 10 + +extern guint8 knx_decryption_keys[ MAX_KNX_DECRYPTION_KEYS ][ KNX_KEY_LENGTH ]; +extern guint8 knx_decryption_key_count; + +#endif + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 2 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=2 tabstop=8 expandtab: + * :indentSize=2:tabSize=8:noTabs=true: + */ diff --git a/epan/dissectors/packet-knxip_decrypt.c b/epan/dissectors/packet-knxip_decrypt.c new file mode 100644 index 0000000000..d9648257c4 --- /dev/null +++ b/epan/dissectors/packet-knxip_decrypt.c @@ -0,0 +1,811 @@ +/* packet-knxip_decrypt.c + * Decryption keys and decryption functions for KNX/IP Dissector + * Copyright 2018, ise GmbH <Ralf.Nasilowski@ise.de> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +// Activate g_debug output with environment variable: G_MESSAGES_DEBUG=packet-knxip +#define G_LOG_DOMAIN "packet-knxip" + +#include <wsutil/file_util.h> +#include "proto.h" +#include "packet-knxip_decrypt.h" +#include <epan/wmem/wmem.h> +#include <wsutil/wsgcrypt.h> +#include <wsutil/strtoi.h> + +#define TEXT_BUFFER_SIZE 128 + +#define IPA_SIZE 4 // = size of IPv4 address + +#define BASE64_KNX_KEY_LENGTH 24 // = length of base64 encoded KNX key + +struct knx_keyring_mca_keys* knx_keyring_mca_keys; +struct knx_keyring_ga_keys* knx_keyring_ga_keys; +struct knx_keyring_ga_senders* knx_keyring_ga_senders; +struct knx_keyring_ia_keys* knx_keyring_ia_keys; +struct knx_keyring_ia_seqs* knx_keyring_ia_seqs; + +// Encrypt 16-byte block via AES +static void encrypt_block( const guint8 key[ KNX_KEY_LENGTH ], const guint8 plain[ KNX_KEY_LENGTH ], guint8 p_crypt[ KNX_KEY_LENGTH ] ) +{ + gcry_cipher_hd_t cryptor = NULL; + gcry_cipher_open( &cryptor, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CBC, 0 ); + gcry_cipher_setkey( cryptor, key, KNX_KEY_LENGTH ); + gcry_cipher_encrypt( cryptor, p_crypt, KNX_KEY_LENGTH, plain, KNX_KEY_LENGTH ); + gcry_cipher_close( cryptor ); +} + +// Create B_0 for CBC-MAC +static void build_b0( guint8 p_result[ KNX_KEY_LENGTH ], const guint8* nonce, guint8 nonce_length ) +{ + DISSECTOR_ASSERT( nonce_length <= KNX_KEY_LENGTH ); + if( nonce_length ) memcpy( p_result, nonce, nonce_length ); + memset( p_result + nonce_length, 0, KNX_KEY_LENGTH - nonce_length ); +} + +// Create Ctr_0 for CCM encryption/decryption +static void build_ctr0( guint8 p_result[ KNX_KEY_LENGTH ], const guint8* nonce, guint8 nonce_length ) +{ + build_b0( p_result, nonce, nonce_length ); + p_result[ KNX_KEY_LENGTH - 2 ] = 0xFF; +} + +// Calculate MAC for KNX IP Security or KNX Data Security +void knx_ccm_calc_cbc_mac( guint8* p_mac, const guint8 key[ KNX_KEY_LENGTH ], + const guint8* a_bytes, gint a_length, const guint8* p_bytes, gint p_length, + const guint8 b_0[ KNX_KEY_LENGTH ] ) +{ + guint8 plain[ KNX_KEY_LENGTH ]; + guint8 b_pos; + + // Add B_0 + memcpy( plain, b_0, KNX_KEY_LENGTH ); + encrypt_block( key, plain, p_mac ); + + // Add a_length + plain[ 0 ] = (guint8) ((a_length >> 8) ^ p_mac[ 0 ]); + plain[ 1 ] = (guint8) ((a_length & 0xFF) ^ p_mac[ 1 ]); + b_pos = 2; + + // Add a_bytes directly followed by p_bytes + while( a_length || p_length ) + { + while( a_length && b_pos < KNX_KEY_LENGTH ) + { + plain[ b_pos ] = *a_bytes++ ^ p_mac[ b_pos ]; + --a_length; + ++b_pos; + } + + while( p_length && b_pos < KNX_KEY_LENGTH ) + { + plain[ b_pos ] = *p_bytes++ ^ p_mac[ b_pos ]; + --p_length; + ++b_pos; + } + + while( b_pos < KNX_KEY_LENGTH ) + { + plain[ b_pos ] = p_mac[ b_pos ]; + ++b_pos; + } + + encrypt_block( key, plain, p_mac ); + + b_pos = 0; + } +} + +// Calculate MAC for KNX IP Security, using 6-byte Sequence ID +void knxip_ccm_calc_cbc_mac( guint8* p_mac, const guint8 key[ KNX_KEY_LENGTH ], + const guint8* a_bytes, gint a_length, const guint8* p_bytes, gint p_length, + const guint8* nonce, guint8 nonce_length ) +{ + guint8 b_0[ KNX_KEY_LENGTH ]; + build_b0( b_0, nonce, nonce_length ); + b_0[ KNX_KEY_LENGTH - 2 ] = (guint8) (p_length >> 8); + b_0[ KNX_KEY_LENGTH - 1 ] = (guint8) (p_length & 0xFF); + knx_ccm_calc_cbc_mac( p_mac, key, a_bytes, a_length, p_bytes, p_length, b_0 ); +} + +// Encrypt for KNX IP Security or KNX Data Security +guint8* knx_ccm_encrypt( guint8* p_result, const guint8 key[ KNX_KEY_LENGTH ], const guint8* p_bytes, gint p_length, + const guint8* mac, guint8 mac_length, const guint8 ctr_0[ KNX_KEY_LENGTH ], guint8 s0_bytes_used_for_mac ) +{ + if( p_length >= 0 && !(p_length && !p_bytes) ) + { + // NB: mac_length = 16 (for IP Security), or 4 (for Data Security) + + guint8* result = p_result ? p_result : (guint8*) wmem_alloc( wmem_packet_scope(), p_length + mac_length ); + + guint8* dest = result; + + guint8 ctr[ KNX_KEY_LENGTH ]; + guint8 mask[ KNX_KEY_LENGTH ]; + guint8 mask_0[ KNX_KEY_LENGTH ]; + guint8 b_pos; + + // Encrypt ctr_0 for mac + memcpy( ctr, ctr_0, KNX_KEY_LENGTH ); + encrypt_block( key, ctr, mask_0 ); + + // Encrypt p_bytes with rest of S_0, only if mac_length < 16. + b_pos = s0_bytes_used_for_mac; + while (p_length && b_pos < KNX_KEY_LENGTH ) + { + *dest++ = mask_0[b_pos++] ^ *p_bytes++; + --p_length; + } + + // Encrypt p_bytes + while( p_length ) + { + // Increment and encrypt ctr + ++ctr[ KNX_KEY_LENGTH - 1 ]; + encrypt_block( key, ctr, mask ); + + // Encrypt input block via encrypted ctr + b_pos = 0; + while( p_length && b_pos < KNX_KEY_LENGTH ) + { + *dest++ = mask[ b_pos++] ^ *p_bytes++; + --p_length; + } + } + + if( mac ) + { + if( mac_length > KNX_KEY_LENGTH ) + { + mac_length = KNX_KEY_LENGTH; + } + + // Encrypt and append mac + b_pos = 0; + while( mac_length ) + { + *dest++ = mask_0[ b_pos++] ^ *mac++; + --mac_length; + } + } + + return result; + } + + return NULL; +} + +// Encrypt for KNX IP Security (with 16-byte MAC and Nonce based on 6-byte Sequence ID) +guint8* knxip_ccm_encrypt( guint8* p_result, const guint8 key[ KNX_KEY_LENGTH ], const guint8* p_bytes, gint p_length, + const guint8* mac, const guint8* nonce, guint8 nonce_length ) +{ + guint8 ctr_0[ KNX_KEY_LENGTH ]; + build_ctr0( ctr_0, nonce, nonce_length ); + return knx_ccm_encrypt( p_result, key, p_bytes, p_length, mac, KNX_KEY_LENGTH, ctr_0, KNX_KEY_LENGTH ); +} + +// Decrypt for KNX-IP Security (with 16-byte MAC and Nonce based on 6-byte Sequence ID) +guint8* knxip_ccm_decrypt( guint8* p_result, const guint8 key[ KNX_KEY_LENGTH ], const guint8* crypt, gint crypt_length, + const guint8* nonce, guint8 nonce_length ) +{ + gint p_length = crypt_length - KNX_KEY_LENGTH; + guint8 ctr_0[ KNX_KEY_LENGTH ]; + build_ctr0( ctr_0, nonce, nonce_length ); + return knx_ccm_encrypt( p_result, key, crypt, p_length, crypt + p_length, KNX_KEY_LENGTH, ctr_0, KNX_KEY_LENGTH ); +} + +static void fprintf_hex( FILE* f, const guint8* data, guint8 length ) +{ + for( ; length; --length ) fprintf( f, " %02X", *data++ ); + fputc( '\n', f ); +} + +static void clear_keyring_data( void ) +{ + while( knx_keyring_mca_keys ) + { + struct knx_keyring_mca_keys* mca_key = knx_keyring_mca_keys; + knx_keyring_mca_keys = mca_key->next; + wmem_free( wmem_epan_scope(), mca_key ); + } + + while( knx_keyring_ga_keys ) + { + struct knx_keyring_ga_keys* ga_key = knx_keyring_ga_keys; + knx_keyring_ga_keys = ga_key->next; + wmem_free( wmem_epan_scope(), ga_key ); + } + + while( knx_keyring_ga_senders ) + { + struct knx_keyring_ga_senders* ga_sender = knx_keyring_ga_senders; + knx_keyring_ga_senders = ga_sender->next; + wmem_free( wmem_epan_scope(), ga_sender ); + } + + while( knx_keyring_ia_keys ) + { + struct knx_keyring_ia_keys* ia_key = knx_keyring_ia_keys; + knx_keyring_ia_keys = ia_key->next; + wmem_free( wmem_epan_scope(), ia_key ); + } + + while( knx_keyring_ia_seqs ) + { + struct knx_keyring_ia_seqs* ia_seq = knx_keyring_ia_seqs; + knx_keyring_ia_seqs = ia_seq->next; + wmem_free( wmem_epan_scope(), ia_seq ); + } +} + +// Read IP address +static void read_ip_addr( guint8 result[ 4 ], const gchar* text ) +{ + ws_in4_addr value = 0; + if( ws_inet_pton4( text, &value ) ) + memcpy( result, &value, 4 ); + else + memset( result, 0, 4 ); +} + +// Read KNX group address +static guint16 read_ga( const gchar* text ) +{ + guint a[ 3 ]; + gint n = sscanf( text, "%u/%u/%u", a, a + 1, a + 2 ); + return + (n == 1) ? (guint16) a[ 0 ] : + (n == 2) ? (guint16) ((a[ 0 ] << 11) | a[ 1 ]) : + (n == 3) ? (guint16) ((a[ 0 ] << 11) | (a[ 1 ] << 8) | a[ 2 ]) : + 0; +} + +// Read KNX individual address +static guint16 read_ia( const gchar* text ) +{ + guint a[ 3 ]; + gint n = sscanf( text, "%u.%u.%u", a, a + 1, a + 2 ); + return + (n == 1) ? (guint16) a[ 0 ] : + (n == 2) ? (guint16) ((a[ 0 ] << 8) | a[ 1 ]) : + (n == 3) ? (guint16) ((a[ 0 ] << 12) | (a[ 1 ] << 8) | a[ 2 ]) : + 0; +} + +// Read 6-byte sequence number from decimal representation +static guint64 read_seq( const gchar* text ) +{ + guint64 result; + return ws_strtou64( text, NULL, &result ) ? result : 0; +} + +// Decrypt key +static void decrypt_key( guint8 key[] _U_, guint8 password_hash[] _U_, guint8 created_hash[] _U_ ) +{ + // TODO: decrypt as AES128-CBC(key, password_hash, created_hash) +} + +// Decode and decrypt key +static void decode_and_decrypt_key( guint8 key[ BASE64_KNX_KEY_LENGTH + 1 ], const gchar* text, guint8 password_hash[], guint8 created_hash[] ) +{ + gsize out_len; + g_snprintf( (gchar*) key, BASE64_KNX_KEY_LENGTH + 1, "%s", text ); + g_base64_decode_inplace( (gchar*) key, &out_len ); + decrypt_key( key, password_hash, created_hash ); +} + +// Add MCA <-> key association +static void add_mca_key( const guint8 mca[ IPA_SIZE ], const gchar* text, guint8 password_hash[], guint8 created_hash[], FILE* f2 ) +{ + gint text_length = (gint) strlen( text ); + + if( text_length == BASE64_KNX_KEY_LENGTH ) + { + guint8 key[ BASE64_KNX_KEY_LENGTH + 1 ]; + struct knx_keyring_mca_keys** mca_keys_next; + struct knx_keyring_mca_keys* mca_key; + + decode_and_decrypt_key( key, text, password_hash, created_hash ); + + mca_keys_next = &knx_keyring_mca_keys; + + while( (mca_key = *mca_keys_next) != NULL ) + { + if( memcmp( mca_key->mca, mca, IPA_SIZE ) == 0 ) + { + if( memcmp( mca_key->key, key, KNX_KEY_LENGTH ) == 0 ) + { + return; + } + } + + mca_keys_next = &mca_key->next; + } + + if( f2 ) + { + fprintf( f2, "MCA %u.%u.%u.%u key", mca[ 0 ], mca[ 1 ], mca[ 2 ], mca[ 3 ] ); + fprintf_hex( f2, key, KNX_KEY_LENGTH ); + } + + mca_key = (struct knx_keyring_mca_keys*) wmem_alloc( wmem_epan_scope(), sizeof( struct knx_keyring_mca_keys ) ); + + if( mca_key ) + { + mca_key->next = NULL; + memcpy( mca_key->mca, mca, IPA_SIZE ); + memcpy( mca_key->key, key, KNX_KEY_LENGTH ); + + *mca_keys_next = mca_key; + } + } +} + +// Add GA <-> key association +static void add_ga_key( guint16 ga, const gchar* text, guint8 password_hash[], guint8 created_hash[], FILE* f2 ) +{ + gint text_length = (gint) strlen( text ); + + if( text_length == BASE64_KNX_KEY_LENGTH ) + { + guint8 key[ BASE64_KNX_KEY_LENGTH + 1 ]; + struct knx_keyring_ga_keys** ga_keys_next; + struct knx_keyring_ga_keys* ga_key; + + decode_and_decrypt_key( key, text, password_hash, created_hash ); + + ga_keys_next = &knx_keyring_ga_keys; + + while( (ga_key = *ga_keys_next) != NULL ) + { + if( ga_key->ga == ga ) + { + if( memcmp( ga_key->key, key, KNX_KEY_LENGTH ) == 0 ) + { + return; + } + } + + ga_keys_next = &ga_key->next; + } + + if( f2 ) + { + fprintf( f2, "GA %u/%u/%u key", (ga >> 11) & 0x1F, (ga >> 8) & 0x7, ga & 0xFF ); + fprintf_hex( f2, key, KNX_KEY_LENGTH ); + } + + ga_key = (struct knx_keyring_ga_keys*) wmem_alloc( wmem_epan_scope(), sizeof( struct knx_keyring_ga_keys ) ); + + if( ga_key ) + { + ga_key->next = NULL; + ga_key->ga = ga; + memcpy( ga_key->key, key, KNX_KEY_LENGTH ); + + *ga_keys_next = ga_key; + } + } +} + +// Add GA <-> sender association +static void add_ga_sender( guint16 ga, const gchar* text, FILE* f2 ) +{ + guint16 ia = read_ia( text ); + struct knx_keyring_ga_senders** ga_senders_next = &knx_keyring_ga_senders; + struct knx_keyring_ga_senders* ga_sender; + + while( (ga_sender = *ga_senders_next) != NULL ) + { + if( ga_sender->ga == ga ) + { + if( ga_sender->ia == ia ) + { + return; + } + } + + ga_senders_next = &ga_sender->next; + } + + if( f2 ) + { + fprintf( f2, "GA %u/%u/%u sender %u.%u.%u\n", (ga >> 11) & 0x1F, (ga >> 8) & 0x7, ga & 0xFF, (ia >> 12) & 0xF, (ia >> 8) & 0xF, ia & 0xFF ); + } + + ga_sender = (struct knx_keyring_ga_senders*) wmem_alloc( wmem_epan_scope(), sizeof( struct knx_keyring_ga_senders ) ); + + if( ga_sender ) + { + ga_sender->next = NULL; + ga_sender->ga = ga; + ga_sender->ia = ia; + + *ga_senders_next = ga_sender; + } +} + +// Add IA <-> key association +static void add_ia_key( guint16 ia, const gchar* text, guint8 password_hash[], guint8 created_hash[], FILE* f2 ) +{ + gint text_length = (gint) strlen( text ); + + if( text_length == BASE64_KNX_KEY_LENGTH ) + { + guint8 key[ BASE64_KNX_KEY_LENGTH + 1 ]; + struct knx_keyring_ia_keys** ia_keys_next; + struct knx_keyring_ia_keys* ia_key; + + decode_and_decrypt_key( key, text, password_hash, created_hash ); + + ia_keys_next = &knx_keyring_ia_keys; + + while( (ia_key = *ia_keys_next) != NULL ) + { + if( ia_key->ia == ia ) + { + if( memcmp( ia_key->key, key, KNX_KEY_LENGTH ) == 0 ) + { + return; + } + } + + ia_keys_next = &ia_key->next; + } + + if( f2 ) + { + fprintf( f2, "IA %u.%u.%u key", (ia >> 12) & 0xF, (ia >> 8) & 0xF, ia & 0xFF ); + fprintf_hex( f2, key, KNX_KEY_LENGTH ); + } + + ia_key = (struct knx_keyring_ia_keys*) wmem_alloc( wmem_epan_scope(), sizeof( struct knx_keyring_ia_keys ) ); + + if( ia_key ) + { + ia_key->next = NULL; + ia_key->ia = ia; + memcpy( ia_key->key, key, KNX_KEY_LENGTH ); + + *ia_keys_next = ia_key; + } + } +} + +// Add IA <-> sequence number association +static void add_ia_seq( guint16 ia, const gchar* text, FILE* f2 ) +{ + guint64 seq = read_seq( text ); + + struct knx_keyring_ia_seqs** ia_seqs_next = &knx_keyring_ia_seqs; + struct knx_keyring_ia_seqs* ia_seq; + + while( (ia_seq = *ia_seqs_next) != NULL ) + { + if( ia_seq->ia == ia ) + { + if( ia_seq->seq == seq ) + { + return; + } + } + + ia_seqs_next = &ia_seq->next; + } + + if( f2 ) + { + fprintf( f2, "IA %u.%u.%u SeqNr %" G_GINT64_MODIFIER "u\n", (ia >> 12) & 0xF, (ia >> 8) & 0xF, ia & 0xFF, seq ); + } + + ia_seq = (struct knx_keyring_ia_seqs*) wmem_alloc( wmem_epan_scope(), sizeof( struct knx_keyring_ia_seqs ) ); + + if( ia_seq ) + { + ia_seq->next = NULL; + ia_seq->ia = ia; + ia_seq->seq = seq; + + *ia_seqs_next = ia_seq; + } +} + +// Calculate PBKDF2(HMAC-SHA256, password, "1.keyring.ets.knx.org", 65536, 128) +static void make_password_hash( guint8 password_hash[] _U_, const gchar* password _U_ ) +{ + // TODO: password_hash = PBKDF2(HMAC-SHA256, password, "1.keyring.ets.knx.org", 65536, 128) +} + +// Calculate MSB128(SHA256(created)) +static void make_created_hash( guint8 created_hash[] _U_, const gchar* created _U_ ) +{ + // TODO: created_hash = MSB128(SHA256(created)) +} + +// Read KNX security key info from keyring XML file. +// +// An example keyring XML file is +// "test/keys/knx_keyring.xml". +// +// Corresponding test is +// suite_decryption.case_decrypt_knxip.test_knxip_keyring_xml_import +// +// We do not use LibXml2 here, because +// (1) we want to be platform independent, +// (2) we just want to extract some data from the keyring XML file, +// (3) we want to avoid the complicated recursive DOM processing implied by LibXml2. +// +// Resulting decoded and decrypted 16-byte keys with context info are optionally written to a "key info" text file. +// This may be useful, as these keys are not directly available from the keyring XML file . +void read_knx_keyring_xml_file( const gchar* key_file, const gchar* password, const gchar* key_info_file ) +{ + // Clear old keyring data + clear_keyring_data(); + + // Read new data from keyring XML file + FILE* f = ws_fopen( key_file, "r" ); + + // Optionally write extracted data to key info file + FILE* f2 = (!key_info_file || !*key_info_file) ? NULL : + (strcmp( key_info_file, "-" ) == 0) ? stdout : + ws_fopen( key_info_file, "w" ); + + if( f ) + { + guint8 backbone_mca[ IPA_SIZE ]; + guint8 backbone_mca_valid = 0; + guint16 group_ga = 0; + guint8 group_ga_valid = 0; + guint16 device_ia = 0; + guint8 device_ia_valid = 0; + gchar name[ TEXT_BUFFER_SIZE ]; + gchar value[ TEXT_BUFFER_SIZE ]; + guint8 password_hash[ KNX_KEY_LENGTH ]; + guint8 created_hash[ KNX_KEY_LENGTH ]; + gchar tag_name[ TEXT_BUFFER_SIZE ]; + guint8 tag_name_done = 0; + guint8 tag_end = 0; + guint8 in_tag = 0; + + memset( backbone_mca, 0, IPA_SIZE ); + *name = '\0'; + *value = '\0'; + memset( password_hash, 0, KNX_KEY_LENGTH ); + memset( created_hash, 0, KNX_KEY_LENGTH ); + *tag_name = '\0'; + + make_password_hash( password_hash, password ); + + g_debug( "%s:", key_file ); + + gint c = fgetc( f ); + + while( c >= 0 ) + { + if( c == '<' ) // tag start + { + in_tag = 1; + tag_end = 0; + *tag_name = 0; + tag_name_done = 0; + *name = '\0'; + *value = '\0'; + } + else if( c == '>' ) // tag end + { + in_tag = 0; + } + else if( c == '/' ) + { + if( in_tag ) // "</" or "/>" + { + tag_end = 1; + *tag_name = 0; + tag_name_done = 0; + *name = '\0'; + *value = '\0'; + } + } + else if( g_ascii_isalpha( c ) || c == '_' ) // possibly tag name, or attribute name + { + size_t length = 0; + name[ length++ ] = (gchar) c; + while( (c = fgetc( f )) >= 0 ) + { + if( g_ascii_isalnum( c ) || c == '_' ) + { + if( length < sizeof name + 1 ) + { + name[ length++ ] = (gchar) c; + } + } + else + { + break; + } + } + name[ length ] = '\0'; + *value = '\0'; + + if( !tag_name_done ) // tag name + { + g_snprintf( tag_name, sizeof tag_name, "%s", name ); + *name = '\0'; + tag_name_done = 1; + } + else // Check for name="value" construct + { + while( c >= 0 && g_ascii_isspace( c ) ) c = fgetc( f ); + + if( c == '=' ) + { + while( (c = fgetc( f )) >= 0 && g_ascii_isspace( c ) ); + + if( c == '"' ) + { + length = 0; + + while( (c = fgetc( f )) >= 0 ) + { + if( c == '"' ) + { + c = fgetc( f ); + if( c != '"' ) + { + break; + } + } + if( length < sizeof value - 1 ) + { + value[ length++ ] = (gchar) c; + } + } + + value[ length ] = 0; + + if( !tag_end ) + { + // Found name="value" construct between < and > + g_debug( "%s %s=%s", tag_name, name, value ); + + // Process name/value pair + if( strcmp( tag_name, "Keyring" ) == 0 ) + { + if( strcmp( name, "Created" ) == 0 ) + { + make_created_hash( created_hash, value ); + } + } + else if( strcmp( tag_name, "Backbone" ) == 0 ) + { + group_ga_valid = 0; + device_ia_valid = 0; + + if( strcmp( name, "MulticastAddress" ) == 0 ) + { + read_ip_addr( backbone_mca, value ); + backbone_mca_valid = 1; + } + else if( strcmp( name, "Key" ) == 0 ) + { + if( backbone_mca_valid ) + { + add_mca_key( backbone_mca, value, password_hash, created_hash, f2 ); + } + } + } + else if( strcmp( tag_name, "Group" ) == 0 ) + { + backbone_mca_valid = 0; + device_ia_valid = 0; + + if( strcmp( name, "Address" ) == 0 ) + { + group_ga = read_ga( value ); + group_ga_valid = 1; + } + else if( strcmp( name, "Key" ) == 0 ) + { + if( group_ga_valid ) + { + add_ga_key( group_ga, value, password_hash, created_hash, f2 ); + } + } + else if( strcmp( name, "Senders" ) == 0 ) + { + if( group_ga_valid ) + { + // Add senders given by space separated list of KNX IAs + static const gchar delim[] = " ,"; + const gchar* token = strtok( value, delim ); + while( token ) + { + add_ga_sender( group_ga, token, f2 ); + token = strtok( NULL, delim ); + } + } + } + } + else if( strcmp( tag_name, "Device" ) == 0 ) + { + backbone_mca_valid = 0; + group_ga_valid = 0; + + if( strcmp( name, "IndividualAddress" ) == 0 ) + { + device_ia = read_ia( value ); + device_ia_valid = 1; + } + else if( strcmp( name, "ToolKey" ) == 0 ) + { + if( device_ia_valid ) + { + add_ia_key( device_ia, value, password_hash, created_hash, f2 ); + } + } + else if( strcmp( name, "SequenceNumber" ) == 0 ) + { + if( device_ia_valid ) + { + add_ia_seq( device_ia, value, f2 ); + } + } + } + else + { + backbone_mca_valid = 0; + group_ga_valid = 0; + device_ia_valid = 0; + } + } + } + } + } + + if( c < 0 ) // EOF + { + break; + } + + continue; + } + else + { + if( !g_ascii_isspace( c ) ) + { + tag_name_done = 1; + *name = '\0'; + *value = '\0'; + } + } + + c = fgetc( f ); + } + + fclose( f ); + } + + if( f2 && f2 != stdout ) + { + fclose( f2 ); + } +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 2 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=2 tabstop=8 expandtab: + * :indentSize=2:tabSize=8:noTabs=true: + */ diff --git a/epan/dissectors/packet-knxip_decrypt.h b/epan/dissectors/packet-knxip_decrypt.h new file mode 100644 index 0000000000..af25b2a7c2 --- /dev/null +++ b/epan/dissectors/packet-knxip_decrypt.h @@ -0,0 +1,97 @@ +/* packet-knxip_decrypt.h + * Decryption keys and decryption functions for KNX/IP Dissector + * Copyright 2018, ise GmbH <Ralf.Nasilowski@ise.de> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef KNXIP_CRYPT_H +#define KNXIP_CRYPT_H + +#define KNX_KEY_LENGTH 16 + +// Calculate MAC for KNX IP Security or KNX Data Security +void knx_ccm_calc_cbc_mac( guint8 p_mac[ KNX_KEY_LENGTH ], const guint8 key[ KNX_KEY_LENGTH ], + const guint8* a_bytes, gint a_length, const guint8* p_bytes, gint p_length, + const guint8 b_0[ KNX_KEY_LENGTH ] ); + +// Calculate MAC for KNX IP Security +void knxip_ccm_calc_cbc_mac( guint8 p_mac[ KNX_KEY_LENGTH ], const guint8 key[ KNX_KEY_LENGTH ], + const guint8* a_bytes, gint a_length, const guint8* p_bytes, gint p_length, + const guint8* nonce, guint8 nonce_length ); + +// Encrypt for KNX IP Security or KNX Data Security +guint8* knx_ccm_encrypt( guint8* p_result, const guint8 key[ KNX_KEY_LENGTH ], const guint8* p_bytes, gint p_length, + const guint8* mac, guint8 mac_length, const guint8 ctr_0[ KNX_KEY_LENGTH ], guint8 s0_bytes_used_for_mac); + +// Encrypt for KNX IP Security +guint8* knxip_ccm_encrypt( guint8* p_result, const guint8 key[ KNX_KEY_LENGTH ], const guint8* p_bytes, gint p_length, + const guint8 mac[ KNX_KEY_LENGTH ], const guint8* nonce, guint8 nonce_length ); + +// Decrypt for KNX IP Security +guint8* knxip_ccm_decrypt( guint8* p_result, const guint8 key[ KNX_KEY_LENGTH ], const guint8* crypt, gint crypt_length, + const guint8* nonce, guint8 nonce_length ); + +// For importing keyring.XML file exported from ETS: + +struct knx_keyring_mca_keys +{ + struct knx_keyring_mca_keys* next; + guint8 mca[ 4 ]; // IP multicast address + guint8 key[ KNX_KEY_LENGTH ]; // encryption key +}; + +struct knx_keyring_ga_keys +{ + struct knx_keyring_ga_keys* next; + guint16 ga; // KNX GA + guint8 key[ KNX_KEY_LENGTH ]; // encryption key +}; + +struct knx_keyring_ga_senders +{ + struct knx_keyring_ga_senders* next; + guint16 ga; // KNX GA + guint16 ia; // sending KNX IA +}; + +struct knx_keyring_ia_keys +{ + struct knx_keyring_ia_keys* next; + guint16 ia; // KNX IA + guint8 key[ KNX_KEY_LENGTH ]; // encryption key +}; + +struct knx_keyring_ia_seqs +{ + struct knx_keyring_ia_seqs* next; + guint16 ia; // KNX IA + guint64 seq; // 6-byte sequence number +}; + +extern struct knx_keyring_mca_keys* knx_keyring_mca_keys; +extern struct knx_keyring_ga_keys* knx_keyring_ga_keys; +extern struct knx_keyring_ga_senders* knx_keyring_ga_senders; +extern struct knx_keyring_ia_keys* knx_keyring_ia_keys; +extern struct knx_keyring_ia_seqs* knx_keyring_ia_seqs; + +// Read KNX security keys from keyring XML file (exported from ETS) +void read_knx_keyring_xml_file( const gchar* key_file, const gchar* password, const gchar* key_info_file ); + +#endif // KNXIP_CRYPT_H + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 2 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=2 tabstop=8 expandtab: + * :indentSize=2:tabSize=8:noTabs=true: + */ diff --git a/epan/dissectors/packet-knxnetip.c b/epan/dissectors/packet-knxnetip.c deleted file mode 100644 index cb682dc85f..0000000000 --- a/epan/dissectors/packet-knxnetip.c +++ /dev/null @@ -1,1842 +0,0 @@ -/* packet-knxnetip.c - * Routines for KNXnet/IP dissection - * Copyright 2014, Alexander Gaertner <gaertner.alex@gmx.de> - * - * Wireshark - Network traffic analyzer - * By Gerald Combs <gerald@wireshark.org> - * Copyright 1998 Gerald Combs - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#include "config.h" -#include <epan/packet.h> -#include <epan/expert.h> - -#define KNXNETIP_PROTOCOL_VERSION 0x10 -#define KNXNETIP_HEADER_LENGTH 0x06 - -#define SEARCH_REQ 0x0201 -#define SEARCH_RES 0x0202 -#define DESCRIPTION_REQ 0x0203 -#define DESCRIPTION_RES 0x0204 -#define CONNECT_REQ 0x0205 -#define CONNECT_RES 0x0206 -#define CONNECTIONSTATE_REQ 0x0207 -#define CONNECTIONSTATE_RES 0x0208 -#define DISCONNECT_REQ 0x0209 -#define DISCONNECT_RES 0x020A -#define DEVICE_CONFIGURATION_REQ 0x0310 -#define DEVICE_CONFIGURATION_ACK 0x0311 -#define TUNNELLING_REQ 0x0420 -#define TUNNELLING_ACK 0x0421 -#define ROUTING_INDICATION 0x0530 -#define ROUTING_LOST 0x0531 -#define ROUTING_BUSY 0x0532 -#define REMOTE_DIAG_REQ 0x0740 -#define REMOTE_DIAG_RES 0x0741 -#define REMOTE_BASIC_CONF_REQ 0x0742 -#define REMOTE_RESET_REQ 0x0743 -#define DIB_DEVICE_INFO 0x01 -#define DIB_SUPP_SVC 0x02 -#define DIB_IP_CONF 0x03 -#define DIB_IP_CURRENT 0x04 -#define DIB_KNX_ADDRESS 0x05 -#define DIB_MFR_DATA 0xFE -#define KNX_TUNNEL_CONNECTION 0x04 -#define FLAGS_DEVICESTATUS_RESERVED 0xFE -#define FLAGS_DEVICESTATUS_PROGRAM 0x01 -#define FLAGS_IPCAPABILITES_RESERVED 0xF8 -#define FLAGS_IPCAPABILITES_BOOTIP 0x01 -#define FLAGS_IPCAPABILITES_DHCP 0x02 -#define FLAGS_IPCAPABILITES_AUTOIP 0x04 -#define FLAGS_DEVICESTATE_RESERVED 0xFC -#define FLAGS_DEVICESTATE_KNX 0x01 -#define FLAGS_DEVICESTATE_IP 0x02 -/*for CEMI*/ -#define RAW_REQ 0x10 -#define DATA_REQ 0x11 -#define POLL_DATA_REQ 0x13 -#define POLL_DATA_CON 0x25 -#define DATA_IND 0x29 -#define BUSMON_IND 0x2B -#define RAW_IND 0x2D -#define DATA_CON 0x2E -#define RAW_CON 0x2F -#define DATA_CONNEC_REQ 0x41 -#define DATA_INDV_REQ 0x4A -#define DATA_CONNEC_IND 0x89 -#define DATA_INDV_IND 0x94 -#define RESET_IND 0xF0 -#define RESET_REQ 0xF1 -#define PROPWRITE_CON 0xF5 -#define PROPWRITE_REQ 0xF6 -#define PROPINFO_IND 0xF7 -#define FUNCPROPCOM_REQ 0xF8 -#define FUNCPROPSTATREAD_REQ 0xF9 -#define FUNCPROPCOM_CON 0xFA -#define PROPREAD_CON 0xFB -#define PROPREAD_REQ 0xFC -#define PL_INFO 0x1 -#define RF_INFO 0x2 -#define BUSMON_INFO 0x3 -#define TIME_REL 0x4 -#define TIME_DELAY 0x5 -#define EXEND_TIME 0x6 -#define BIBAT_INFO 0x7 -#define RF_MULTI 0x8 -#define PREAMBEL 0x9 -#define RF_FAST_ACK 0xA -#define MANU_DATA 0xFE -#define RESER 0xFF -#define A_GROUPVALUE_RES 0x040 -#define A_GROUPVALUE_WRT 0x080 -#define A_ADC_RED 0x180 -#define A_ADC_RES 0x1C0 -#define A_MEM_RED 0x200 -#define A_MEM_RES 0x240 -#define A_MEM_WRT 0x280 -#define A_SYS_RED 0x1C8 -#define A_SYS_RES 0x1C9 -#define A_SYS_WRT 0x1CA -#define A_SYS_BROAD 0x1CB -#define GROUPADD 0x80 -#define COUPLER_SPECIFIC_SERVICE 0x3C0 -#define A_AUTHORIZE_REQ 0x3D1 -#define A_AUTHORIZE_RES 0x3D2 -#define A_KEY_WRT 0x3D3 -#define A_KEY_RES 0x3D4 -#define A_PROPVALUE_RED 0x3D5 -#define A_PROPVALUE_RES 0x3D6 - -#define FLAGS_CEMI_CONTROL1_FT 0x80 -#define FLAGS_CEMI_CONTROL1_R 0x20 -#define FLAGS_CEMI_CONTROL1_SB 0x10 -#define FLAGS_CEMI_CONTROL1_P 0x0C -#define FLAGS_CEMI_CONTROL1_A 0x02 -#define FLAGS_CEMI_CONTROL1_C 0x01 -#define FLAGS_CEMI_CONTROL2_AT 0x80 -#define FLAGS_CEMI_CONTROL2_HC 0x70 -#define FLAGS_CEMI_CONTROL2_EFF 0x0F -#define FLAGS_CEMI_RF_RESERVED 0xC0 -#define FLAGS_CEMI_RF_MESURE 0x30 -#define FLAGS_CEMI_RF_MESURE_RE 0x0C -#define FLAGS_CEMI_RF_BATTERY 0x02 -#define FLAGS_CEMI_RF_BIDIRETIONAL 0x01 -#define FLAGS_CEMI_BUS_F 0x80 -#define FLAGS_CEMI_BUS_B 0x40 -#define FLAGS_CEMI_BUS_P 0x20 -#define FLAGS_CEMI_BUS_D 0x10 -#define FLAGS_CEMI_BUS_L 0x08 -#define FLAGS_CEMI_BUS_SSS 0x07 -#define FLAGS_CEMI_FASTACK_CRC 0x400 -#define FLAGS_CEMI_FASTACK_ERROR 0x200 -#define FLAGS_CEMI_FASTACK_RES 0x100 -#define FLAGS_CEMI_FASTACK_INFO 0xFF - - -void proto_register_knxnetip(void); -void proto_reg_handoff_knxnetip(void); - -static int proto_knxnetip = -1; -static int hf_knxnetip_headerlength = -1; -static int hf_knxnetip_version = -1; -static int hf_knxnetip_servicetype = -1; -static int hf_knxnetip_totallength = -1; -static int hf_knxnetip_hpai = -1; -static int hf_knxnetip_hpai_structure_length = -1; -static int hf_knxnetip_hpai_host_protocol = -1; -static int hf_knxnetip_hpai_ip_address = -1; -static int hf_knxnetip_hpai_port = -1; -static int hf_knxnetip_dib = -1; -static int hf_knxnetip_structure_length = -1; -static int hf_knxnetip_dib_type = -1; -static int hf_knxnetip_dib_medium = -1; -static int hf_knxnetip_knxaddress = -1; -static int hf_knxnetip_dib_projectid = -1; -static int hf_knxnetip_dib_serialnumber = -1; -static int hf_knxnetip_dib_multicast_address = -1; -static int hf_knxnetip_mac_address = -1; -static int hf_knxnetip_dib_friendly = -1; -static int hf_knxnetip_dib_service = -1; -static int hf_knxnetip_dib_ipaddress = -1; -static int hf_knxnetip_dib_subnet = -1; -static int hf_knxnetip_dib_gateway = -1; -static int hf_knxnetip_dib_ipassign = -1; -static int hf_knxnetip_dib_dhcp = -1; -static int hf_knxnetip_dib_manuid = -1; -static int hf_knxnetip_dib_manudata = -1; -static int hf_knxnetip_cri = -1; -static int hf_knxnetip_connection_type = -1; -static int hf_knxnetip_cri_protocol_data = -1; -static int hf_knxnetip_communication_channel_id = -1; -static int hf_knxnetip_crd_protocol_data = -1; -static int hf_knxnetip_crd = -1; -static int hf_knxnetip_connect_status = -1; -static int hf_knxnetip_connectionstate_status = -1; -static int hf_knxnetip_counter = -1; -static int hf_knxnetip_confack_status = -1; -static int hf_knxnetip_tunnelack_status = -1; -static int hf_knxnetip_numberoflost = -1; -static int hf_knxnetip_busywaittime = -1; -static int hf_knxnetip_busycontrol = -1; -static int hf_knxnetip_knxlayer = -1; -static int hf_knxnetip_selector_type = -1; -static int hf_knxnetip_reset = -1; -static int hf_knxnetip_projectnumber = -1; -static int hf_knxnetip_installnumber = -1; -static int hf_knxnetip_dib_svc_version = -1; -static int hf_knxnetip_reserved = -1; -static int hf_knxnetip_raw = -1; -static int hf_knxnetip_data = -1; -static int hf_knxnetip_additional = -1; -static int hf_knxnetip_unknown = -1; -static int hf_knxnetip_polldata = -1; - - -static int hf_knxnetip_cemi = -1; -static int hf_knxnetip_cemi_mc = -1; -static int hf_knxnetip_cemi_addlength = -1; -static int hf_knxnetip_cemi_additemlength = -1; -static int hf_knxnetip_cemi_typid = -1; -static int hf_knxnetip_cemi_type_pl = -1; -static int hf_knxnetip_cemi_type_relt = -1; -static int hf_knxnetip_cemi_type_delay = -1; -static int hf_knxnetip_cemi_type_exttime = -1; -static int hf_knxnetip_cemi_type_bibat = -1; -static int hf_knxnetip_cemi_sourceaddress = -1; -static int hf_knxnetip_cemi_destaddress = -1; -static int hf_knxnetip_cemi_tpci = -1; -static int hf_knxnetip_cemi_counter = -1; -static int hf_knxnetip_cemi_npdu_length = -1; -static int hf_knxnetip_cemi_tpdu_length = -1; -static int hf_knxnetip_cemi_apci = -1; -static int hf_knxnetip_cemi_data = -1; -static int hf_knxnetip_cemi_numberofslots = -1; -static int hf_knxnetip_cemi_iot = -1; -static int hf_knxnetip_cemi_oi = -1; -static int hf_knxnetip_cemi_six = -1; -static int hf_knxnetip_cemi_pid = -1; -static int hf_knxnetip_cemi_reserved = -1; -static int hf_knxnetip_cemi_noe = -1; -static int hf_knxnetip_cemi_error = -1; -static int hf_knxnetip_cemi_return = -1; -static int hf_knxnetip_cemi_numberofelements = -1; -static int hf_knxnetip_cemi_apci_memory_number = -1; -static int hf_knxnetip_cemi_rf_lfn = -1; -static int hf_knxnetip_cemi_type_bibat_block = -1; -static int hf_knxnetip_cemi_type_rf_multi_fastack = -1; -static int hf_knxnetip_cemi_type_rf_multi_freq = -1; -static int hf_knxnetip_cemi_type_rf_multi_channel = -1; -static int hf_knxnetip_cemi_type_rf_multi_recep_freq = -1; -static int hf_knxnetip_cemi_rf_sn = -1; -static int hf_knxnetip_cemi_type_preamble_length = -1; -static int hf_knxnetip_cemi_type_postamble_length = -1; -static int hf_knxnetip_cemi_subfunction = -1; -static int hf_knxnetip_cemi_manuspecificdata = -1; -static int hf_knxnetip_cemi_apci_mem_address = -1; -static int hf_knxnetip_cemi_channel = -1; -static int hf_knxnetip_cemi_apci_key = -1; -static int hf_knxnetip_cemi_apci_level = -1; -static int hf_knxnetip_cemi_apci_object = -1; -static int hf_knxnetip_cemi_apci_propid = -1; - - -/*FLAGS -DIB Device Status Flags*/ -static int hf_knxnetip_dib_status = -1; -static int hf_knxnetip_dib_status_flag_reserved = -1; -static int hf_knxnetip_dib_status_flag_program = -1; -static const int *dib_device_status_flags[] = { - &hf_knxnetip_dib_status_flag_reserved, - &hf_knxnetip_dib_status_flag_program, - NULL -}; -/*DIB IP Capabilities Flags*/ -static int hf_knxnetip_dib_ipcapa = -1; -static int hf_knxnetip_dib_ipcapa_flag_bootip = -1; -static int hf_knxnetip_dib_ipcapa_flag_dhcp = -1; -static int hf_knxnetip_dib_ipcapa_flag_autoip = -1; -static int hf_knxnetip_dib_ipcapa_flag_reserved = -1; -static const int *dib_ipcapabilities_flags[] = { - &hf_knxnetip_dib_ipcapa_flag_bootip, - &hf_knxnetip_dib_ipcapa_flag_dhcp, - &hf_knxnetip_dib_ipcapa_flag_autoip, - &hf_knxnetip_dib_ipcapa_flag_reserved, - NULL -}; -/*Device State*/ -static int hf_knxnetip_devicestate = -1; -static int hf_knxnetip_devicestate_reserved = -1; -static int hf_knxnetip_devicestate_knx = -1; -static int hf_knxnetip_devicestate_ip = -1; -static const int *devicestate_flags[] = { - &hf_knxnetip_devicestate_knx, - &hf_knxnetip_devicestate_ip, - &hf_knxnetip_devicestate_reserved, - NULL -}; -/*cEMI FLAGS -controlfield 1*/ -static int hf_knxnetip_cemi_controlfield1 = -1; -static int hf_knxnetip_cemi_flag_frametype = -1; -static int hf_knxnetip_cemi_flag_repeat = -1; -static int hf_knxnetip_cemi_flag_sb = -1; -static int hf_knxnetip_cemi_flag_priority = -1; -static int hf_knxnetip_cemi_flag_ack = -1; -static int hf_knxnetip_cemi_flag_confirm = -1; -static const int *cemi_control1_flags[] = { - &hf_knxnetip_cemi_flag_frametype, - &hf_knxnetip_cemi_flag_repeat, - &hf_knxnetip_cemi_flag_sb, - &hf_knxnetip_cemi_flag_priority, - &hf_knxnetip_cemi_flag_ack, - &hf_knxnetip_cemi_flag_confirm, - NULL -}; -/*controlfield 2*/ -static int hf_knxnetip_cemi_controlfield2 = -1; -static int hf_knxnetip_flag_destaddress = -1; -static int hf_knxnetip_flag_hop = -1; -static int hf_knxnetip_flag_eff = -1; -static const int *cemi_control2_flags[] = { - &hf_knxnetip_flag_destaddress, - &hf_knxnetip_flag_hop, - &hf_knxnetip_flag_eff, - NULL -}; - -static int hf_knxnetip_cemi_type_rf_info = -1; -static int hf_knxnetip_cemi_type_rf_reserved = -1; -static int hf_knxnetip_cemi_type_rf_mesure = -1; -static int hf_knxnetip_cemi_type_rf_mesure_re = -1; -static int hf_knxnetip_cemi_type_rf_battery = -1; -static int hf_knxnetip_cemi_type_rf_bidirekt = -1; -static const int *cemi_rf_info[] = { - &hf_knxnetip_cemi_type_rf_reserved, - &hf_knxnetip_cemi_type_rf_mesure, - &hf_knxnetip_cemi_type_rf_mesure_re, - &hf_knxnetip_cemi_type_rf_battery, - &hf_knxnetip_cemi_type_rf_bidirekt, - NULL -}; - -static int hf_knxnetip_cemi_type_bus = -1; -static int hf_knxnetip_cemi_type_bus_flag_f = -1; -static int hf_knxnetip_cemi_type_bus_flag_b = -1; -static int hf_knxnetip_cemi_type_bus_flag_p = -1; -static int hf_knxnetip_cemi_type_bus_flag_d = -1; -static int hf_knxnetip_cemi_type_bus_flag_l = -1; -static int hf_knxnetip_cemi_type_bus_flag_sss = -1; -static const int *cemi_bus_flags[] = { - &hf_knxnetip_cemi_type_bus_flag_f, - &hf_knxnetip_cemi_type_bus_flag_b, - &hf_knxnetip_cemi_type_bus_flag_p, - &hf_knxnetip_cemi_type_bus_flag_d, - &hf_knxnetip_cemi_type_bus_flag_l, - &hf_knxnetip_cemi_type_bus_flag_sss, - NULL -}; - -static int hf_knxnetip_cemi_type_fastack = -1; -static int hf_knxnetip_cemi_type_fastack_crc = -1; -static int hf_knxnetip_cemi_type_fastack_error = -1; -static int hf_knxnetip_cemi_type_fastack_received = -1; -static int hf_knxnetip_cemi_type_fastack_info = -1; -static const int *cemi_fastack_flags[] = { - &hf_knxnetip_cemi_type_fastack_crc, - &hf_knxnetip_cemi_type_fastack_error, - &hf_knxnetip_cemi_type_fastack_received, - &hf_knxnetip_cemi_type_fastack_info, - NULL -}; - - -static const value_string knxnetip_service_identifier[] = { - { SEARCH_REQ, "SEARCH_REQUEST" }, - { SEARCH_RES, "SEARCH_RESPONSE" }, - { DESCRIPTION_REQ, "DESCRIPTION_REQUEST" }, - { DESCRIPTION_RES, "DESCRIPTION_RESPONSE" }, - { CONNECT_REQ, "CONNECT_REQUEST" }, - { CONNECT_RES, "CONNECT_RESPONSE" }, - { CONNECTIONSTATE_REQ, "CONNECTIONSTATE_REQUEST" }, - { CONNECTIONSTATE_RES, "CONNECTIONSTATE_RESPONSE" }, - { DISCONNECT_REQ, "DISCONNECT_REQUEST" }, - { DISCONNECT_RES, "DISCONNECT_RESPONSE" }, - { DEVICE_CONFIGURATION_REQ, "DEVICE_CONFIGURATION_REQUEST" }, - { DEVICE_CONFIGURATION_ACK, "DEVICE_CONFIGURATION_ACK" }, - { TUNNELLING_REQ, "TUNNELLING_REQUEST" }, - { TUNNELLING_ACK, "TUNNELING_ACK" }, - { ROUTING_INDICATION, "ROUTING_INDICATION" }, - { ROUTING_LOST, "ROUTING_LOST_MESSAGE" }, - { ROUTING_BUSY, "ROUTING_BUSY" }, - { REMOTE_DIAG_REQ, "REMOTE_DIAGNOSTIC_REQUEST" }, - { REMOTE_DIAG_RES, "REMOTE_DIAGNOSTIC_RESPONSE" }, - { REMOTE_BASIC_CONF_REQ, "REMOTE_BASIC_CONFIGURATION_REQUEST" }, - { REMOTE_RESET_REQ, "REMOTE_RESET_REQUEST" }, - { 0, NULL } -}; - - -static const value_string knxnetip_service_types[] = { - { 0x02, "KNXnet/IP Core" }, - { 0x03, "KNXnet/IP Device Management" }, - { 0x04, "KNXnet/IP Tunneling" }, - { 0x05, "KNXnet/IP Routing" }, - { 0x06, "KNXnet/IP Remote Logging" }, - { 0x07, "KNXnet/IP Remote Configuration and Diagnosis" }, - { 0x08, "KNXnet/IP Object Server" }, - { 0, NULL } -}; - -static const value_string knxnetip_connection_types[] = { - { 0x03, "DEVICE_MGMT_CONNECTION" }, - { 0x04, "TUNNEL_CONNECTION" }, - { 0x06, "REMLOG_CONNECTION" }, - { 0x07, "REMCONF_CONNECTION" }, - { 0x08, "OBJSVR_CONNECTION" }, - { 0, NULL } -}; - - -static const value_string knxnetip_connect_response_status_codes[] = { - { 0x00, "E_NO_ERROR - The connection was established successfully" }, - { 0x22, "E_CONNECTION_TYPE - The KNXnet/IP server device does not support the requested connection type" }, - { 0x23, "E_CONNECTION_OPTION - The KNXnet/IP server device does not support one or more requested connection options" }, - { 0x24, "E_NO_MORE_CONNECTIONS - The KNXnet/IP server device could not accept the new data connection (busy)" }, - { 0, NULL } -}; - -static const value_string knxnetip_connectionstate_response_status_codes[] = { - { 0x00, "E_NO_ERROR - The connection state is normal" }, - { 0x21, "E_CONNECTION_ID - The KNXnet/IP server device could not find an active data connection with the specified ID" }, - { 0x26, "E_DATA_CONNECTION - The KNXnet/IP server device detected an error concerning the data connection with the specified ID" }, - { 0x27, "E_KNX_CONNECTION - The KNXnet/IP server device detected an error concerning the EIB bus / KNX subsystem connection with the specified ID" }, - { 0, NULL } -}; - -static const value_string knxnetip_tunneling_error_codes[] = { - { 0x00, "E_NO_ERROR - The message was received successfully" }, - { 0x29, "E_TUNNELLING_LAYER - The KNXnet/IP server device does not support the requested tunnelling layer" }, - { 0, NULL } -}; - -static const value_string knxnetip_device_configuration_ack_status_codes[] = { - { 0x00, "E_NO_ERROR - The message was received successfully" }, - { 0, NULL } -}; - -static const value_string knxnetip_dib_description_type_codes[] = { - { DIB_DEVICE_INFO, "DEVICE_INFO" }, - { DIB_SUPP_SVC, "SUPP_SVC_FAMILIES" }, - { DIB_IP_CONF, "IP_CONFIG" }, - { DIB_IP_CURRENT, "IP_CUR_CONFIG" }, - { DIB_KNX_ADDRESS, "KNX_ADDRESSES" }, - { DIB_MFR_DATA, "MFR_DATA" }, - { 0, NULL } -}; - -static const value_string knxnetip_dib_medium_codes[] = { - { 0x01, "reserved" }, - { 0x02, "KNX TP" }, - { 0x04, "KNX PL110" }, - { 0x08, "reserved" }, - { 0x10, "KNX RF" }, - { 0x20, "KNX IP" }, - { 0, NULL } -}; - -static const value_string knxnetip_host_protocol_codes[] = { - { 0x01, "IPV4_UDP" }, - { 0x02, "IPV4_TCP" }, - { 0, NULL } -}; - -static const value_string knxnetip_ip_assignment_method[] = { - { 0x01, "manuell" }, - { 0x02, "BootP" }, - { 0x04, "DHCP" }, - { 0x08, "AutoIP" }, - { 0, NULL } -}; - -static const value_string knxnetip_knxlayer_values[] = { - { 0x02, "TUNNEL_LINKLAYER" }, - { 0x04, "TUNNEL_RAW"}, - { 0x80, "TUNNEL_BUSMONITOR"}, - { 0, NULL} -}; - -static const value_string knxnetip_selector_types[] = { - { 0x01, "PrgMode Selector" }, - { 0x02, "MAC Selector" }, - { 0, NULL } -}; - -static const value_string knxnetip_reset_codes[] = { - { 0x01, "Restart" }, - { 0x02, "Master Reset" }, - { 0, NULL } -}; - -/*for CEMI*/ -static const value_string cemi_messagecodes[] = { - { RAW_REQ, "L_Raw.req"}, - { DATA_REQ, "L_Data.req"}, - { POLL_DATA_REQ, "L_Poll_Data.req"}, - { POLL_DATA_CON, "L_Poll_Data.con"}, - { DATA_IND, "L_Data.ind"}, - { BUSMON_IND, "L_Busmon.ind"}, - { RAW_IND, "L_Raw.ind"}, - { DATA_CON, "L_Data.con"}, - { RAW_CON, "L_Raw.con"}, - { DATA_CONNEC_REQ, "T_Data_Connected.req"}, - { DATA_INDV_REQ, "T_Data_Individual.req"}, - { DATA_CONNEC_IND, "T_Data_Connected.ind"}, - { DATA_INDV_IND, "T_Data_Individual.ind"}, - { RESET_IND, "M_Reset.ind"}, - { RESET_REQ, "M_Reset.req"}, - { PROPWRITE_CON, "M_PropWrite.con"}, - { PROPWRITE_REQ, "M_PropWrite.req"}, - { PROPINFO_IND, "M_PropInfo.ind"}, - { FUNCPROPCOM_REQ, "M_FuncPropCommand.req"}, - { FUNCPROPSTATREAD_REQ, "M_FuncPropStateRead.req"}, - { FUNCPROPCOM_CON, "M_FuncPropCommand/StateRead.con"}, - { PROPREAD_CON, "M_PropRead.con"}, - { PROPREAD_REQ, "M_PropRead.req"}, - { 0, NULL } -}; - -static const value_string cemi_add_type_id[] = { - { 0x00, "reserved" }, - { PL_INFO, "PL Info"}, - { RF_INFO, "RF Info"}, - { BUSMON_INFO, "Busmonitor Info"}, - { TIME_REL, "relative timestamp"}, - { TIME_DELAY, "time delay until send"}, - { EXEND_TIME, "extended relative timestamp"}, - { BIBAT_INFO, "BiBat information"}, - { RF_MULTI, "RF Multi information"}, - { PREAMBEL, "Preamble and postamble"}, - { RF_FAST_ACK, "RF Fast Ack information"}, - { MANU_DATA, "Manufacturer specific data"}, - { RESER, "reserved"}, - { 0, NULL} -}; - -static const value_string cemi_tpci_vals[] = { - { 0x0, "UDT (Unnumbered Data Packet)" }, - { 0x2, "UCD (Unnumbered)"}, - { 0x1, "NDT (Numbered Data Packet)"}, - { 0x3, "NCD (Numbered Control Data)"}, - { 0, NULL} -}; - -static const value_string cemi_apci_codes[] = { - { 0x000, "A_GroupValue_Read" }, - { 0x001, "A_GroupValue_Response"}, - { 0x002, "A_GroupValue_Write"}, - { 0x0C0, "A_IndividualAddress_Write"}, - { 0x100, "A_IndividualAddress_Read"}, - { 0x140, "A_IndividualAddress_Response"}, - { 0x006, "A_ADC_Read"}, - { 0x1C0, "A_ADC_Response"}, - { 0x1C4, "A_SystemNetworkParameter_Read"}, - { 0x1C9, "A_SystemNetworkParameter_Response"}, - { 0x1CA, "A_SystemNetworkParameter_Write"}, - { 0x020, "A_Memory_Read"}, - { 0x024, "A_Memory_Response"}, - { 0x028, "A_Memory_Write"}, - { 0x2C0, "A_UserMemory_Read"}, - { 0x2C1, "A_UserMemory_Response"}, - { 0x2C2, "A_UserMemory_Write"}, - { 0x2C5, "A_UserManufacturerInfo_Read"}, - { 0x2C6, "A_UserManufacturerInfo_Response"}, - { 0x2C7, "A_FunctionPropertyCommand"}, - { 0x2C8, "A_FunctionPropertyState_Read"}, - { 0x2C9, "A_FunctionPropertyState_Response"}, - { 0x300, "A_DeviceDescriptor_Read"}, - { 0x340, "A_DeviceDescriptor_Response"}, - { 0x380, "A_Restart"}, - { 0x3D1, "A_Authorize_Request"}, - { 0x3D2, "A_Authorize_Response"}, - { 0x3D3, "A_Key_Write"}, - { 0x3D4, "A_Key_Response"}, - { 0x3D5, "A_PropertyValue_Read"}, - { 0x3D6, "A_PropertyValue_Response"}, - { 0x3D7, "A_PropertyValue_Write"}, - { 0x3D8, "A_PropertyDescription_Read"}, - { 0x3D9, "A_PropertyDescription_Response"}, - { 0x3DA, "A_NetworkParameter_Read"}, - { 0x3DB, "A_NetworkParameter_Response"}, - { 0x3DC, "A_IndividualAddressSerialNumber_Read"}, - { 0x3DD, "A_IndividualAddressSerialNumber_Response"}, - { 0x3DF, "A_IndividualAddressSerialNumber_Write"}, - { 0x3E0, "A_DomainAddress_Write"}, - { 0x3E1, "A_DomainAddress_Read"}, - { 0x3E2, "A_DomainAddress_Response"}, - { 0x3E3, "A_DomainAddressSelective_Read"}, - { 0x3E4, "A_NetworkParameter_Write"}, - { 0x3E5, "A_Link_Read"}, - { 0x3E6, "A_Link_Response"}, - { 0x3E7, "A_Link_Write"}, - { 0x3E8, "A_GroupPropValue_Read"}, - { 0x3E9, "A_GroupPropValue_Response"}, - { 0x3EA, "A_GroupPropValue_Write"}, - { 0x3EB, "A_GroupPropValue_InfoReport"}, - { 0x3EC, "A_DomainAddressSerialNumber_Read"}, - { 0x3ED, "A_DomainAddressSerialNumber_Response"}, - { 0x3EE, "A_DomainAddressSerialNumber_Write"}, - { 0x3F0, "A_FileStream_InforReport"}, - { 0, NULL} -}; - -static const value_string cemi_propertyid[] = { - { 1, "PID_OBJECT_TYPE" }, - { 8, "PID_SERVICE_CONTROL" }, - { 9, "PID_FIRMWARE_REVISION" }, - { 11, "PID_SERIAL_NUMBER" }, - { 12, "PID_MANUFACTURER_ID" }, - { 14, "PID_DEVICE_CONTROL" }, - { 19, "PID_MANUFACTURE_DATA" }, - { 51, "PID_ROUTING_COUNT" }, - { 52, "PID_MAX_RETRY_COUNT " }, - { 53, "PID_ERROR_FLAGS" }, - { 54, "PID_PROGMODE" }, - { 56, "PID_MAX_APDULENGTH" }, - { 57, "PID_SUBNET_ADDR" }, - { 58, "PID_DEVICE_ADDR" }, - { 59, "PID_PB_CONFIG" }, - { 60, "PID_ADDR_REPORT" }, - { 61, "PID_ADDR_CHECK" }, - { 62, "PID_OBJECT_VALUE" }, - { 63, "PID_OBJECTLINK" }, - { 64, "PID_APPLICATION" }, - { 65, "PID_PARAMETER" }, - { 66, "PID_OBJECTADDRESS" }, - { 67, "PID_PSU_TYPE" }, - { 68, "PID_PSU_STATUS" }, - { 70, "PID_DOMAIN_ADDR"}, - { 71, "PID_IO_LIST"}, - { 0, NULL } -}; - -static const value_string cemi_error_codes[] = { - { 0x00, "Unspecified Error"}, - { 0x01, "Out of range"}, - { 0x02, "Out of maxrange"}, - { 0x03, "Out of minrange"}, - { 0x04, "Memory Error"}, - { 0x05, "Read only"}, - { 0x06, "Illegal command"}, - { 0x07, "Void DP"}, - { 0x08, "Type conflict"}, - { 0x09, "Prop. Index range error"}, - { 0x0A, "Value temporarily not writeable"}, - { 0, NULL } -}; - -static const value_string cemi_bibat_ctrl[] = { - { 0x0, "asynchr. RF frame"}, - { 0x1, "Fast_ACK"}, - { 0x4, "synchronous L_Data frames"}, - { 0x5, "Sync frame"}, - { 0x6, "Help Call"}, - { 0x7, "Help Call Response"}, - { 0, NULL } -}; - -static gint ett_knxnetip = -1; -static gint ett_knxnetip_header = -1; -static gint ett_knxnetip_body = -1; -static gint ett_knxnetip_hpai = -1; -static gint ett_knxnetip_dib = -1; -static gint ett_knxnetip_dib_projectid = -1; -static gint ett_knxnetip_dib_service = -1; -static gint ett_knxnetip_cri = -1; -static gint ett_knxnetip_crd = -1; -static gint ett_knxnetip_dib_status = -1; -static gint ett_knxnetip_dib_ipcapa = -1; -static gint ett_knxnetip_devicestate = -1; -static gint ett_knxnetip_cemi = -1; -static gint ett_knxnetip_cemi_additional = -1; -static gint ett_knxnetip_cemi_additional_item = -1; -static gint ett_knxnetip_cemi_control1 = -1; -static gint ett_knxnetip_cemi_control2 = -1; -static gint ett_knxnetip_cemi_rf_info = -1; -static gint ett_knxnetip_cemi_bus_info = -1; -static gint ett_knxnetip_cemi_fastack = -1; - -static expert_field ei_knxnetip_length = EI_INIT; - -static void dissect_hpai(tvbuff_t *tvb, guint32 *offset, proto_tree *insert_tree, const char *append_text) { - - proto_item *hpai_item; - proto_tree *hpai_tree; - - hpai_item = proto_tree_add_item( insert_tree, hf_knxnetip_hpai, tvb, *offset, 8, ENC_NA ); - hpai_tree = proto_item_add_subtree(hpai_item, ett_knxnetip_hpai); - proto_item_append_text(hpai_item, "%s", append_text); - proto_tree_add_item(hpai_tree, hf_knxnetip_hpai_structure_length, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(hpai_tree, hf_knxnetip_hpai_host_protocol, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(hpai_tree, hf_knxnetip_hpai_ip_address, tvb, *offset, 4, ENC_BIG_ENDIAN); - *offset+=4; - proto_tree_add_item(hpai_tree, hf_knxnetip_hpai_port, tvb, *offset, 2, ENC_BIG_ENDIAN); - *offset+=2; - -} - -static gboolean dissect_dib(tvbuff_t *tvb, guint32 *offset, proto_tree *insert_tree) { - - proto_item *dib_item = NULL; - proto_item *projectid_item = NULL; - proto_item *service_item = NULL; - - proto_tree *dib_tree = NULL; - proto_tree *projectid_tree = NULL; - proto_tree *service_tree = NULL; - - guint8 i; - guint8 dib_type; - guint8 length; - guint16 knx_address; - guint16 install_id; - - length = tvb_get_guint8(tvb, *offset); - dib_item = proto_tree_add_item(insert_tree, hf_knxnetip_dib, tvb, *offset, length, ENC_NA); - dib_tree = proto_item_add_subtree(dib_item, ett_knxnetip_dib); - proto_tree_add_item(dib_tree, hf_knxnetip_structure_length, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(dib_tree, hf_knxnetip_dib_type, tvb, *offset, 1, ENC_BIG_ENDIAN); - dib_type = tvb_get_guint8(tvb, *offset); - proto_item_append_text(dib_item, ": %s", val_to_str_const(dib_type, knxnetip_dib_description_type_codes, "Unknown Type")); - *offset+=1; - - switch (dib_type){ - - case(DIB_DEVICE_INFO): - proto_tree_add_item(dib_tree, hf_knxnetip_dib_medium, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_bitmask(dib_tree, tvb, *offset, hf_knxnetip_dib_status, ett_knxnetip_dib_status, dib_device_status_flags, ENC_BIG_ENDIAN); - *offset+=1; - knx_address = tvb_get_ntohs(tvb, *offset); - proto_tree_add_uint_format(dib_tree, hf_knxnetip_knxaddress, tvb, *offset, 2, knx_address, "KNX Address %d.%d.%d", ((knx_address & 0xF000)>>12),((knx_address & 0x0F00)>>8),(knx_address & 0xFF)); - *offset+=2; - projectid_item = proto_tree_add_item(dib_tree, hf_knxnetip_dib_projectid, tvb, *offset, 2, ENC_BIG_ENDIAN); - projectid_tree = proto_item_add_subtree(projectid_item, ett_knxnetip_dib_projectid); - install_id = tvb_get_ntohs(tvb, *offset); - proto_tree_add_uint_format(projectid_tree, hf_knxnetip_projectnumber, tvb, *offset, 2, install_id, "Project number %d", (install_id & 0xFFF0)>>4); - proto_tree_add_uint_format(projectid_tree, hf_knxnetip_installnumber, tvb, *offset, 2, install_id, "Installation number %d", (install_id & 0xF)); - *offset+=2; - proto_tree_add_item(dib_tree, hf_knxnetip_dib_serialnumber, tvb, *offset, 6, ENC_NA); - *offset+=6; - proto_tree_add_item(dib_tree, hf_knxnetip_dib_multicast_address, tvb, *offset, 4, ENC_BIG_ENDIAN); - *offset+=4; - proto_tree_add_item(dib_tree, hf_knxnetip_mac_address, tvb, *offset, 6, ENC_NA); - *offset+=6; - proto_tree_add_item(dib_tree, hf_knxnetip_dib_friendly, tvb, *offset, 30, ENC_ASCII|ENC_NA ); - *offset+=30; - break; - - case(DIB_SUPP_SVC): - if (length > 4) { - length-=4; - } else { - return TRUE; - } - - for (i = 0; i <= length; i+=2) { - service_item = proto_tree_add_item(dib_tree, hf_knxnetip_dib_service, tvb, *offset, 1, ENC_BIG_ENDIAN); - service_tree = proto_item_add_subtree(service_item, ett_knxnetip_dib_service); - *offset+=1; - proto_tree_add_item(service_tree, hf_knxnetip_dib_svc_version, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - } - break; - - case(DIB_IP_CONF): - proto_tree_add_item(dib_tree, hf_knxnetip_dib_ipaddress, tvb, *offset, 4, ENC_BIG_ENDIAN); - *offset+=4; - proto_tree_add_item(dib_tree, hf_knxnetip_dib_subnet, tvb, *offset, 4, ENC_BIG_ENDIAN); - *offset+=4; - proto_tree_add_item(dib_tree, hf_knxnetip_dib_gateway, tvb, *offset, 4, ENC_BIG_ENDIAN); - *offset+=4; - proto_tree_add_bitmask(dib_tree, tvb, *offset, hf_knxnetip_dib_ipcapa, ett_knxnetip_dib_ipcapa, dib_ipcapabilities_flags, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(dib_tree, hf_knxnetip_dib_ipassign, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - break; - - case(DIB_IP_CURRENT): - proto_tree_add_item(dib_tree, hf_knxnetip_dib_ipaddress, tvb, *offset, 4, ENC_BIG_ENDIAN); - *offset+=4; - proto_tree_add_item(dib_tree, hf_knxnetip_dib_subnet, tvb, *offset, 4, ENC_BIG_ENDIAN); - *offset+=4; - proto_tree_add_item(dib_tree, hf_knxnetip_dib_gateway, tvb, *offset, 4, ENC_BIG_ENDIAN); - *offset+=4; - proto_tree_add_item(dib_tree, hf_knxnetip_dib_dhcp, tvb, *offset, 4, ENC_BIG_ENDIAN); - *offset+=4; - proto_tree_add_item(dib_tree, hf_knxnetip_dib_ipassign, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(dib_tree, hf_knxnetip_reserved, tvb, *offset, 1, ENC_NA); - *offset+=1; - break; - - case(DIB_KNX_ADDRESS): - if (length > 4) { - length-=4; - } else { - return TRUE; - } - - for (i = 0; i <= length; i+=2) { - knx_address = tvb_get_ntohs(tvb, *offset); - proto_tree_add_uint_format(dib_tree, hf_knxnetip_knxaddress, tvb, *offset, 2, knx_address, "KNX Address %d.%d.%d", ((knx_address & 0xF000)>>12),((knx_address & 0x0F00)>>8),(knx_address & 0xFF)); - *offset+=2; - } - break; - - case(DIB_MFR_DATA): - if (length > 4) { - length-=4; - } else { - return TRUE; - } - proto_tree_add_item(dib_tree, hf_knxnetip_dib_manuid, tvb, *offset, 2, ENC_BIG_ENDIAN); - *offset+=2; - proto_tree_add_item(dib_tree, hf_knxnetip_dib_manudata, tvb, *offset, length, ENC_ASCII|ENC_NA); - *offset+=length; - break; - } - - return FALSE; -} - -static guint dissect_cri(tvbuff_t *tvb, guint32 offset, proto_tree *insert_tree) { - - proto_item *cri_item = NULL; - proto_tree *cri_tree = NULL; - - guint8 length; - - length = tvb_get_guint8(tvb ,offset); - cri_item = proto_tree_add_item(insert_tree, hf_knxnetip_cri, tvb, offset, length, ENC_NA); - cri_tree = proto_item_add_subtree(cri_item, ett_knxnetip_cri); - - proto_tree_add_item(cri_tree, hf_knxnetip_structure_length, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - proto_tree_add_item(cri_tree, hf_knxnetip_connection_type, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - if (tvb_get_guint8(tvb,offset-1)== KNX_TUNNEL_CONNECTION ){ - proto_tree_add_item(cri_tree, hf_knxnetip_knxlayer, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - proto_tree_add_item(cri_tree, hf_knxnetip_reserved, tvb, offset, 1, ENC_NA); - offset+=1; - } - else if (length > 2) { - proto_tree_add_item(cri_tree, hf_knxnetip_cri_protocol_data, tvb, offset, (length-2), ENC_NA); - offset+=(length-2); - } - return offset; -} - -static void dissect_crd(tvbuff_t *tvb, guint32 *offset, proto_tree *insert_tree) { - - proto_item *crd_item = NULL; - proto_tree *crd_tree = NULL; - - guint8 length; - guint16 knx_address; - - length = tvb_get_guint8(tvb, *offset); - crd_item = proto_tree_add_item(insert_tree, hf_knxnetip_crd, tvb, *offset, length, ENC_NA); - crd_tree = proto_item_add_subtree(crd_item, ett_knxnetip_crd); - - proto_tree_add_item(crd_tree, hf_knxnetip_structure_length, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(crd_tree, hf_knxnetip_connection_type, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - if (tvb_get_guint8(tvb, *offset-1) == KNX_TUNNEL_CONNECTION){ - knx_address = tvb_get_ntohs(tvb, *offset); - proto_tree_add_uint_format(crd_tree, hf_knxnetip_knxaddress, tvb, *offset, 2, knx_address, "KNX Address %d.%d.%d", ((knx_address & 0xF000)>>12),((knx_address & 0x0F00)>>8),(knx_address & 0xFF)); - *offset+=2; - } - else if (length > 2) { - proto_tree_add_item(crd_tree, hf_knxnetip_crd_protocol_data, tvb, *offset, (length-2), ENC_NA); - *offset+=(length-2); - } -} - -static guint dissect_connection_header(tvbuff_t *tvb, guint32 offset, proto_tree *insert_tree, gboolean have_status) { - - proto_tree_add_item(insert_tree, hf_knxnetip_structure_length, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - proto_tree_add_item(insert_tree, hf_knxnetip_communication_channel_id, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - proto_tree_add_item(insert_tree, hf_knxnetip_counter, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - if (have_status == FALSE){ - proto_tree_add_item(insert_tree, hf_knxnetip_reserved, tvb, offset, 1, ENC_NA); - offset+=1; - } - - return offset; -} - -static guint dissect_selector(tvbuff_t *tvb, guint32 offset, proto_tree *insert_tree){ - - proto_tree_add_item(insert_tree, hf_knxnetip_structure_length, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - proto_tree_add_item(insert_tree, hf_knxnetip_selector_type, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - if (tvb_get_guint8(tvb, offset-1)==0x02){ - proto_tree_add_item(insert_tree, hf_knxnetip_mac_address, tvb, offset, 6, ENC_NA); - offset+=6; - } - return offset; -} - -static void dissect_apci(tvbuff_t *tvb, guint32 *offset, proto_tree *insert_tree, gboolean tpdu){ - - guint16 type; - guint16 sub_type; - guint8 length; - - length = tvb_get_guint8(tvb, *offset-1); - if (tpdu == TRUE){ - proto_tree_add_item(insert_tree, hf_knxnetip_cemi_reserved, tvb, *offset, 1, ENC_BIG_ENDIAN); - } - else { - proto_tree_add_item(insert_tree, hf_knxnetip_cemi_tpci, tvb, *offset, 1, ENC_BIG_ENDIAN); - type = (tvb_get_guint8(tvb, *offset)&0xC0); - if (type == 0x40 || type == 0xC0){ - proto_tree_add_item(insert_tree, hf_knxnetip_cemi_counter, tvb, *offset, 1, ENC_BIG_ENDIAN); - } - } - - if (length != 0) { - type = (tvb_get_ntohs(tvb, *offset) & 0x03C0); - switch (type){ - case(A_ADC_RED): - case(A_ADC_RES): - type = (tvb_get_ntohs(tvb, *offset) & 0x1FF); - if (type == A_SYS_RED || type == A_SYS_RES || type == A_SYS_WRT || type == A_SYS_BROAD){ - proto_tree_add_bits_item(insert_tree, hf_knxnetip_cemi_apci, tvb, (*offset*8)+6, 10, ENC_BIG_ENDIAN); - } - else { - proto_tree_add_bits_item(insert_tree, hf_knxnetip_cemi_apci, tvb, (*offset*8)+6, 4, ENC_BIG_ENDIAN); - proto_tree_add_bits_item(insert_tree, hf_knxnetip_cemi_channel, tvb, (*offset*8)+10, 6, ENC_BIG_ENDIAN); - } - *offset+=2; - break; - case(A_GROUPVALUE_RES): - case(A_GROUPVALUE_WRT): - proto_tree_add_bits_item(insert_tree, hf_knxnetip_cemi_apci, tvb, (*offset*8)+6, 4, ENC_BIG_ENDIAN); - if (length == 1){ - proto_tree_add_bits_item(insert_tree, hf_knxnetip_cemi_data, tvb, (*offset*8)+10, 6, ENC_BIG_ENDIAN); - } - *offset+=2; - break; - case(A_MEM_RED): - case(A_MEM_RES): - case(A_MEM_WRT): - proto_tree_add_bits_item(insert_tree, hf_knxnetip_cemi_apci, tvb, (*offset*8)+6, 6, ENC_BIG_ENDIAN); - proto_tree_add_bits_item(insert_tree, hf_knxnetip_cemi_apci_memory_number, tvb, (*offset*8)+12, 4, ENC_BIG_ENDIAN); - *offset+=2; - proto_tree_add_item(insert_tree, hf_knxnetip_cemi_apci_mem_address, tvb, *offset, 2, ENC_BIG_ENDIAN); - *offset+=2; - break; - case(COUPLER_SPECIFIC_SERVICE): - sub_type = (tvb_get_ntohs(tvb, *offset) & 0x3FF); - proto_tree_add_bits_item(insert_tree, hf_knxnetip_cemi_apci, tvb, (*offset*8)+6, 10, ENC_BIG_ENDIAN); - *offset+=2; - switch(sub_type){ - case(A_AUTHORIZE_REQ): - case(A_KEY_WRT): - proto_tree_add_item(insert_tree, hf_knxnetip_reserved, tvb, *offset, 1, ENC_NA); - *offset+=1; - proto_tree_add_item(insert_tree, hf_knxnetip_cemi_apci_key, tvb, *offset, 4, ENC_NA); - *offset+=4; - break; - case(A_AUTHORIZE_RES): - case(A_KEY_RES): - proto_tree_add_item(insert_tree, hf_knxnetip_cemi_apci_level, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - break; - case(A_PROPVALUE_RED): - case(A_PROPVALUE_RES): - proto_tree_add_item(insert_tree, hf_knxnetip_cemi_apci_object, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(insert_tree, hf_knxnetip_cemi_apci_propid, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(insert_tree, hf_knxnetip_cemi_noe, tvb, *offset, 1, ENC_BIG_ENDIAN); - proto_tree_add_item(insert_tree, hf_knxnetip_cemi_six, tvb, *offset, 2, ENC_BIG_ENDIAN); - *offset+=2; - } - break; - default: - proto_tree_add_bits_item(insert_tree, hf_knxnetip_cemi_apci, tvb, (*offset*8)+6, 10, ENC_BIG_ENDIAN); - *offset+=2; - } - - if (length >= 1){ - length-=1; - } - - if (length >= 1 && (tvb_reported_length_remaining(tvb, *offset) > 0)){ - proto_tree_add_item(insert_tree, hf_knxnetip_data, tvb, *offset, -1, ENC_NA); - *offset+=length; - } - - } - else { - *offset+=1; - } - -} - - -static gboolean dissect_cemi(tvbuff_t *tvb, guint32 *offset, proto_tree *insert_tree, packet_info *pinfo){ - - proto_item *cemi_item = NULL; - proto_item *additional_item = NULL; - proto_item *additional_info = NULL; - - proto_tree *cemi_tree = NULL; - proto_tree *additional_tree = NULL; - proto_tree *additional_subtree = NULL; - - guint8 i; - guint8 messagecode; - guint8 length; - guint8 type_id; - guint8 noe; - guint8 num_of_octets; - guint16 knx_address; - guint16 six; - - cemi_item = proto_tree_add_item(insert_tree, hf_knxnetip_cemi, tvb, *offset, -1, ENC_NA); - cemi_tree = proto_item_add_subtree(cemi_item, ett_knxnetip_cemi); - messagecode = tvb_get_guint8(tvb, *offset); - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_mc, tvb, *offset, 1, ENC_BIG_ENDIAN); - col_append_fstr(pinfo->cinfo, COL_INFO, "| cEMI: %s", val_to_str(messagecode, cemi_messagecodes, "Unknown MC:0x%0x")); - *offset+=1; - /*check if M_ Message*/ - if ((messagecode & 0xF0) < 0xF0){ - length = tvb_get_guint8(tvb, *offset); - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_addlength, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - - if (length != 0){ - - additional_info = proto_tree_add_item(cemi_tree, hf_knxnetip_additional, tvb, *offset, length, ENC_NA); - additional_tree = proto_item_add_subtree(additional_info, ett_knxnetip_cemi_additional); - do { - type_id = tvb_get_guint8(tvb, *offset); - additional_item = proto_tree_add_item(additional_tree, hf_knxnetip_cemi_typid, tvb, *offset, 1, ENC_BIG_ENDIAN); - additional_subtree = proto_item_add_subtree(additional_item, ett_knxnetip_cemi_additional_item); - *offset+=1; - proto_tree_add_item(additional_item, hf_knxnetip_cemi_additemlength, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - if (length >= 2){ - length-=2; - } - else{ - return TRUE; - } - - switch(type_id){ - case(PL_INFO): - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_type_pl, tvb, *offset, 2, ENC_BIG_ENDIAN); - *offset+=2; - if (length >= 2){ - length-=2; - } - else{ - return TRUE; - } - break; - case(RF_INFO): - proto_tree_add_bitmask(additional_subtree, tvb, *offset, hf_knxnetip_cemi_type_rf_info, ett_knxnetip_cemi_rf_info, cemi_rf_info, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_rf_sn, tvb, *offset, 6, ENC_BIG_ENDIAN); - *offset+=6; - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_rf_lfn, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - if (length >= 8){ - length-=8; - } - else{ - return TRUE; - } - break; - case(BUSMON_INFO): - proto_tree_add_bitmask(additional_subtree, tvb, *offset, hf_knxnetip_cemi_type_bus, ett_knxnetip_cemi_bus_info, cemi_bus_flags, ENC_BIG_ENDIAN); - *offset+=1; - if (length >= 1){ - length-=1; - } - else{ - return TRUE; - } - break; - case(TIME_REL): - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_type_relt, tvb, *offset, 2, ENC_BIG_ENDIAN); - *offset+=2; - if (length >= 2){ - length-=2; - } - else{ - return TRUE; - } - break; - case(TIME_DELAY): - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_type_delay, tvb, *offset, 4, ENC_BIG_ENDIAN); - *offset+=4; - if (length >= 4){ - length-=4; - } - else{ - return TRUE; - } - break; - case(EXEND_TIME): - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_type_exttime, tvb, *offset, 4, ENC_BIG_ENDIAN); - *offset+=4; - if (length >= 4){ - length-=4; - } - else{ - return TRUE; - } - break; - case(BIBAT_INFO): - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_type_bibat, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_type_bibat_block, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - if (length >= 2){ - length-=2; - } - else{ - return TRUE; - } - break; - case(RF_MULTI): - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_type_rf_multi_freq, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_type_rf_multi_channel, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_type_rf_multi_fastack, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_type_rf_multi_recep_freq, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - if (length >= 4){ - length-=4; - } - else{ - return TRUE; - } - break; - case(PREAMBEL): - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_type_preamble_length, tvb, *offset, 2, ENC_BIG_ENDIAN); - *offset+=2; - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_type_postamble_length, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - if (length >= 3){ - length-=3; - } - else{ - return TRUE; - } - break; - case(RF_FAST_ACK): - num_of_octets = tvb_get_guint8(tvb, *offset-1); - for(i=0; i<num_of_octets; i++) { - proto_tree_add_bitmask(additional_subtree, tvb, *offset, hf_knxnetip_cemi_type_fastack, ett_knxnetip_cemi_fastack, cemi_fastack_flags, ENC_BIG_ENDIAN); - *offset+=2; - if (length >= 2){ - length-=2; - } - else{ - return TRUE; - } - } - break; - case(MANU_DATA): - num_of_octets = tvb_get_guint8(tvb, *offset-1); - proto_tree_add_item(additional_subtree, hf_knxnetip_dib_manuid, tvb, *offset, 2, ENC_BIG_ENDIAN); - *offset+=2; - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_subfunction, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(additional_subtree, hf_knxnetip_cemi_manuspecificdata, tvb, *offset, (num_of_octets-3), ENC_NA); - *offset+=(num_of_octets-3); - if (length >= num_of_octets){ - length-=num_of_octets; - } - else{ - return TRUE; - } - break; - default: - proto_tree_add_item(additional_subtree, hf_knxnetip_unknown, tvb, *offset, -1, ENC_NA); - return *offset; - } - } while (length > 0); - } - } - switch (messagecode){ - case(DATA_REQ): - case(DATA_CON): - case(DATA_IND): - case(POLL_DATA_REQ): - case(POLL_DATA_CON): - proto_tree_add_bitmask(cemi_tree, tvb, *offset, hf_knxnetip_cemi_controlfield1, ett_knxnetip_cemi_control1, cemi_control1_flags, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_bitmask(cemi_tree, tvb, *offset, hf_knxnetip_cemi_controlfield2, ett_knxnetip_cemi_control2, cemi_control2_flags, ENC_BIG_ENDIAN); - *offset+=1; - knx_address = tvb_get_ntohs(tvb, *offset); - proto_tree_add_uint_format(cemi_tree, hf_knxnetip_cemi_sourceaddress, tvb, *offset, 2, knx_address, "Source Address %d.%d.%d", ((knx_address & 0xF000)>>12),((knx_address & 0x0F00)>>8),(knx_address & 0xFF)); - *offset+=2; - knx_address = tvb_get_ntohs(tvb, *offset); - if ((tvb_get_guint8(tvb, *offset-3) & 0x80) == GROUPADD){ - proto_tree_add_uint_format(cemi_tree, hf_knxnetip_cemi_destaddress, tvb, *offset, 2, knx_address, "Destination Address %d/%d/%d or %d/%d", ((knx_address & 0x7800)>>11),((knx_address & 0x0700)>>8),(knx_address & 0xFF), ((knx_address & 0x7800)>>11),(knx_address & 0x7FF)); - } - else { - proto_tree_add_uint_format(cemi_tree, hf_knxnetip_cemi_destaddress, tvb, *offset, 2, knx_address, "Destination Address %d.%d.%d", ((knx_address & 0xF000)>>12),((knx_address & 0x0F00)>>8),(knx_address & 0xFF)); - } - *offset+=2; - if (messagecode == POLL_DATA_REQ){ - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_numberofslots, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - } - else if (messagecode == POLL_DATA_CON){ - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_numberofslots, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(cemi_tree, hf_knxnetip_polldata, tvb, *offset, -1, ENC_NA); - } - else { - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_npdu_length, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - dissect_apci(tvb, offset, cemi_tree, FALSE); - } - break; - case(RAW_REQ): - case(RAW_CON): - case(RAW_IND): - case(BUSMON_IND): - proto_tree_add_item(cemi_tree, hf_knxnetip_raw, tvb, *offset, -1, ENC_NA); - break; - case(DATA_INDV_IND): - case(DATA_INDV_REQ): - case(DATA_CONNEC_IND): - case(DATA_CONNEC_REQ): - proto_tree_add_item(cemi_tree, hf_knxnetip_reserved, tvb, *offset, 6, ENC_NA); - *offset+=6; - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_tpdu_length, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - dissect_apci(tvb, offset, cemi_tree, TRUE); - break; - case(PROPREAD_REQ): - case(PROPREAD_CON): - case(PROPWRITE_REQ): - case(PROPWRITE_CON): - case(PROPINFO_IND): - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_iot, tvb, *offset, 2, ENC_BIG_ENDIAN); - *offset+=2; - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_oi, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_pid, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - noe = ((tvb_get_guint8(tvb, *offset)& 0xF0)>>4); - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_noe, tvb, *offset, 1, ENC_BIG_ENDIAN); - six = tvb_get_bits16(tvb, (*offset*8+4), 12, ENC_BIG_ENDIAN); - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_six, tvb, *offset, 2, ENC_BIG_ENDIAN); - *offset+=2; - if (messagecode == PROPREAD_REQ || (messagecode == PROPREAD_CON && noe > 0)){ - break; - } - else if (noe == 0){ - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_error, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - } - else if (noe == 1 && six == 0){ - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_numberofelements, tvb, *offset, 2, ENC_BIG_ENDIAN); - *offset+=2; - } - else { - proto_tree_add_item(cemi_tree, hf_knxnetip_data, tvb, *offset, -1, ENC_NA); - } - break; - case(FUNCPROPCOM_REQ): - case(FUNCPROPSTATREAD_REQ): - case(FUNCPROPCOM_CON): - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_iot, tvb, *offset, 2, ENC_BIG_ENDIAN); - *offset+=2; - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_oi, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_pid, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - if (messagecode == FUNCPROPCOM_CON){ - proto_tree_add_item(cemi_tree, hf_knxnetip_cemi_return, tvb, *offset, 1, ENC_BIG_ENDIAN); - *offset+=1; - } - proto_tree_add_item(cemi_tree, hf_knxnetip_data, tvb, *offset, -1, ENC_NA); - break; - case(RESET_REQ): - case(RESET_IND): - break; - default: - proto_tree_add_item(cemi_tree, hf_knxnetip_data, tvb, *offset, -1, ENC_NA); - } - return FALSE; -} - - - -static void dissect_knxnetip (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { - proto_item *knx_item = NULL; - - proto_tree *knx_tree = NULL; - proto_tree *header_tree = NULL; - proto_tree *body_tree = NULL; - - guint offset = 0; - guint16 service_type = 0; - gboolean err = FALSE; - - col_set_str(pinfo->cinfo, COL_PROTOCOL, "KNXnetIP"); - col_clear(pinfo->cinfo,COL_INFO); - - knx_item = proto_tree_add_item(tree, proto_knxnetip, tvb, 0, -1, ENC_NA); - knx_tree = proto_item_add_subtree(knx_item, ett_knxnetip); - - /* HEADER*/ - header_tree = proto_tree_add_subtree(knx_tree, tvb, offset, 6, ett_knxnetip_header, NULL, "Header"); - proto_tree_add_item(header_tree, hf_knxnetip_headerlength, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - proto_tree_add_item(header_tree, hf_knxnetip_version, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - service_type = tvb_get_ntohs(tvb, offset); - proto_tree_add_item(header_tree, hf_knxnetip_servicetype, tvb, offset, 2, ENC_BIG_ENDIAN); - col_add_fstr(pinfo->cinfo, COL_INFO, "%s %d > %d", val_to_str(service_type, knxnetip_service_identifier, "Unknown Identifier:0x%02x"), pinfo->srcport, pinfo->destport); - offset+=2; - proto_tree_add_item(header_tree, hf_knxnetip_totallength, tvb, offset, 2, ENC_BIG_ENDIAN); - offset+=2; - /* BODY */ - body_tree = proto_tree_add_subtree(knx_tree, tvb, offset, -1, ett_knxnetip_body, NULL, "Body"); - - switch(service_type) { - - case(SEARCH_REQ): - dissect_hpai(tvb, &offset, body_tree, ": Discovery endpoint"); - break; - case(SEARCH_RES): - dissect_hpai(tvb, &offset, body_tree, ": Control endpoint"); - err = dissect_dib(tvb, &offset, body_tree); - if (err == TRUE){ - proto_tree_add_expert(body_tree, pinfo, &ei_knxnetip_length, tvb, offset, -1); - break; - } - err = dissect_dib(tvb, &offset, body_tree); - if (err == TRUE){ - proto_tree_add_expert(body_tree, pinfo, &ei_knxnetip_length, tvb, offset, -1); - break; - } - break; - case(DESCRIPTION_REQ): - dissect_hpai(tvb, &offset, body_tree, ": Control endpoint"); - break; - case(DESCRIPTION_RES): - err = dissect_dib(tvb, &offset, body_tree); - if (err == TRUE){ - proto_tree_add_expert(body_tree, pinfo, &ei_knxnetip_length, tvb, offset, -1); - break; - } - err = dissect_dib(tvb, &offset, body_tree); - if (err == TRUE){ - proto_tree_add_expert(body_tree, pinfo, &ei_knxnetip_length, tvb, offset, -1); - break; - } - if (tvb_reported_length_remaining(tvb, offset) != 0){ - err = dissect_dib(tvb, &offset, body_tree); - if (err == TRUE){ - proto_tree_add_expert(body_tree, pinfo, &ei_knxnetip_length, tvb, offset, -1); - } - } - break; - case(CONNECT_REQ): - dissect_hpai(tvb, &offset, body_tree, ": Discovery endpoint"); - dissect_hpai(tvb, &offset, body_tree, ": Data endpoint"); - offset = dissect_cri(tvb, offset, body_tree); - break; - case(CONNECT_RES): - proto_tree_add_item(body_tree, hf_knxnetip_communication_channel_id, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - proto_tree_add_item(body_tree, hf_knxnetip_connect_status, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - dissect_hpai(tvb, &offset, body_tree, ": Data endpoint"); - dissect_crd(tvb, &offset, body_tree); - break; - case(CONNECTIONSTATE_REQ): - case(DISCONNECT_REQ): - proto_tree_add_item(body_tree, hf_knxnetip_communication_channel_id, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - proto_tree_add_item(body_tree, hf_knxnetip_reserved, tvb, offset, 1, ENC_NA); - offset+=1; - dissect_hpai(tvb, &offset, body_tree, ": Control endpoint"); - break; - case(DISCONNECT_RES): - case(CONNECTIONSTATE_RES): - proto_tree_add_item(body_tree, hf_knxnetip_communication_channel_id, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - proto_tree_add_item(body_tree, hf_knxnetip_connectionstate_status, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - break; - case(DEVICE_CONFIGURATION_ACK): - offset = dissect_connection_header(tvb, offset, body_tree, TRUE); - proto_tree_add_item(body_tree, hf_knxnetip_confack_status, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - break; - case(DEVICE_CONFIGURATION_REQ): - case(TUNNELLING_REQ): - offset = dissect_connection_header(tvb, offset, body_tree, FALSE); - err = dissect_cemi (tvb, &offset, body_tree, pinfo); - if (err == TRUE){ - proto_tree_add_expert(body_tree, pinfo, &ei_knxnetip_length, tvb, offset, -1); - } - break; - case(TUNNELLING_ACK): - offset = dissect_connection_header(tvb, offset, body_tree, TRUE); - proto_tree_add_item(body_tree, hf_knxnetip_tunnelack_status, tvb, offset, 1, ENC_BIG_ENDIAN); - break; - case(ROUTING_INDICATION): - err = dissect_cemi (tvb, &offset, body_tree, pinfo); - if (err == TRUE){ - proto_tree_add_expert(body_tree, pinfo, &ei_knxnetip_length, tvb, offset, -1); - } - break; - case(ROUTING_LOST): - proto_tree_add_item(body_tree, hf_knxnetip_structure_length, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - proto_tree_add_bitmask(body_tree, tvb, offset, hf_knxnetip_devicestate, ett_knxnetip_devicestate, devicestate_flags, ENC_BIG_ENDIAN); - offset+=1; - proto_tree_add_item(body_tree, hf_knxnetip_numberoflost, tvb, offset, 2, ENC_BIG_ENDIAN); - offset+=2; - break; - case(ROUTING_BUSY): - proto_tree_add_item(body_tree, hf_knxnetip_structure_length, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - proto_tree_add_bitmask(body_tree, tvb, offset, hf_knxnetip_devicestate, ett_knxnetip_devicestate, devicestate_flags, ENC_BIG_ENDIAN); - offset+=1; - proto_tree_add_item(body_tree, hf_knxnetip_busywaittime, tvb, offset, 2, ENC_BIG_ENDIAN); - offset+=2; - proto_tree_add_item(body_tree, hf_knxnetip_busycontrol, tvb, offset, 2, ENC_BIG_ENDIAN); - offset+=2; - break; - case(REMOTE_DIAG_REQ): - dissect_hpai(tvb, &offset, body_tree, ": Discovery endpoint"); - offset = dissect_selector(tvb ,offset, body_tree); - break; - case(REMOTE_DIAG_RES): - offset = dissect_selector(tvb ,offset, body_tree); - do{ - err = dissect_dib(tvb, &offset, body_tree); - if (err == TRUE){ - proto_tree_add_expert(body_tree, pinfo, &ei_knxnetip_length, tvb, offset, -1); - break; - } - } while (tvb_reported_length_remaining(tvb,offset) > 0); - break; - case(REMOTE_BASIC_CONF_REQ): - dissect_hpai(tvb, &offset, body_tree, ": Discovery endpoint"); - offset = dissect_selector(tvb ,offset, body_tree); - err = dissect_dib(tvb, &offset, body_tree); - if (err == TRUE){ - proto_tree_add_expert(body_tree, pinfo, &ei_knxnetip_length, tvb, offset, -1); - break; - } - if (tvb_reported_length_remaining(tvb,offset) > 0) { - err = dissect_dib(tvb, &offset, body_tree); - if (err == TRUE){ - proto_tree_add_expert(body_tree, pinfo, &ei_knxnetip_length, tvb, offset, -1); - } - } - break; - case(REMOTE_RESET_REQ): - offset = dissect_selector(tvb ,offset, body_tree); - proto_tree_add_item(body_tree, hf_knxnetip_reset, tvb, offset, 1, ENC_BIG_ENDIAN); - offset+=1; - proto_tree_add_item(body_tree, hf_knxnetip_reserved, tvb, offset, 1, ENC_NA); - offset+=1; - break; - - default: - proto_tree_add_item(body_tree, hf_knxnetip_unknown, tvb, offset, -1, ENC_NA); - } -} - -static gboolean dissect_knxnetip_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { - - gint idx; - idx = 0; - - if (tvb_captured_length(tvb) < 8){ - return (FALSE); - } - if ( tvb_get_guint8(tvb, 0) != KNXNETIP_HEADER_LENGTH) { - return (FALSE); - } - if ( tvb_get_guint8(tvb, 1) != KNXNETIP_PROTOCOL_VERSION){ - return (FALSE); - } - try_val_to_str_idx((guint32)tvb_get_ntohs(tvb, 2), knxnetip_service_identifier, &idx); - if (idx == -1){ - return (FALSE); - } - - dissect_knxnetip(tvb, pinfo, tree); - return (TRUE); -} - -void proto_register_knxnetip (void) { - expert_module_t* expert_knxnetip; - - static hf_register_info hf[] = { - { &hf_knxnetip_headerlength, - { "Header Length", "knxnetip.header_length", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_version, - { "Protocol Version", "knxnetip.version", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_servicetype, - { "Service Type Identifier", "knxnetip.service_type_identifier", FT_UINT16, BASE_HEX, VALS(knxnetip_service_identifier), 0x0, NULL, HFILL }}, - { &hf_knxnetip_totallength, - { "Total Length", "knxnetip.total_length", FT_UINT16, BASE_DEC|BASE_UNIT_STRING, &units_octet_octets, 0x0, NULL, HFILL }}, - { &hf_knxnetip_hpai, - { "HPAI", "knxnetip.hpai", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_hpai_structure_length, - { "Structure Length", "knxnetip.hpai_structure_length", FT_UINT8, BASE_DEC|BASE_UNIT_STRING, &units_octet_octets, 0x0, NULL, HFILL }}, - { &hf_knxnetip_structure_length, - { "Structure Length", "knxnetip.struct_length", FT_UINT8, BASE_DEC|BASE_UNIT_STRING, &units_octet_octets, 0x0, NULL, HFILL }}, - { &hf_knxnetip_hpai_host_protocol, - { "Host Protocol Code", "knxnetip.hpai_host_protocol", FT_UINT8, BASE_HEX, VALS(knxnetip_host_protocol_codes), 0x0, NULL, HFILL }}, - { &hf_knxnetip_hpai_ip_address, - { "IP Address", "knxnetip.hpai_ip_address", FT_IPv4, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_hpai_port, - { "IP Port", "knxnetip.hpai_port", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib, - { "DIB", "knxnetip.dib", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cri, - { "Connection Request Information", "knxnetip.cri", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_type, - { "Description Type", "knxnetip.dib_type", FT_UINT8, BASE_HEX, VALS(knxnetip_dib_description_type_codes), 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_medium, - { "KNX medium", "knxnetip.dib_medium", FT_UINT8, BASE_HEX, VALS(knxnetip_dib_medium_codes), 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_status, - { "Device Status", "knxnetip.dib_status", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_projectid, - { "Project-Installation identifier", "knxnetip.dib_projectid", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_knxaddress, - { "KNX Individual Address", "knxnetip.knxaddress", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_serialnumber, - { "KNXnet/IP device serial number", "knxnetip.serialnumber", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_multicast_address, - { "KNXnet/IP device multicast address", "knxnetip.multicast", FT_IPv4, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_mac_address, - { "KNXnet/IP device MAC address", "knxnetip.macaddress", FT_ETHER, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_friendly, - { "Device Friendly Name", "knxnetip.devicename", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_service, - { "Service ID", "knxnetip.dib_service", FT_UINT8, BASE_HEX, VALS(knxnetip_service_types), 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_ipaddress, - { "IP Address", "knxnetip.dib_ipaddress", FT_IPv4, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_subnet, - { "Subnet Mask", "knxnetip.dib_subnet", FT_IPv4, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_gateway, - { "Default Gateway", "knxnetip.dib_gateway", FT_IPv4, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_ipcapa, - { "IP Capabilities", "knxnetip.dib_ipcapabilities", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_ipassign, - { "IP assignment method", "knxnetip.dib_assignment", FT_UINT8, BASE_HEX, VALS(knxnetip_ip_assignment_method), 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_dhcp, - { "DHCP Server", "knxnetip.dib_dhcp", FT_IPv4, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_manuid, - { "Manufacturer ID", "knxnetip.manufacturer_id", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_manudata, - { "Manufacturer specific data", "knxnetip.manufacturer_data", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_connection_type, - { "Connection Type", "knxnetip.connection_type", FT_UINT8, BASE_HEX, VALS(knxnetip_connection_types), 0x0, NULL, HFILL }}, - { &hf_knxnetip_cri_protocol_data, - { "Protocol Data", "knxnetip.cri_data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_communication_channel_id, - { "Communication Channel ID", "knxnetip.communication_channel_id", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_connect_status, - { "Status", "knxnetip.connect_status", FT_UINT8, BASE_HEX, VALS(knxnetip_connect_response_status_codes), 0x0, NULL, HFILL }}, - { &hf_knxnetip_crd_protocol_data, - { "Protocol Data", "knxnetip.crd_data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_crd, - { "Connection Response Data Block", "knxnetip.crd", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_connectionstate_status, - { "Status", "knxnetip.connect_state_status", FT_UINT8, BASE_HEX, VALS(knxnetip_connectionstate_response_status_codes), 0x0, NULL, HFILL }}, - { &hf_knxnetip_counter, - { "Sequence Counter", "knxnetip.sequence_counter", FT_UINT8, BASE_DEC, 0x0, 0x0, NULL, HFILL }}, - { &hf_knxnetip_confack_status, - { "Status", "knxnetip.confirm_ack_status", FT_UINT8, BASE_HEX, VALS(knxnetip_device_configuration_ack_status_codes), 0x0, NULL, HFILL }}, - { &hf_knxnetip_tunnelack_status, - { "Status", "knxnetip.tunnel_status", FT_UINT8, BASE_HEX, VALS(knxnetip_tunneling_error_codes), 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_status_flag_reserved, - { "reserved", "knxnetip.dib_reserved", FT_UINT8, BASE_HEX, NULL, FLAGS_DEVICESTATUS_RESERVED, NULL, HFILL }}, - { &hf_knxnetip_dib_status_flag_program, - { "program mode", "knxnetip.dib_program_mode", FT_UINT8, BASE_DEC, NULL, FLAGS_DEVICESTATUS_PROGRAM, NULL , HFILL }}, - { &hf_knxnetip_dib_ipcapa_flag_reserved, - { "reserved", "knxnetip.ip_capabilities_reserved", FT_UINT8, BASE_HEX, NULL, FLAGS_IPCAPABILITES_RESERVED, NULL, HFILL }}, - { &hf_knxnetip_dib_ipcapa_flag_bootip, - { "BootIP", "knxnetip.ip_capabilities_bootip", FT_UINT8, BASE_DEC, NULL, FLAGS_IPCAPABILITES_BOOTIP, NULL, HFILL }}, - { &hf_knxnetip_dib_ipcapa_flag_dhcp, - { "DHCP", "knxnetip.ip_capabilities_dhcp", FT_UINT8, BASE_DEC, NULL, FLAGS_IPCAPABILITES_DHCP, NULL, HFILL }}, - { &hf_knxnetip_dib_ipcapa_flag_autoip, - { "AutoIP", "knxnetip.ip_capabilities_autoip", FT_UINT8, BASE_DEC, NULL, FLAGS_IPCAPABILITES_AUTOIP, NULL, HFILL }}, - { &hf_knxnetip_devicestate, - { "DeviceState", "knxnetip.devicestate", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_devicestate_reserved, - { "reserved", "knxnetip.devicestate_reserved", FT_UINT8, BASE_HEX, NULL, FLAGS_DEVICESTATE_RESERVED, NULL, HFILL }}, - { &hf_knxnetip_devicestate_knx, - { "KNX Fault", "knxnetip.devicestate_knx", FT_UINT8, BASE_DEC, NULL, FLAGS_DEVICESTATE_KNX, "is set if KNX network cannot be accessed", HFILL }}, - { &hf_knxnetip_devicestate_ip, - { "IP Fault", "knxnetip.devicestate_ip", FT_UINT8, BASE_DEC, NULL, FLAGS_DEVICESTATE_IP, "is set if IP network cannot be accessed", HFILL }}, - { &hf_knxnetip_numberoflost, - { "NumberofLostMessages", "knxnetip.number_of_lost_msg", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_busywaittime, - { "Busy Wait Time", "knxnetip.busy_time", FT_UINT16, BASE_DEC|BASE_UNIT_STRING, &units_milliseconds, 0x0, NULL, HFILL }}, - { &hf_knxnetip_busycontrol, - { "Busy Control Field", "knxnetip.busy_control", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_knxlayer, - { "KNX Layer", "knxnetip.layer", FT_UINT8, BASE_HEX, VALS(knxnetip_knxlayer_values), 0x0, NULL, HFILL }}, - { &hf_knxnetip_selector_type, - { "Selector Type Code", "knxnetip.selector", FT_UINT8, BASE_HEX, VALS(knxnetip_selector_types), 0x0, NULL, HFILL }}, - { &hf_knxnetip_reset, - { "Reset Command", "knxnetip.reset", FT_UINT8, BASE_HEX, VALS(knxnetip_reset_codes), 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi, - { "cEMI", "knxnetip.cemi", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_mc, - { "messagecode", "knxnetip.cemi_messagecode", FT_UINT8, BASE_HEX, VALS(cemi_messagecodes), 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_addlength, - { "add information length", "knxnetip.additional_length", FT_UINT8, BASE_DEC|BASE_UNIT_STRING, &units_octet_octets, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_additemlength, - { "Length", "knxnetip.additional_item_length", FT_UINT8, BASE_DEC|BASE_UNIT_STRING, &units_octet_octets, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_typid, - { "Type id", "knxnetip.cemi_type_id", FT_UINT8, BASE_HEX, VALS(cemi_add_type_id), 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_pl, - { "Domain-Address", "knxnetip.cemi_type_pl", FT_UINT16, BASE_HEX, 0x0, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_bus, - { "Busmonitor error flags", "knxnetip.cemi_type_bus", FT_UINT8, BASE_HEX, 0x0, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_relt, - { "relative timestamp", "knxnetip.cemi_type_reltime", FT_UINT16, BASE_HEX, 0x0, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_delay, - { "delay", "knxnetip.cemi_type_delay", FT_UINT32, BASE_HEX, 0x0, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_exttime, - { "extended timestamp", "knxnetip.cemi_type_exttime", FT_UINT32, BASE_HEX, 0x0, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_bibat, - { "BiBat", "knxnetip.cemi_type_bibat", FT_UINT8, BASE_HEX, VALS(cemi_bibat_ctrl), 0xF8, NULL, HFILL }}, - { &hf_knxnetip_cemi_controlfield1, - { "Controlfield 1", "knxnetip.controlfield_one", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_flag_frametype, - { "Frametype", "knxnetip.controlfield_type", FT_UINT8, BASE_DEC, NULL, FLAGS_CEMI_CONTROL1_FT, "0: extended frame; 1: standard frame", HFILL }}, - { &hf_knxnetip_cemi_flag_repeat, - { "Repeat", "knxnetip.controlfield_repeat", FT_UINT8, BASE_DEC, NULL, FLAGS_CEMI_CONTROL1_R, "0: repeat if error frame; 1: do not repeat", HFILL }}, - { &hf_knxnetip_cemi_flag_sb, - { "System-Broadcast", "knxnetip.controlfield_broadcast", FT_UINT8, BASE_DEC, NULL, FLAGS_CEMI_CONTROL1_SB, "0: system-broadcast; 1: broadcast", HFILL }}, - { &hf_knxnetip_cemi_flag_priority, - { "Priority", "knxnetip.controlfield_priority", FT_UINT8, BASE_HEX, NULL, FLAGS_CEMI_CONTROL1_P, NULL, HFILL }}, - { &hf_knxnetip_cemi_flag_ack, - { "Acknowledge-Request", "knxnetip.controlfield_ack", FT_UINT8, BASE_DEC, NULL, FLAGS_CEMI_CONTROL1_A, "0: no request for ack; 1: request ack", HFILL }}, - { &hf_knxnetip_cemi_flag_confirm, - { "Confirm-Flag", "knxnetip.controlfield_confirm", FT_UINT8, BASE_DEC, NULL, FLAGS_CEMI_CONTROL1_C, "0: no error in frame; 1: error in frame", HFILL }}, - { &hf_knxnetip_cemi_controlfield2, - { "Controlfield 2", "knxnetip.controlfield_two", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_flag_destaddress, - { "Destination address type", "knxnetip.controldestaddress", FT_UINT8, BASE_DEC, NULL, FLAGS_CEMI_CONTROL2_AT, "0: individual; 1: group", HFILL }}, - { &hf_knxnetip_flag_hop, - { "Hop count", "knxnetip.controlhop", FT_UINT8, BASE_DEC, NULL, FLAGS_CEMI_CONTROL2_HC, NULL, HFILL }}, - { &hf_knxnetip_flag_eff, - { "Extended Frame Format", "knxnetip.controleff", FT_UINT8, BASE_HEX, NULL, FLAGS_CEMI_CONTROL2_EFF, "0000b for standard frame", HFILL }}, - { &hf_knxnetip_cemi_sourceaddress, - { "Source Address", "knxnetip.cemisource", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_destaddress, - { "Destination Address", "knxnetip.cemidestination", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_tpci, - { "TPCI", "knxnetip.cemitpci", FT_UINT8, BASE_HEX, VALS(cemi_tpci_vals), 0xC0, NULL, HFILL }}, - { &hf_knxnetip_cemi_npdu_length, - { "NPDU length", "knxnetip.npdulength", FT_UINT8, BASE_DEC|BASE_UNIT_STRING, &units_octet_octets, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_tpdu_length, - { "TPDU length", "knxnetip.tpdulength", FT_UINT8, BASE_DEC|BASE_UNIT_STRING, &units_octet_octets, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_counter, - { "sequence NCD/NDT", "knxnetip.npduseq", FT_UINT8, BASE_DEC, NULL, 0x3C, NULL, HFILL }}, - { &hf_knxnetip_cemi_apci, - { "APCI", "knxnetip.npduapci", FT_UINT16, BASE_HEX, VALS(cemi_apci_codes), 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_data, - { "Data", "knxnetip.cemidata", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_numberofslots, - { "number of slots", "knxnetip.ceminumberofslots", FT_UINT8, BASE_DEC, NULL, 0xF, NULL, HFILL }}, - { &hf_knxnetip_cemi_apci_memory_number, - { "number of octets to be read/write", "knxnetip.cemidata", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_iot, - { "Interface object type", "knxnetip.cemiiot", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_oi, - { "Object Instance", "knxnetip.cemioi", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_pid, - { "Property Identifier", "knxnetip.cemipid", FT_UINT8, BASE_DEC, VALS(cemi_propertyid), 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_noe, - { "Number of Elements", "knxnetip.ceminoe", FT_UINT8, BASE_DEC, NULL, 0xF0, NULL, HFILL }}, - { &hf_knxnetip_cemi_six, - { "Startindex", "knxnetip.cemipid", FT_UINT16, BASE_DEC, NULL, 0xFFF, NULL, HFILL }}, - { &hf_knxnetip_cemi_numberofelements, - { "Number of Elements", "knxnetip.ceminumber", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_error, - { "Error Code", "knxnetip.cemierror", FT_UINT8, BASE_HEX, VALS(cemi_error_codes), 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_return, - { "retrun code", "knxnetip.cemireturn", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_reserved, - { "reserved", "knxnetip.cemireserved", FT_UINT8, BASE_HEX, NULL, 0xFC, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_rf_info, - { "RF-Info", "knxnetip.cemirfinfo", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_rf_mesure, - { "received signal strength", "knxnetip.cemirfmesure", FT_UINT8, BASE_HEX, NULL, FLAGS_CEMI_RF_MESURE, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_rf_reserved, - { "reserved", "knxnetip.cemirfreserved", FT_UINT8, BASE_HEX, NULL, FLAGS_CEMI_RF_RESERVED, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_rf_mesure_re, - { "retransmitter signal strrength", "knxnetip.cemirfmesurere", FT_UINT8, BASE_HEX, NULL, FLAGS_CEMI_RF_MESURE_RE, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_rf_battery, - { "Battery state", "knxnetip.cemirfbattery", FT_UINT8, BASE_HEX, NULL, FLAGS_CEMI_RF_BATTERY, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_rf_bidirekt, - { "is not bidirektional", "knxnetip.cemirfbattery", FT_UINT8, BASE_HEX, NULL, FLAGS_CEMI_RF_BIDIRETIONAL, NULL, HFILL }}, - { &hf_knxnetip_cemi_rf_sn, - { "KNX Serial Number", "knxnetip.cemiknxsn", FT_UINT64, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_rf_lfn, - { "Data Link Layer frame number", "knxnetip.cemilfn", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_bus_flag_f, - { "Frame error flag", "knxnetip.cemibusferror", FT_UINT8, BASE_DEC, NULL, FLAGS_CEMI_BUS_F, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_bus_flag_b, - { "Bit error flag", "knxnetip.cemibusberror", FT_UINT8, BASE_DEC, NULL, FLAGS_CEMI_BUS_B, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_bus_flag_p, - { "Parity error flag", "knxnetip.cemibusparity", FT_UINT8, BASE_DEC, NULL, FLAGS_CEMI_BUS_P, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_bus_flag_d, - { "dont care", "knxnetip.cemibusdont", FT_UINT8, BASE_DEC, NULL, FLAGS_CEMI_BUS_D, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_bus_flag_l, - { "Lost flag", "knxnetip.cemibuslost", FT_UINT8, BASE_DEC, NULL, FLAGS_CEMI_BUS_L, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_bus_flag_sss, - { "Sequence Number", "knxnetip.cemibusseq", FT_UINT8, BASE_DEC, NULL, FLAGS_CEMI_BUS_SSS, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_bibat_block, - { "BiBat Block number", "knxnetip.cemibibbatblock", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_rf_multi_fastack, - { "KNX RF Multi Fast Ack", "knxnetip.cemirffastack", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_rf_multi_freq, - { "KNX RF Multi Transmission Frequency", "knxnetip.cemirffreq", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_rf_multi_channel, - { "KNX RF Multi Call Channel", "knxnetip.cemirfchannel", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_rf_multi_recep_freq, - { "KNX RF Multi Reception Frequency", "knxnetip.cemirfrecfreq", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_preamble_length, - { "Preamble Length", "knxnetip.cemipreamblelength", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_postamble_length, - { "Postamble Length", "knxnetip.cemipostamblelength", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_fastack, - { "Fast Ack information", "knxnetip.cemifastack", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_fastack_crc, - { "Fast Ack is received with a CRC", "knxnetip.cemifastackcrc", FT_UINT16, BASE_DEC, NULL, FLAGS_CEMI_FASTACK_CRC, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_fastack_error, - { "Fast Ack is received with a Manchester error", "knxnetip.cemifastackerror", FT_UINT16, BASE_DEC, NULL, FLAGS_CEMI_FASTACK_ERROR, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_fastack_received, - { "Fast Ack has been received", "knxnetip.cemifastackres", FT_UINT16, BASE_DEC, NULL, FLAGS_CEMI_FASTACK_RES, NULL, HFILL }}, - { &hf_knxnetip_cemi_type_fastack_info, - { "Fast Ack Info", "knxnetip.cemifastackinfo", FT_UINT16, BASE_HEX, NULL, FLAGS_CEMI_FASTACK_INFO, NULL, HFILL }}, - { &hf_knxnetip_cemi_subfunction, - { "Subfunction", "knxnetip.cemisubfunction", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_manuspecificdata, - { "Manufacturer specific data", "knxnetip.cemimanuspecificdata", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_projectnumber, - { "Project number", "knxnetip.projectnumber", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_installnumber, - { "Installation number", "knxnetip.installnumber", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_dib_svc_version, - { "Version", "knxnetip.svcversion", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_reserved, - { "reserved", "knxnetip.reserved", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_data, - { "data", "knxnetip.data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_polldata, - { "Poll data", "knxnetip.polldata", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_raw, - { "RAW Frame", "knxnetip.raw", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_additional, - { "Additional information", "knxnetip.additional", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_unknown, - { "UNKNOWN", "knxnetip.unknown", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_apci_mem_address, - { "Memory Address", "knxnetip.cemimemaddress", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_channel, - { "Channel nr", "knxnetip.cemichannel", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_apci_key, - { "key", "knxnetip.apcikey", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_apci_level, - { "level", "knxnetip.apcilevel", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_apci_object, - { "object index", "knxnetip.apciobjidx", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, - { &hf_knxnetip_cemi_apci_propid, - { "property id", "knxnetip.apcipropid", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }} - }; - - static gint *ett[] = { - &ett_knxnetip, - &ett_knxnetip_header, - &ett_knxnetip_body, - &ett_knxnetip_hpai, - &ett_knxnetip_dib, - &ett_knxnetip_dib_projectid, - &ett_knxnetip_dib_service, - &ett_knxnetip_cri, - &ett_knxnetip_crd, - &ett_knxnetip_dib_status, - &ett_knxnetip_dib_ipcapa, - &ett_knxnetip_devicestate, - &ett_knxnetip_cemi, - &ett_knxnetip_cemi_additional, - &ett_knxnetip_cemi_additional_item, - &ett_knxnetip_cemi_control1, - &ett_knxnetip_cemi_control2, - &ett_knxnetip_cemi_rf_info, - &ett_knxnetip_cemi_bus_info, - &ett_knxnetip_cemi_fastack - }; - - static ei_register_info ei[] = { - { &ei_knxnetip_length, { "knxnetip.invalid.length", PI_PROTOCOL, PI_ERROR, "invalid length", EXPFILL }}, - }; - - proto_knxnetip = proto_register_protocol("KNXnet/IP", "knxnetip", "knx"); - proto_register_field_array(proto_knxnetip, hf, array_length(hf)); - proto_register_subtree_array(ett, array_length(ett)); - expert_knxnetip = expert_register_protocol(proto_knxnetip); - expert_register_field_array(expert_knxnetip, ei, array_length(ei)); -} - - -void proto_reg_handoff_knxnetip(void) { - /* register as heuristic dissector for both TCP and UDP */ - heur_dissector_add("tcp", dissect_knxnetip_heur, "KNXnet/IP over TCP", "knxnetip_tcp", proto_knxnetip, HEURISTIC_ENABLE); - heur_dissector_add("udp", dissect_knxnetip_heur, "KNXnet/IP over UDP", "knxnetip_udp", proto_knxnetip, HEURISTIC_ENABLE); -} - -/* - * Editor modelines - http://www.wireshark.org/tools/modelines.html - * - * Local variables: - * c-basic-offset: 4 - * tab-width: 8 - * indent-tabs-mode: nil - * End: - * - * vi: set shiftwidth=4 tabstop=8 expandtab: - * :indentSize=4:tabSize=8:noTabs=true: - */ diff --git a/test/captures/knxip_DataSec.pcap b/test/captures/knxip_DataSec.pcap Binary files differnew file mode 100644 index 0000000000..7e28cd40e5 --- /dev/null +++ b/test/captures/knxip_DataSec.pcap diff --git a/test/captures/knxip_SecureWrapper.pcap b/test/captures/knxip_SecureWrapper.pcap Binary files differnew file mode 100644 index 0000000000..0be64d8c1a --- /dev/null +++ b/test/captures/knxip_SecureWrapper.pcap diff --git a/test/captures/knxip_TimerNotify.pcap b/test/captures/knxip_TimerNotify.pcap Binary files differnew file mode 100644 index 0000000000..4e7ae3781b --- /dev/null +++ b/test/captures/knxip_TimerNotify.pcap diff --git a/test/keys/knx_keyring.xml b/test/keys/knx_keyring.xml new file mode 100644 index 0000000000..c9b3cdd299 --- /dev/null +++ b/test/keys/knx_keyring.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- All attributes of the Keyring element are mandatory. For details on how to calculate/verify the Signature attribute, see below --> +<!-- "oKGio6SlpqeoqaqrrK2urw==" = $ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF --> +<!-- "sLGys7S1tre4ubq7vL2+vw==" = $ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF --> +<Keyring xmlns="http://knx.org/xml/keyring/1" Project="Family Home" Created="2015-03-04T20:55:58.0160546Z" CreatedBy="ETS 5.5 Build 456" Signature="MTIzNDU2Nzg5MDEyMzQ1Ng=="> + <!-- A backbone element is included if the project has a secure IP backbone or the keyring contains at least one IP Routing interface --> + <!-- The Latency and BackboneKey attributes are only included if the project has a secure IP backbone --> + <!-- BackboneKey := Base64( AES128-CBC( PBKDF2( HMAC-SHA256, KeyringPassword, "1.keyring.ets.knx.org", 65536, 128), MSB128(SHA256(this.Keyring.Created), Project.BackboneKey as byte[])) --> (1) + <Backbone MulticastAddress="224.0.23.12" Latency="1000" Key="oKGio6SlpqeoqaqrrK2urw=="/> + <!-- There might be 0..n interface elements. Possible types are "USB", "Tunneling" or "Routing". There is no support for "Eiblib/IP". --> + <!-- Depending on the type of interface different interface attributes are possible. --> + <!-- USB interfaces only appear in the keying if used for Data Security communication. They only have the mandatory attribute "IndividualAddress". --> + <Interface Type="USB" IndividualAddress="2.1.56"> + <!-- Interfaces used for Data Security communication have a list of group addresses which will be communicated over this interface. --> + <!-- Each interface group has a mandatory list of trusted senders that are allowed to send telegrams to this group address. --> + <Group Address="3971" Senders="1.1.1 1.1.3 1.1.4"/> + <Group Address="3972" Senders="1.1.2 1.1.4"/> + <Group Address="14271" Senders="1.1.1"/> + </Interface> + <!-- Tunneling interfaces might appear in the keying because either they are used for Data Security communication or access to the interface itself is protected (or both). --> + <!-- KNXnet/IP Tunneling interfaces have as a mandatary attribute the KNX individual address of the host device. --> + <!-- If the Tunneling interface is used for Data Security communication, the attribute "IndividualAddress" is mandatory, otherwise it's optional. --> + <!-- Secured Tunneling interfaces need to have the "UserID" and "Password" attributes. An "Authentication" attribute containing the interface's device authenication code is optional. --> + <!-- Password := Base64( AES128-CBC( PBKDF2( HMAC-SHA256, KeyringPassword, "1.keyring.ets.knx.org", 65536, 128), MSB128(SHA256(this.Keyring.Created)), RandomBytes(8)+PKCS#7Padding( Device.BusAccess(IA).Password, 24) as byte[])) --> (1) + <!-- Authentication := Base64( AES128-CBC( PBKDF2( HMAC-SHA256, KeyringPassword, "1.keyring.ets.knx.org", 65536, 128), MSB128(SHA256(this.Keyring.Created)), RandomBytes(8)+PKCS#7Padding( Device.AthenticationCode, 24) as byte[])) --> (1) + <Interface Type="Tunneling" Host="2.1.0" IndividualAddress="2.1.56" UserID="3" Password="A3BzNDU2Nzg5MDEyMzQnUA3BzNDU2Nzg5MDEyMzQnUw=" Authentication="A3BzNDU2Nzg5MDEyMzQnUA3BzNDU2Nzg5MDEyMzQnUw="/> + <!-- If the Backbone (KNXnet/IP Routing) interface is used for Data Security communication, the attribute "IndividualAddress" is mandatory, otherwise it's optional. --> + <!-- If no "IndividualAddress" attribute is present, backbone interface elements shall be omitted altogether as all necessary information is part of the Backbone element already. --> + <Interface Type="Backbone" IndividualAddress="0.0.123"> + <Group Address="256" Senders="1.1.1 1.1.3 1.1.4"/> + <Group Address="3972" Senders="1.1.2 1.1.4"/> + </Interface> + <!-- The "GroupAddresses" collection shall only be present if the keyring contains interface groups for Data Security communication. --> + <!-- The "Address" and "Key" attributes are mandatory for every "GroupAddresses/Group" element. --> + <!-- Key := Base64( AES128-CBC( PBKDF2( HMAC-SHA256, KeyringPassword, "1.keyring.ets.knx.org", 65536, 128), MSB128(SHA256(this.Keyring.Created)), GroupAddress.Key as byte[])) --> (1) + <GroupAddresses> + <Group Address="256" Key="oKGio6SlpqeoqaqrrK2urw=="/> + <Group Address="3971" Key="oKGio6SlpqeoqaqrrK2urw=="/> + <Group Address="3972" Key="oKGio6SlpqeoqaqrrK2urw=="/> + <Group Address="14271" Key="oKGio6SlpqeoqaqrrK2urw=="/> + </GroupAddresses> + <!-- Keyings exported for the whole project (for backup or diagnostic purposes) shall contain one "Device" entry for every secure device. --> + <!-- For interface keyings the "Devices" list is optional and shall only be present if sequence numbers are known for devices referenced as interface group senders. --> + <!-- The "ToolKey" attribute shall only be present if the keying was exported for the whole project. --> + <!-- The "SeqNr" attribute is optional and shall only be present if a received sequence number for a given device is known. --> + <!-- ToolKey := Base64( AES128-CBC( PBKDF2( HMAC-SHA256, KeyringPassword, "1.keyring.ets.knx.org", 65536, 128), MSB128(SHA256(this.Keyring.Created)), Device.ToolKey as byte[])) --> (1) + <!-- Secured IP-enabled devices need to have the "ManagementPassword" attribute. An "Authentication" attribute containing the device's authenication code is optional. --> + <!-- ManagementPassword := Base64( AES128-CBC( PBKDF2( HMAC-SHA256, KeyringPassword, "1.keyring.ets.knx.org", 65536, 128), MSB128(SHA256(this.Keyring.Created)), RandomBytes(8)+PKCS#7Padding( Device.ManagementPassword, 24) as byte[])) --> (1) + <!-- Authentication := Base64( AES128-CBC( PBKDF2( HMAC-SHA256, KeyringPassword, "1.keyring.ets.knx.org", 65536, 128), MSB128(SHA256(this.Keyring.Created)), RandomBytes(8)+PKCS#7Padding( Device.AthenticationCode, 24) as byte[])) --> (1) + <!-- The "ManagementPassword" and "Authentication" attributes shall only be present if the keying was exported for the whole project. --> + <Devices> + <Device IndividualAddress="1.1.1" ToolKey="sLGys7S1tre4ubq7vL2+vw==" SequenceNumber="45678"/> + <Device IndividualAddress="1.1.2" ToolKey="sLGys7S1tre4ubq7vL2+vw==" SequenceNumber="34567"/> + <Device IndividualAddress="1.1.3" ToolKey="sLGys7S1tre4ubq7vL2+vw==" SequenceNumber="23456"/> + <Device IndividualAddress="1.1.4" ToolKey="sLGys7S1tre4ubq7vL2+vw==" SequenceNumber="12345"/> + <Device IndividualAddress="2.1.0" ToolKey="sLGys7S1tre4ubq7vL2+vw==" SequenceNumber="1234" ManagementPassword="A3BzNDU2Nzg5MDEyMzQnUA3BzNDU2Nzg5MDEyMzQnUw=" Authentication="A3BzNDU2Nzg5MDEyMzQnUA3BzNDU2Nzg5MDEyMzQnUw="/> + </Devices> +</Keyring>
\ No newline at end of file diff --git a/test/suite_decryption.py b/test/suite_decryption.py index e393bf4d93..88e92438bb 100644 --- a/test/suite_decryption.py +++ b/test/suite_decryption.py @@ -760,3 +760,118 @@ class case_decrypt_wireguard(subprocesstest.SubprocessTestCase): ], pcap_file='wireguard-psk.pcap') self.assertIn('2\t0', lines) self.assertIn('4\t0', lines) + +class case_decrypt_knxip(subprocesstest.SubprocessTestCase): + # Capture files for these tests contain single telegrams. + # For realistic (live captured) KNX/IP telegram sequences, see: + # https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=14825 + + def test_knxip_data_security_decryption_ok(self): + '''KNX/IP: Data Security decryption OK''' + # capture_file contains KNX/IP ConfigReq DataSec PropExtValueWriteCon telegram + capture_file = os.path.join(config.capture_dir, 'knxip_DataSec.pcap') + self.runProcess((config.cmd_tshark, + '-r', capture_file, + '-o', 'kip.key_1:00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F', + ), + env=config.test_env) + self.assertTrue(self.grepOutput(' DataSec ')) + self.assertTrue(self.grepOutput(' PropExtValueWriteCon ')) + + def test_knxip_data_security_decryption_fails(self): + '''KNX/IP: Data Security decryption fails''' + # capture_file contains KNX/IP ConfigReq DataSec PropExtValueWriteCon telegram + capture_file = os.path.join(config.capture_dir, 'knxip_DataSec.pcap') + self.runProcess((config.cmd_tshark, + '-r', capture_file, + '-o', 'kip.key_1:""', # "" is really necessary, otherwise test fails + ), + env=config.test_env) + self.assertTrue(self.grepOutput(' DataSec ')) + self.assertFalse(self.grepOutput(' PropExtValueWriteCon ')) + + def test_knxip_secure_wrapper_decryption_ok(self): + '''KNX/IP: SecureWrapper decryption OK''' + # capture_file contains KNX/IP SecureWrapper RoutingInd telegram + capture_file = os.path.join(config.capture_dir, 'knxip_SecureWrapper.pcap') + self.runProcess((config.cmd_tshark, + '-r', capture_file, + '-o', 'kip.key_1:00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F', + ), + env=config.test_env) + self.assertTrue(self.grepOutput(' SecureWrapper ')) + self.assertTrue(self.grepOutput(' RoutingInd ')) + + def test_knxip_secure_wrapper_decryption_fails(self): + '''KNX/IP: SecureWrapper decryption fails''' + # capture_file contains KNX/IP SecureWrapper RoutingInd telegram + capture_file = os.path.join(config.capture_dir, 'knxip_SecureWrapper.pcap') + self.runProcess((config.cmd_tshark, + '-r', capture_file, + '-o', 'kip.key_1:""', # "" is really necessary, otherwise test fails + ), + env=config.test_env) + self.assertTrue(self.grepOutput(' SecureWrapper ')) + self.assertFalse(self.grepOutput(' RoutingInd ')) + + def test_knxip_timer_notify_authentication_ok(self): + '''KNX/IP: TimerNotify authentication OK''' + # capture_file contains KNX/IP TimerNotify telegram + capture_file = os.path.join(config.capture_dir, 'knxip_TimerNotify.pcap') + self.runProcess((config.cmd_tshark, + '-r', capture_file, + '-o', 'kip.key_1:00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F', + ), + env=config.test_env) + self.assertTrue(self.grepOutput(' TimerNotify ')) + self.assertTrue(self.grepOutput(' OK$')) + + def test_knxip_timer_notify_authentication_fails(self): + '''KNX/IP: TimerNotify authentication fails''' + # capture_file contains KNX/IP TimerNotify telegram + capture_file = os.path.join(config.capture_dir, 'knxip_TimerNotify.pcap') + self.runProcess((config.cmd_tshark, + '-r', capture_file, + '-o', 'kip.key_1:""', # "" is really necessary, otherwise test fails + ), + env=config.test_env) + self.assertTrue(self.grepOutput(' TimerNotify ')) + self.assertFalse(self.grepOutput(' OK$')) + + def test_knxip_keyring_xml_import(self): + '''KNX/IP: keyring.xml import''' + # key_file "keyring.xml" contains KNX decryption keys + key_file = os.path.join(config.key_dir, 'knx_keyring.xml') + # capture_file is empty + capture_file = os.path.join(config.capture_dir, 'empty.pcap') + # Write extracted key info to stdout + self.runProcess((config.cmd_tshark, + '-o', 'kip.key_file:' + key_file, + '-o', 'kip.key_info_file:-', + '-r', capture_file, + ), + env=config.test_env) + self.assertTrue(self.grepOutput('^MCA 224[.]0[.]23[.]12 key A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF$')) + self.assertTrue(self.grepOutput('^GA 1/7/131 sender 1[.]1[.]1$')) + self.assertTrue(self.grepOutput('^GA 1/7/131 sender 1[.]1[.]3$')) + self.assertTrue(self.grepOutput('^GA 1/7/131 sender 1[.]1[.]4$')) + self.assertTrue(self.grepOutput('^GA 1/7/132 sender 1[.]1[.]2$')) + self.assertTrue(self.grepOutput('^GA 1/7/132 sender 1[.]1[.]4$')) + self.assertTrue(self.grepOutput('^GA 6/7/191 sender 1[.]1[.]1$')) + self.assertTrue(self.grepOutput('^GA 0/1/0 sender 1[.]1[.]1$')) + self.assertTrue(self.grepOutput('^GA 0/1/0 sender 1[.]1[.]3$')) + self.assertTrue(self.grepOutput('^GA 0/1/0 sender 1[.]1[.]4$')) + self.assertTrue(self.grepOutput('^GA 0/1/0 key A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF$')) + self.assertTrue(self.grepOutput('^GA 1/7/131 key A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF$')) + self.assertTrue(self.grepOutput('^GA 1/7/132 key A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF$')) + self.assertTrue(self.grepOutput('^GA 6/7/191 key A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]1 key B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]1 SeqNr 45678$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]2 key B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]2 SeqNr 34567$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]3 key B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]3 SeqNr 23456$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]4 key B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]4 SeqNr 12345$')) + self.assertTrue(self.grepOutput('^IA 2[.]1[.]0 key B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF$')) + self.assertTrue(self.grepOutput('^IA 2[.]1[.]0 SeqNr 1234$')) |