diff options
Diffstat (limited to 'epan/dissectors/packet-cemi.c')
-rw-r--r-- | epan/dissectors/packet-cemi.c | 3485 |
1 files changed, 3485 insertions, 0 deletions
diff --git a/epan/dissectors/packet-cemi.c b/epan/dissectors/packet-cemi.c new file mode 100644 index 00000000..2717c943 --- /dev/null +++ b/epan/dissectors/packet-cemi.c @@ -0,0 +1,3485 @@ +/* packet-cemi.c + * Routines for cEMI (Common External Message Interface) dissection + * By Jan Kessler <kessler@ise.de> + * Copyright 2004, Jan Kessler <kessler@ise.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 "packet-knxip.h" + +void proto_register_cemi(void); +void proto_reg_handoff_cemi(void); + +/* 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 } +}; + +/* 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 } +}; + +/* 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( wmem_allocator_t *pool, const guint8* key, const guint8* encrypted, gint encrypted_size, const guint8* cemi, gint cemi_size ) +{ + guint8 ctr_0[ KNX_KEY_LENGTH ]; + guint8 b_0[ KNX_KEY_LENGTH ]; + guint8 mac[ KNX_KEY_LENGTH ]; + 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( pool, 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( pool, a_bytes ); + + if( memcmp( mac, decrypted + p_length, 4 ) != 0 ) + { + // Wrong mac. Return 0. + wmem_free( pool, 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( wmem_allocator_t *pool, 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; + 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( pool, key, encrypted, encrypted_size, cemi, cemi_size ); + + if( decrypted ) + { + 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( pool, key, encrypted, encrypted_size, cemi, cemi_size ); + + if( decrypted ) + { + 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( pool, key, encrypted, encrypted_size, cemi, cemi_size ); + + if( decrypted ) + { + 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( pool, key, encrypted, encrypted_size, cemi, cemi_size ); + + if( decrypted ) + { + break; + } + } + } + + if( decrypted ) + { + guint8 count; + + snprintf( output, output_max, "key" ); + + for( count = 16; count; --count ) + { + while( *output ) { ++output; --output_max; } + snprintf( output, output_max, " %02X", *key++ ); + } + } + else + { + 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; + } + + proto_tree_add_expert_format( list, pinfo, KIP_ERROR, tvb, offset, 0, "? Object Index: 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 ); + offset += 2; + + *p_offset = offset; + 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; + } + + proto_tree_add_expert_format( list, pinfo, KIP_ERROR, tvb, offset, 0, "? Property ID: 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; + } + + proto_tree_add_expert_format( list, pinfo, KIP_ERROR, tvb, offset, 0, "? Property Index: 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 */ + 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->cinfo; + 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->cinfo; + 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_tree_add_expert_format( cemi_list, pinfo, KIP_ERROR, tvb, offset, 0, "? Object Instance: 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" PRIX32, x ); + proto_item_append_text( cemi_node, ", X=$%06" PRIX32, 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_tree_add_expert_format( cemi_list, pinfo, KIP_ERROR, tvb, offset, 0, "? Level: 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 ) + { + proto_tree_add_expert_format( cemi_list, pinfo, KIP_ERROR, tvb, offset, 0, "? Data: 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 ); + proto_tree_add_item( list, hf_cemi_scf_t, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( list, hf_cemi_scf_sai, tvb, offset, 1, ENC_BIG_ENDIAN ); + proto_tree_add_item( list, hf_cemi_scf_sbc, tvb, offset, 1, ENC_BIG_ENDIAN ); + 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" PRIX64, 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( pinfo->pool, 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 ); + } + + proto_tree_add_data( tree, tvb2, 0, size2, NULL, NULL, "Embedded APDU", NULL, NULL ); + 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( pinfo->pool, 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 ) + { + proto_tree_add_expert_format( cemi_list, pinfo, KIP_ERROR, tvb, offset, 0, "? Count: 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 ); + } + 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 ) + { + proto_tree_add_expert_format( cemi_list, pinfo, KIP_ERROR, tvb, offset, 0, "? TPCI: 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 ) + { + 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_list = proto_tree_add_subtree( cemi_list, tvb, offset, 0, ett_cemi_ai, &ai_node, "? Additional Info" ); + proto_tree_add_expert_format( ai_list, pinfo, KIP_ERROR, tvb, offset, 0, "? Length: 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, "? " ); + proto_tree_add_expert_format( aie_list, pinfo, KIP_ERROR, tvb, offset, 0, "? Length: 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 ) + { + proto_tree_add_expert_format( cemi_list, pinfo, KIP_ERROR, tvb, offset, 0, "? Ctrl1: 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 ) + { + proto_tree_add_expert_format( cemi_list, pinfo, KIP_ERROR, tvb, offset, 0, "? Ctrl2: 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) */ + + 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 ); + 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 */ + snprintf( text, sizeof text, "%u.%u.%u", (dest_addr >> 12) & 0xF, (dest_addr >> 8) & 0xF, dest_addr & 0xFF ); + } + else + { + /* Group Address */ + 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 ) + { + proto_tree_add_expert_format( cemi_list, pinfo, KIP_ERROR, tvb, offset, 0, "? Length: 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_BOOLEAN, 8, TFS(&tfs_no_yes), 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_BOOLEAN, 8, TFS(&tfs_no_yes), 0x02, NULL, HFILL } }, + { &hf_cemi_ce, { "Confirmation Error", "cemi.ce", FT_BOOLEAN, 8, TFS(&tfs_no_yes), 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.ml", FT_UINT8, BASE_HEX, NULL, 0x3F, NULL, HFILL } }, + { &hf_cemi_ad_channel, { "Channel", "cemi.ad.ch", FT_UINT8, BASE_HEX, NULL, 0x3F, NULL, HFILL } }, + { &hf_cemi_ad_type, { "Data", "cemi.ad.type", 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: + */ |