KNX-IP: new KNXnet/IP dissector
authorRalf Nasilowski <Ralf.Nasilowski@ise.de>
Thu, 16 Aug 2018 08:49:31 +0000 (10:49 +0200)
committerRoland Knall <rknall@gmail.com>
Tue, 16 Oct 2018 09:03:43 +0000 (09:03 +0000)
The new KNXnet/IP dissector replaces the old KNXnet/IP dissector.

The new KNXnet/IP dissector supports the new KNX features
- A_MemoryExtended services
- A_PropertyExt services
- KNX Data Security
- KNXnet/IP Core V2
- KNXnet/IP Device Management V2
- KNXnet/IP Tunneling V2
- KNXnet/IP Routing V2
- KNXnet/IP Security

Change-Id: I3d1d716ef03d16d2720e6a1fcb23c2243d1cd956
Reviewed-on: https://code.wireshark.org/review/29155
Petri-Dish: Roland Knall <rknall@gmail.com>
Tested-by: Petri Dish Buildbot
Reviewed-by: Peter Wu <peter@lekensteyn.nl>
Reviewed-by: Roland Knall <rknall@gmail.com>
12 files changed:
epan/dissectors/CMakeLists.txt
epan/dissectors/packet-cemi.c [new file with mode: 0644]
epan/dissectors/packet-knxip.c [new file with mode: 0644]
epan/dissectors/packet-knxip.h [new file with mode: 0644]
epan/dissectors/packet-knxip_decrypt.c [new file with mode: 0644]
epan/dissectors/packet-knxip_decrypt.h [new file with mode: 0644]
epan/dissectors/packet-knxnetip.c [deleted file]
test/captures/knxip_DataSec.pcap [new file with mode: 0644]
test/captures/knxip_SecureWrapper.pcap [new file with mode: 0644]
test/captures/knxip_TimerNotify.pcap [new file with mode: 0644]
test/keys/knx_keyring.xml [new file with mode: 0644]
test/suite_decryption.py

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