/* packet-s7comm.c
 *
 * Author:      Thomas Wiens, 2014 (th.wiens@gmx.de)
 * Description: Wireshark dissector for S7-Communication
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "config.h"

#include <epan/packet.h>

#include "packet-s7comm.h"
#include "packet-s7comm_szl_ids.h"

#define PROTO_TAG_S7COMM                    "S7COMM"

/* Min. telegram length for heuristic check */
#define S7COMM_MIN_TELEGRAM_LENGTH          10

/* Protocol identifier */
#define S7COMM_PROT_ID                      0x32

/* Wireshark ID of the S7COMM protocol */
static int proto_s7comm = -1;

/* Forward declarations */
void proto_reg_handoff_s7comm(void);
void proto_register_s7comm (void);

/**************************************************************************
 * Function call tree of the dissect process

dissect_s7comm()
    +
    +-------s7comm_decode_req_resp()
    +        +        +
    +     response  request
    +        +        +
    +        +        +------ s7comm_decode_param_item()
    +        +        +       s7comm_decode_response_read_data()
    +        +        +
    +        +        +------ s7comm_decode_pdu_setup_communication()
    +        +        +------ s7comm_decode_plc_controls_param_hex1x()
    +        +        +------ s7comm_decode_plc_controls_param_hex28()
    +        +        +------ s7comm_decode_plc_controls_param_hex29()
    +        +
    +        +------ s7comm_decode_response_read_data()
    +        +------ s7comm_decode_response_write_data()
    +        +------ s7comm_decode_pdu_setup_communication()
    +
    +
    +-------s7comm_decode_ud()
             +
             +------ s7comm_decode_ud_prog_subfunc()
             +                  +
             +                  +------- s7comm_decode_ud_prog_vartab_req_item()
             +                  +------- s7comm_decode_ud_prog_vartab_res_item()
             +                  +------- s7comm_decode_ud_prog_reqdiagdata()
             +
             +------ s7comm_decode_ud_cyclic_subfunc()
             +                  +
             +                  +------- s7comm_decode_param_item()
             +                  +------- s7comm_decode_response_read_data()
             +
             +------ s7comm_decode_ud_block_subfunc()
             +------ s7comm_decode_ud_szl_subfunc()
             +                  +
             +                  +------- s7comm_decode_szl_id_XXXX_idx_XXXX()
             +
             +------ s7comm_decode_ud_security_subfunc()
             +------ s7comm_decode_ud_time_subfunc()

 **************************************************************************/



/**************************************************************************
 * PDU types
 */
#define S7COMM_ROSCTR_JOB                   0x01
#define S7COMM_ROSCTR_ACK                   0x02
#define S7COMM_ROSCTR_ACK_DATA              0x03
#define S7COMM_ROSCTR_USERDATA              0x07

static const value_string rosctr_names[] = {
    { S7COMM_ROSCTR_JOB,                    "Job" },        /* Request: job with acknowledgement */
    { S7COMM_ROSCTR_ACK,                    "Ack" },        /* acknowledgement without additional field */
    { S7COMM_ROSCTR_ACK_DATA,               "Ack_Data" },   /* Response: acknowledgement with additional field */
    { S7COMM_ROSCTR_USERDATA,               "Userdata" },
    { 0,                                    NULL }
};
/**************************************************************************
 * Error classes in header
 */
#define S7COMM_ERRCLS_NONE                  0x00
#define S7COMM_ERRCLS_APPREL                0x81
#define S7COMM_ERRCLS_OBJDEF                0x82
#define S7COMM_ERRCLS_RESSOURCE             0x83
#define S7COMM_ERRCLS_SERVICE               0x84
#define S7COMM_ERRCLS_SUPPLIES              0x85
#define S7COMM_ERRCLS_ACCESS                0x87

static const value_string errcls_names[] = {
    { S7COMM_ERRCLS_NONE,                   "No error" },
    { S7COMM_ERRCLS_APPREL,                 "Application relationship" },
    { S7COMM_ERRCLS_OBJDEF,                 "Object definition" },
    { S7COMM_ERRCLS_RESSOURCE,              "No ressources available" },
    { S7COMM_ERRCLS_SERVICE,                "Error on service processing" },
    { S7COMM_ERRCLS_SUPPLIES,               "Error on supplies" },
    { S7COMM_ERRCLS_ACCESS,                 "Access error" },
    { 0,                                    NULL }
};

/**************************************************************************
 * Error code in parameter part
 */
#define S7COMM_PERRCOD_NO_ERROR                     0x0000
#define S7COMM_PERRCOD_INVALID_BLOCK_TYPE_NUM       0x0110
#define S7COMM_PERRCOD_INVALID_PARAM                0x0112
#define S7COMM_PERRCOD_PG_RESOURCE_ERROR            0x011A
#define S7COMM_PERRCOD_PLC_RESOURCE_ERROR           0x011B
#define S7COMM_PERRCOD_PROTOCOL_ERROR               0x011C
#define S7COMM_PERRCOD_USER_BUFFER_TOO_SHORT        0x011F
#define S7COMM_PERRCOD_REQ_INI_ERR                  0x0141
#define S7COMM_PERRCOD_VERSION_MISMATCH             0x01C0
#define S7COMM_PERRCOD_NOT_IMPLEMENTED              0x01F0
#define S7COMM_PERRCOD_L7_INVALID_CPU_STATE         0x8001
#define S7COMM_PERRCOD_L7_PDU_SIZE_ERR              0x8500
#define S7COMM_PERRCOD_L7_INVALID_SZL_ID            0xD401
#define S7COMM_PERRCOD_L7_INVALID_INDEX             0xD402
#define S7COMM_PERRCOD_L7_DGS_CONN_ALREADY_ANNOU    0xD403
#define S7COMM_PERRCOD_L7_MAX_USER_NB               0xD404
#define S7COMM_PERRCOD_L7_DGS_FKT_PAR_SYNTAX_ERR    0xD405
#define S7COMM_PERRCOD_L7_NO_INFO                   0xD406
#define S7COMM_PERRCOD_L7_PRT_FKT_PAR_SYNTAX_ERR    0xD601
#define S7COMM_PERRCOD_L7_INVALID_VAR_ADDR          0xD801
#define S7COMM_PERRCOD_L7_UNKNOWN_REQ               0xD802
#define S7COMM_PERRCOD_L7_INVALID_REQ_STATUS        0xD803

static const value_string param_errcode_names[] = {
    { S7COMM_PERRCOD_NO_ERROR,                      "No error" },
    { S7COMM_PERRCOD_INVALID_BLOCK_TYPE_NUM,        "Invalid block type number" },
    { S7COMM_PERRCOD_INVALID_PARAM,                 "Invalid parameter" },
    { S7COMM_PERRCOD_PG_RESOURCE_ERROR,             "PG ressource error" },
    { S7COMM_PERRCOD_PLC_RESOURCE_ERROR,            "PLC ressource error" },
    { S7COMM_PERRCOD_PROTOCOL_ERROR,                "Protocol error" },
    { S7COMM_PERRCOD_USER_BUFFER_TOO_SHORT,         "User buffer too short" },
    { S7COMM_PERRCOD_REQ_INI_ERR,                   "Request error" },
    { S7COMM_PERRCOD_VERSION_MISMATCH,              "Version mismatch" },
    { S7COMM_PERRCOD_NOT_IMPLEMENTED,               "Not implemented" },
    { S7COMM_PERRCOD_L7_INVALID_CPU_STATE,          "L7 invalid CPU state" },
    { S7COMM_PERRCOD_L7_PDU_SIZE_ERR,               "L7 PDU size error" },
    { S7COMM_PERRCOD_L7_INVALID_SZL_ID,             "L7 invalid SZL ID" },
    { S7COMM_PERRCOD_L7_INVALID_INDEX,              "L7 invalid index" },
    { S7COMM_PERRCOD_L7_DGS_CONN_ALREADY_ANNOU,     "L7 DGS Connection already announced" },
    { S7COMM_PERRCOD_L7_MAX_USER_NB,                "L7 Max user NB" },
    { S7COMM_PERRCOD_L7_DGS_FKT_PAR_SYNTAX_ERR,     "L7 DGS function parameter syntax error" },
    { S7COMM_PERRCOD_L7_NO_INFO,                    "L7 no info" },
    { S7COMM_PERRCOD_L7_PRT_FKT_PAR_SYNTAX_ERR,     "L7 PRT function parameter syntax error" },
    { S7COMM_PERRCOD_L7_INVALID_VAR_ADDR,           "L7 invalid variable address" },
    { S7COMM_PERRCOD_L7_UNKNOWN_REQ,                "L7 unknown request" },
    { S7COMM_PERRCOD_L7_INVALID_REQ_STATUS,         "L7 invalid request status" },
    { 0,                                    NULL }
};

/**************************************************************************
 * Function codes in parameter part
 */
#define S7COMM_SERV_CPU                     0x00
#define S7COMM_SERV_SETUPCOMM               0xF0
#define S7COMM_SERV_READVAR                 0x04
#define S7COMM_SERV_WRITEVAR                0x05

#define S7COMM_FUNCREQUESTDOWNLOAD          0x1A
#define S7COMM_FUNCDOWNLOADBLOCK            0x1B
#define S7COMM_FUNCDOWNLOADENDED            0x1C
#define S7COMM_FUNCSTARTUPLOAD              0x1D
#define S7COMM_FUNCUPLOAD                   0x1E
#define S7COMM_FUNCENDUPLOAD                0x1F
#define S7COMM_FUNC_PLC_CONTROL             0x28
#define S7COMM_FUNC_PLC_STOP                0x29

static const value_string param_functionnames[] = {
    { S7COMM_SERV_CPU,                      "CPU services" },
    { S7COMM_SERV_SETUPCOMM,                "Setup communication" },
    { S7COMM_SERV_READVAR,                  "Read Var" },
    { S7COMM_SERV_WRITEVAR,                 "Write Var" },
    /* Block management services */
    { S7COMM_FUNCREQUESTDOWNLOAD,           "Request download" },
    { S7COMM_FUNCDOWNLOADBLOCK,             "Download block" },
    { S7COMM_FUNCDOWNLOADENDED,             "Download ended" },
    { S7COMM_FUNCSTARTUPLOAD,               "Start upload" },
    { S7COMM_FUNCUPLOAD,                    "Upload" },
    { S7COMM_FUNCENDUPLOAD,                 "End upload" },
    { S7COMM_FUNC_PLC_CONTROL,              "PLC Control" },
    { S7COMM_FUNC_PLC_STOP,                 "PLC Stop" },
    { 0,                                    NULL }
};
/**************************************************************************
 * Area names
 */
#define S7COMM_AREA_SYSINFO                 0x03        /* System info of 200 family */
#define S7COMM_AREA_SYSFLAGS                0x05        /* System flags of 200 family */
#define S7COMM_AREA_ANAIN                   0x06        /* analog inputs of 200 family */
#define S7COMM_AREA_ANAOUT                  0x07        /* analog outputs of 200 family */
#define S7COMM_AREA_P                       0x80        /* direct peripheral access */
#define S7COMM_AREA_INPUTS                  0x81
#define S7COMM_AREA_OUTPUTS                 0x82
#define S7COMM_AREA_FLAGS                   0x83
#define S7COMM_AREA_DB                      0x84        /* data blocks */
#define S7COMM_AREA_DI                      0x85        /* instance data blocks */
#define S7COMM_AREA_LOCAL                   0x86        /* local data (should not be accessible over network) */
#define S7COMM_AREA_V                       0x87        /* previous (Vorgaenger) local data (should not be accessible over network)  */
#define S7COMM_AREA_COUNTER                 28          /* S7 counters */
#define S7COMM_AREA_TIMER                   29          /* S7 timers */
#define S7COMM_AREA_COUNTER200              30          /* IEC counters (200 family) */
#define S7COMM_AREA_TIMER200                31          /* IEC timers (200 family) */

static const value_string item_areanames[] = {
    { S7COMM_AREA_SYSINFO,                  "System info of 200 family" },
    { S7COMM_AREA_SYSFLAGS,                 "System flags of 200 family" },
    { S7COMM_AREA_ANAIN,                    "Analog inputs of 200 family" },
    { S7COMM_AREA_ANAOUT,                   "Analog outputs of 200 family" },
    { S7COMM_AREA_P,                        "Direct peripheral access (P)" },
    { S7COMM_AREA_INPUTS,                   "Inputs (I)" },
    { S7COMM_AREA_OUTPUTS,                  "Outputs (Q)" },
    { S7COMM_AREA_FLAGS,                    "Flags (M)" },
    { S7COMM_AREA_DB,                       "Data blocks (DB)" },
    { S7COMM_AREA_DI,                       "Instance data blocks (DI)" },
    { S7COMM_AREA_LOCAL,                    "Local data (L)" },
    { S7COMM_AREA_V,                        "Unknown yet (V)" },
    { S7COMM_AREA_COUNTER,                  "S7 counters (C)" },
    { S7COMM_AREA_TIMER,                    "S7 timers (T)" },
    { S7COMM_AREA_COUNTER200,               "IEC counters (200 family)" },
    { S7COMM_AREA_TIMER200,                 "IEC timers (200 family)" },
    { 0,                                    NULL }
};
/**************************************************************************
 * Transport sizes in item data
 */
    /* types of 1 byte length */
#define S7COMM_TRANSPORT_SIZE_BIT           1
#define S7COMM_TRANSPORT_SIZE_BYTE          2
#define S7COMM_TRANSPORT_SIZE_CHAR          3
    /* types of 2 bytes length */
#define S7COMM_TRANSPORT_SIZE_WORD          4
#define S7COMM_TRANSPORT_SIZE_INT           5
    /* types of 4 bytes length */
#define S7COMM_TRANSPORT_SIZE_DWORD         6
#define S7COMM_TRANSPORT_SIZE_DINT          7
#define S7COMM_TRANSPORT_SIZE_REAL          8
    /* Special types */
#define S7COMM_TRANSPORT_SIZE_DATE          9
#define S7COMM_TRANSPORT_SIZE_TOD           10
#define S7COMM_TRANSPORT_SIZE_TIME          11
#define S7COMM_TRANSPORT_SIZE_S5TIME        12
#define S7COMM_TRANSPORT_SIZE_DT            15
    /* Timer or counter */
#define S7COMM_TRANSPORT_SIZE_COUNTER       28
#define S7COMM_TRANSPORT_SIZE_TIMER         29
#define S7COMM_TRANSPORT_SIZE_IEC_COUNTER   30
#define S7COMM_TRANSPORT_SIZE_IEC_TIMER     31
#define S7COMM_TRANSPORT_SIZE_HS_COUNTER    32
static const value_string item_transportsizenames[] = {
    { S7COMM_TRANSPORT_SIZE_BIT,            "BIT" },
    { S7COMM_TRANSPORT_SIZE_BYTE,           "BYTE" },
    { S7COMM_TRANSPORT_SIZE_CHAR,           "CHAR" },
    { S7COMM_TRANSPORT_SIZE_WORD,           "WORD" },
    { S7COMM_TRANSPORT_SIZE_INT,            "INT" },
    { S7COMM_TRANSPORT_SIZE_DWORD,          "DWORD" },
    { S7COMM_TRANSPORT_SIZE_DINT,           "DINT" },
    { S7COMM_TRANSPORT_SIZE_REAL,           "REAL" },
    { S7COMM_TRANSPORT_SIZE_TOD,            "TOD" },
    { S7COMM_TRANSPORT_SIZE_TIME,           "TIME" },
    { S7COMM_TRANSPORT_SIZE_S5TIME,         "S5TIME" },
    { S7COMM_TRANSPORT_SIZE_DT,             "DATE_AND_TIME" },
    { S7COMM_TRANSPORT_SIZE_COUNTER,        "COUNTER" },
    { S7COMM_TRANSPORT_SIZE_TIMER,          "TIMER" },
    { S7COMM_TRANSPORT_SIZE_IEC_COUNTER,    "IEC TIMER" },
    { S7COMM_TRANSPORT_SIZE_IEC_TIMER,      "IEC COUNTER" },
    { S7COMM_TRANSPORT_SIZE_HS_COUNTER,     "HS COUNTER" },
    { 0,                                    NULL }
};

/**************************************************************************
 * Syntax Ids of variable specification
 */
#define S7COMM_SYNTAXID_S7ANY               0x10        /* Address data S7-Any pointer-like DB1.DBX10.2 */
#define S7COMM_SYNTAXID_PBC_ID              0x13        /* R_ID for PBC */
#define S7COMM_SYNTAXID_DRIVEESANY          0xa2        /* seen on Drive ES Starter with routing over S7 */
#define S7COMM_SYNTAXID_1200SYM             0xb2        /* Symbolic address mode of S7-1200 */
#define S7COMM_SYNTAXID_DBREAD              0xb0        /* Kind of DB block read, seen only at an S7-400 */
#define S7COMM_SYNTAXID_NCK                 0x82        /* Sinumerik NCK HMI access */

static const value_string item_syntaxid_names[] = {
    { S7COMM_SYNTAXID_S7ANY,                "S7ANY" },
    { S7COMM_SYNTAXID_PBC_ID,               "PBC-R_ID" },
    { S7COMM_SYNTAXID_DRIVEESANY,           "DRIVEESANY" },
    { S7COMM_SYNTAXID_1200SYM,              "1200SYM" },
    { S7COMM_SYNTAXID_DBREAD,               "DBREAD" },
    { S7COMM_SYNTAXID_NCK,                  "NCK" },
    { 0,                                    NULL }
};

/**************************************************************************
 * Transport sizes in data
 */
#define S7COMM_DATA_TRANSPORT_SIZE_NULL     0
#define S7COMM_DATA_TRANSPORT_SIZE_BBIT     3           /* bit access, len is in bits */
#define S7COMM_DATA_TRANSPORT_SIZE_BBYTE    4           /* byte/word/dword acces, len is in bits */
#define S7COMM_DATA_TRANSPORT_SIZE_BINT     5           /* integer access, len is in bits */
#define S7COMM_DATA_TRANSPORT_SIZE_BDINT    6           /* integer access, len is in bytes */
#define S7COMM_DATA_TRANSPORT_SIZE_BREAL    7           /* real access, len is in bytes */
#define S7COMM_DATA_TRANSPORT_SIZE_BSTR     9           /* octet string, len is in bytes */

static const value_string data_transportsizenames[] = {
    { S7COMM_DATA_TRANSPORT_SIZE_NULL,      "NULL" },
    { S7COMM_DATA_TRANSPORT_SIZE_BBIT,      "BIT" },
    { S7COMM_DATA_TRANSPORT_SIZE_BBYTE,     "BYTE/WORD/DWORD" },
    { S7COMM_DATA_TRANSPORT_SIZE_BINT,      "INTEGER" },
    { S7COMM_DATA_TRANSPORT_SIZE_BDINT,     "DINTEGER" },
    { S7COMM_DATA_TRANSPORT_SIZE_BREAL,     "REAL" },
    { S7COMM_DATA_TRANSPORT_SIZE_BSTR,      "OCTET STRING" },
    { 0,                                    NULL }
};
/**************************************************************************
 * Returnvalues of an item response
 */

const value_string s7comm_item_return_valuenames[] = {
    { S7COMM_ITEM_RETVAL_RESERVED,              "Reserved" },
    { S7COMM_ITEM_RETVAL_DATA_HW_FAULT,         "Hardware error" },
    { S7COMM_ITEM_RETVAL_DATA_ACCESS_FAULT,     "Accessing the object not allowed" },
    { S7COMM_ITEM_RETVAL_DATA_OUTOFRANGE,       "Invalid address" },
    { S7COMM_ITEM_RETVAL_DATA_NOT_SUP,          "Data type not supported" },
    { S7COMM_ITEM_RETVAL_DATA_SIZEMISMATCH,     "Data type inconsistent" },
    { S7COMM_ITEM_RETVAL_DATA_ERR,              "Object does not exist" },
    { S7COMM_ITEM_RETVAL_DATA_OK,               "Success" },
    { 0,                                        NULL }
};
/**************************************************************************
 * Block Types
 */
#define S7COMM_BLOCKTYPE_OB                 '8'
#define S7COMM_BLOCKTYPE_DB                 'A'
#define S7COMM_BLOCKTYPE_SDB                'B'
#define S7COMM_BLOCKTYPE_FC                 'C'
#define S7COMM_BLOCKTYPE_SFC                'D'
#define S7COMM_BLOCKTYPE_FB                 'E'
#define S7COMM_BLOCKTYPE_SFB                'F'

static const value_string blocktype_names[] = {
    { S7COMM_BLOCKTYPE_OB,                  "OB" },
    { S7COMM_BLOCKTYPE_DB,                  "DB" },
    { S7COMM_BLOCKTYPE_SDB,                 "SDB" },
    { S7COMM_BLOCKTYPE_FC,                  "FC" },
    { S7COMM_BLOCKTYPE_SFC,                 "SFC" },
    { S7COMM_BLOCKTYPE_FB,                  "FB" },
    { S7COMM_BLOCKTYPE_SFB,                 "SFB" },
    { 0,                                    NULL }
};

/**************************************************************************
 * Subblk types
 */
#define S7COMM_SUBBLKTYPE_OB                0x08
#define S7COMM_SUBBLKTYPE_DB                0x0a
#define S7COMM_SUBBLKTYPE_SDB               0x0b
#define S7COMM_SUBBLKTYPE_FC                0x0c
#define S7COMM_SUBBLKTYPE_SFC               0x0d
#define S7COMM_SUBBLKTYPE_FB                0x0e
#define S7COMM_SUBBLKTYPE_SFB               0x0f

static const value_string subblktype_names[] = {
    { S7COMM_SUBBLKTYPE_OB,                 "OB" },
    { S7COMM_SUBBLKTYPE_DB,                 "DB" },
    { S7COMM_SUBBLKTYPE_SDB,                "SDB" },
    { S7COMM_SUBBLKTYPE_FC,                 "FC" },
    { S7COMM_SUBBLKTYPE_SFC,                "SFC" },
    { S7COMM_SUBBLKTYPE_FB,                 "FB" },
    { S7COMM_SUBBLKTYPE_SFB,                "SFB" },
    { 0,                                    NULL }
};

/**************************************************************************
 * Block security
 */
#define S7COMM_BLOCKSECURITY_OFF            0
#define S7COMM_BLOCKSECURITY_KNOWHOWPROTECT 3

static const value_string blocksecurity_names[] = {
    { S7COMM_BLOCKSECURITY_OFF,             "None" },
    { S7COMM_BLOCKSECURITY_KNOWHOWPROTECT,  "Kow How Protect" },
    { 0,                                    NULL }
};
/**************************************************************************
 * Block Languages
 */
static const value_string blocklanguage_names[] = {
    { 0x00,                                 "Not defined" },
    { 0x01,                                 "AWL" },
    { 0x02,                                 "KOP" },
    { 0x03,                                 "FUP" },
    { 0x04,                                 "SCL" },
    { 0x05,                                 "DB" },
    { 0x06,                                 "GRAPH" },
    { 0x07,                                 "SDB" },
    { 0x08,                                 "CPU-DB" },                     /* DB was created from Plc programm (CREAT_DB) */
    { 0x11,                                 "SDB (after overall reset)" },  /* another SDB, don't know what it means, in SDB 1 and SDB 2, uncertain*/
    { 0x12,                                 "SDB (Routing)" },              /* another SDB, in SDB 999 and SDB 1000 (routing information), uncertain */
    { 0x29,                                 "ENCRYPT" },                    /* block is encrypted with S7-Block-Privacy */
    { 0,                                    NULL }
};

/**************************************************************************
 * Names of types in userdata parameter part
 */

static const value_string userdata_type_names[] = {
    { S7COMM_UD_TYPE_PUSH,                  "Push" },         /* this type occurs when 2 telegrams follow after another from the same partner, or initiated from PLC */
    { S7COMM_UD_TYPE_REQ,                   "Request" },
    { S7COMM_UD_TYPE_RES,                   "Response" },
    { 0,                                    NULL }
};

/**************************************************************************
 * Userdata Parameter, last data unit
 */
#define S7COMM_UD_LASTDATAUNIT_YES          0x00
#define S7COMM_UD_LASTDATAUNIT_NO           0x01

static const value_string userdata_lastdataunit_names[] = {
    { S7COMM_UD_LASTDATAUNIT_YES,           "Yes" },
    { S7COMM_UD_LASTDATAUNIT_NO,            "No" },
    { 0,                                    NULL }
};

/**************************************************************************
 * Names of Function groups in userdata parameter part
 */
#define S7COMM_UD_FUNCGROUP_PROG            0x1
#define S7COMM_UD_FUNCGROUP_CYCLIC          0x2
#define S7COMM_UD_FUNCGROUP_BLOCK           0x3
#define S7COMM_UD_FUNCGROUP_CPU             0x4
#define S7COMM_UD_FUNCGROUP_SEC             0x5                     /* Security funnctions e.g. plc password */
#define S7COMM_UD_FUNCGROUP_PBC             0x6                     /* PBC = Programmable Block Communication (PBK in german) */
#define S7COMM_UD_FUNCGROUP_TIME            0x7

static const value_string userdata_functiongroup_names[] = {
    { S7COMM_UD_FUNCGROUP_PROG,             "Programmer commands" },
    { S7COMM_UD_FUNCGROUP_CYCLIC,           "Cyclic data" },        /* to read data from plc without a request */
    { S7COMM_UD_FUNCGROUP_BLOCK,            "Block functions" },
    { S7COMM_UD_FUNCGROUP_CPU,              "CPU functions" },
    { S7COMM_UD_FUNCGROUP_SEC,              "Security" },
    { S7COMM_UD_FUNCGROUP_PBC,              "PBC BSEND/BRECV" },
    { S7COMM_UD_FUNCGROUP_TIME,             "Time functions" },
    { 0,                                    NULL }
};

/**************************************************************************
 * Vartab: Typ of data in data part, first two bytes
 */
#define S7COMM_UD_SUBF_PROG_VARTAB_TYPE_REQ 0x14
#define S7COMM_UD_SUBF_PROG_VARTAB_TYPE_RES 0x04

static const value_string userdata_prog_vartab_type_names[] = {
    { S7COMM_UD_SUBF_PROG_VARTAB_TYPE_REQ,  "Request" },            /* Request of data areas */
    { S7COMM_UD_SUBF_PROG_VARTAB_TYPE_RES,  "Response" },           /* Response from plc with data */
    { 0,                                    NULL }
};

/**************************************************************************
 * Vartab: area of data request
 *
 * Low       Hi
 * 0=M       1=BYTE
 * 1=E       2=WORD
 * 2=A       3=DWORD
 * 3=PEx
 * 7=DB
 * 54=TIMER
 * 64=COUNTER
 */
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_MB      0x01
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_MW      0x02
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_MD      0x03
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_EB      0x11
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_EW      0x12
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_ED      0x13
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_AB      0x21
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_AW      0x22
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_AD      0x23
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_PEB     0x31
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_PEW     0x32
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_PED     0x33
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBB     0x71
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBW     0x72
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBD     0x73
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_T       0x54
#define S7COMM_UD_SUBF_PROG_VARTAB_AREA_C       0x64

static const value_string userdata_prog_vartab_area_names[] = {
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_MB,       "MB" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_MW,       "MW" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_MD,       "MD" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_EB,       "IB" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_EW,       "IW" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_ED,       "ID" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_AB,       "QB" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_AW,       "QW" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_AD,       "QD" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_PEB,      "PIB" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_PEW,      "PIW" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_PED,      "PID" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBB,      "DBB" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBW,      "DBW" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBD,      "DBD" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_T,        "TIMER" },
    { S7COMM_UD_SUBF_PROG_VARTAB_AREA_C,        "COUNTER" },
    { 0,                                        NULL }
};

/**************************************************************************
 * Names of userdata subfunctions in group 1 (Programmer commands)
 */
#define S7COMM_UD_SUBF_PROG_REQDIAGDATA1    0x01
#define S7COMM_UD_SUBF_PROG_VARTAB1         0x02
#define S7COMM_UD_SUBF_PROG_ERASE           0x0c
#define S7COMM_UD_SUBF_PROG_READDIAGDATA    0x0e
#define S7COMM_UD_SUBF_PROG_REMOVEDIAGDATA  0x0f
#define S7COMM_UD_SUBF_PROG_FORCE           0x10
#define S7COMM_UD_SUBF_PROG_REQDIAGDATA2    0x13

static const value_string userdata_prog_subfunc_names[] = {
    { S7COMM_UD_SUBF_PROG_REQDIAGDATA1,     "Request diag data (Type 1)" },     /* Start online block view */
    { S7COMM_UD_SUBF_PROG_VARTAB1,          "VarTab" },                         /* Variable table */
    { S7COMM_UD_SUBF_PROG_READDIAGDATA,     "Read diag data" },                 /* online block view */
    { S7COMM_UD_SUBF_PROG_REMOVEDIAGDATA,   "Remove diag data" },               /* Stop online block view */
    { S7COMM_UD_SUBF_PROG_ERASE,            "Erase" },
    { S7COMM_UD_SUBF_PROG_FORCE,            "Forces" },
    { S7COMM_UD_SUBF_PROG_REQDIAGDATA2,     "Request diag data (Type2)" },      /* Start online block view */
    { 0,                                    NULL }
};

/**************************************************************************
 * Names of userdata subfunctions in group 2 (cyclic data)
 */
#define S7COMM_UD_SUBF_CYCLIC_MEM           0x01
#define S7COMM_UD_SUBF_CYCLIC_UNSUBSCRIBE   0x04

static const value_string userdata_cyclic_subfunc_names[] = {
    { S7COMM_UD_SUBF_CYCLIC_MEM,            "Memory" },                         /* read data from memory (DB/M/etc.) */
    { S7COMM_UD_SUBF_CYCLIC_UNSUBSCRIBE,    "Unsubscribe" },                    /* Unsubcribe (diable) cyclic data */
    { 0,                                    NULL }
};

/**************************************************************************
 * Names of userdata subfunctions in group 3 (Block functions)
 */
#define S7COMM_UD_SUBF_BLOCK_LIST           0x01
#define S7COMM_UD_SUBF_BLOCK_LISTTYPE       0x02
#define S7COMM_UD_SUBF_BLOCK_BLOCKINFO      0x03

static const value_string userdata_block_subfunc_names[] = {
    { S7COMM_UD_SUBF_BLOCK_LIST,            "List blocks" },
    { S7COMM_UD_SUBF_BLOCK_LISTTYPE,        "List blocks of type" },
    { S7COMM_UD_SUBF_BLOCK_BLOCKINFO,       "Get block info" },
    { 0,                                    NULL }
};

/**************************************************************************
 * Names of userdata subfunctions in group 4 (CPU functions)
 */

static const value_string userdata_cpu_subfunc_names[] = {
    { S7COMM_UD_SUBF_CPU_READSZL,           "Read SZL" },
    { S7COMM_UD_SUBF_CPU_MSGS,              "Message service" },                /* Header constant is also different here */
    { S7COMM_UD_SUBF_CPU_TRANSSTOP,         "Transition to STOP" },             /* PLC changed state to STOP */
    { S7COMM_UD_SUBF_CPU_ALARMIND,          "ALARM indication" },               /* PLC is indicating a ALARM message */
    { S7COMM_UD_SUBF_CPU_ALARMINIT,         "ALARM initiate" },                 /* HMI/SCADA initiating ALARM subscription */
    { S7COMM_UD_SUBF_CPU_ALARMACK1,         "ALARM ack 1" },                    /* Alarm was acknowledged in HMI/SCADA */
    { S7COMM_UD_SUBF_CPU_ALARMACK2,         "ALARM ack 2" },                    /* Alarm was acknowledged in HMI/SCADA */
    { 0,                                    NULL }
};

/**************************************************************************
 * Names of userdata subfunctions in group 5 (Security?)
 */
#define S7COMM_UD_SUBF_SEC_PASSWD           0x01

static const value_string userdata_sec_subfunc_names[] = {
    { S7COMM_UD_SUBF_SEC_PASSWD,            "PLC password" },
    { 0,                                    NULL }
};

/**************************************************************************
 * Names of userdata subfunctions in group 7 (Time functions)
 */
#define S7COMM_UD_SUBF_TIME_READ            0x01
#define S7COMM_UD_SUBF_TIME_SET             0x02
#define S7COMM_UD_SUBF_TIME_READF           0x03
#define S7COMM_UD_SUBF_TIME_SET2            0x04

static const value_string userdata_time_subfunc_names[] = {
    { S7COMM_UD_SUBF_TIME_READ,             "Read clock" },
    { S7COMM_UD_SUBF_TIME_SET,              "Set clock" },
    { S7COMM_UD_SUBF_TIME_READF,            "Read clock (following)" },
    { S7COMM_UD_SUBF_TIME_SET2,             "Set clock" },
    { 0,                                    NULL }
};

/*******************************************************************************************************
 * Weekday names in DATE_AND_TIME
 */
static const value_string weekdaynames[] = {
    { 0,                                    "Undefined" },
    { 1,                                    "Sunday" },
    { 2,                                    "Monday" },
    { 3,                                    "Tuesday" },
    { 4,                                    "Wednesday" },
    { 5,                                    "Thursday" },
    { 6,                                    "Friday" },
    { 7,                                    "Saturday" },
    { 0,                                    NULL }
};

/**************************************************************************
 **************************************************************************/

/**************************************************************************
 * Flags for LID access
 */
#define S7COMM_TIA1200_VAR_ENCAPS_LID       0x2
#define S7COMM_TIA1200_VAR_ENCAPS_IDX       0x3
#define S7COMM_TIA1200_VAR_OBTAIN_LID       0x4
#define S7COMM_TIA1200_VAR_OBTAIN_IDX       0x5
#define S7COMM_TIA1200_VAR_PART_START       0x6
#define S7COMM_TIA1200_VAR_PART_LEN         0x7

static const value_string tia1200_var_lid_flag_names[] = {
    { S7COMM_TIA1200_VAR_ENCAPS_LID,        "Encapsulated LID" },
    { S7COMM_TIA1200_VAR_ENCAPS_IDX,        "Encapsulated Index" },
    { S7COMM_TIA1200_VAR_OBTAIN_LID,        "Obtain by LID" },
    { S7COMM_TIA1200_VAR_OBTAIN_IDX,        "Obtain by Index" },
    { S7COMM_TIA1200_VAR_PART_START,        "Part Start Address" },
    { S7COMM_TIA1200_VAR_PART_LEN,          "Part Length" },
    { 0,                                    NULL }
};

/**************************************************************************
 * TIA 1200 Area Names for variable access
 */
#define S7COMM_TIA1200_VAR_ITEM_AREA1_DB    0x8a0e              /* Reading DB, 2 byte DB-Number following */
#define S7COMM_TIA1200_VAR_ITEM_AREA1_IQMCT 0x0000              /* Reading I/Q/M/C/T, 2 Byte detail area following */

static const value_string tia1200_var_item_area1_names[] = {
    { S7COMM_TIA1200_VAR_ITEM_AREA1_DB,     "DB" },
    { S7COMM_TIA1200_VAR_ITEM_AREA1_IQMCT,  "IQMCT" },
    { 0,                                    NULL }
};

#define S7COMM_TIA1200_VAR_ITEM_AREA2_I     0x50
#define S7COMM_TIA1200_VAR_ITEM_AREA2_Q     0x51
#define S7COMM_TIA1200_VAR_ITEM_AREA2_M     0x52
#define S7COMM_TIA1200_VAR_ITEM_AREA2_C     0x53
#define S7COMM_TIA1200_VAR_ITEM_AREA2_T     0x54

static const value_string tia1200_var_item_area2_names[] = {
    { S7COMM_TIA1200_VAR_ITEM_AREA2_I,      "Inputs (I)" },
    { S7COMM_TIA1200_VAR_ITEM_AREA2_Q,      "Outputs (Q)" },
    { S7COMM_TIA1200_VAR_ITEM_AREA2_M,      "Flags (M)" },
    { S7COMM_TIA1200_VAR_ITEM_AREA2_C,      "Counter (C)" },
    { S7COMM_TIA1200_VAR_ITEM_AREA2_T,      "Timer (T)" },
    { 0,                                    NULL }
};

/**************************************************************************
 * NCK areas
 */
#define S7COMM_NCK_AREA_N_NCK               0
#define S7COMM_NCK_AREA_B_MODEGROUP         1
#define S7COMM_NCK_AREA_C_CHANNEL           2
#define S7COMM_NCK_AREA_A_AXIS              3
#define S7COMM_NCK_AREA_T_TOOL              4
#define S7COMM_NCK_AREA_V_FEEDDRIVE         5
#define S7COMM_NCK_AREA_H_MAINDRIVE         6
#define S7COMM_NCK_AREA_M_MMC               7

static const value_string nck_area_names[] = {
    { S7COMM_NCK_AREA_N_NCK,                "N - NCK" },
    { S7COMM_NCK_AREA_B_MODEGROUP,          "B - Mode group" },
    { S7COMM_NCK_AREA_C_CHANNEL,            "C - Channel" },
    { S7COMM_NCK_AREA_A_AXIS,               "A - Axis" },
    { S7COMM_NCK_AREA_T_TOOL,               "T - Tool" },
    { S7COMM_NCK_AREA_V_FEEDDRIVE,          "V - Feed drive" },
    { S7COMM_NCK_AREA_H_MAINDRIVE,          "M - Main drive" },
    { S7COMM_NCK_AREA_M_MMC,                "M - MMC" },
    { 0,                                    NULL }
};

static const value_string nck_module_names[] = {
    { 0x10,                                 "Y - Global system data" },
    { 0x11,                                 "YNCFL - NCK instruction groups" },
    { 0x12,                                 "FU - NCU global settable frames" },
    { 0x13,                                 "FA - Active NCU global frames" },
    { 0x14,                                 "TO - Tool data" },
    { 0x15,                                 "RP - Arithmetic parameters" },
    { 0x16,                                 "SE - Setting data" },
    { 0x17,                                 "SGUD - SGUD-Block" },
    { 0x18,                                 "LUD - Local userdata" },
    { 0x19,                                 "TC - Toolholder parameters" },
    { 0x1a,                                 "M - Machine data" },
    { 0x1c,                                 "WAL - Working area limitation" },
    { 0x1e,                                 "DIAG - Internal diagnostic data" },
    { 0x1f,                                 "CC - Unknown" },
    { 0x20,                                 "FE - Channel-specific external frame" },
    { 0x21,                                 "TD - Tool data: General data" },
    { 0x22,                                 "TS - Tool edge data: Monitoring data" },
    { 0x23,                                 "TG - Tool data: Grinding-specific data" },
    { 0x24,                                 "TU - Tool data" },
    { 0x25,                                 "TUE - Tool edge data, userdefined data" },
    { 0x26,                                 "TV - Tool data, directory" },
    { 0x27,                                 "TM - Magazine data: General data" },
    { 0x28,                                 "TP - Magazine data: Location data" },
    { 0x29,                                 "TPM - Magazine data: Multiple assignment of location data" },
    { 0x2a,                                 "TT - Magazine data: Location typ" },
    { 0x2b,                                 "TMV - Magazine data: Directory" },
    { 0x2c,                                 "TMC - Magazine data: Configuration data" },
    { 0x2d,                                 "MGUD - MGUD-Block" },
    { 0x2e,                                 "UGUD - UGUD-Block" },
    { 0x2f,                                 "GUD4 - GUD4-Block" },
    { 0x30,                                 "GUD5 - GUD5-Block" },
    { 0x31,                                 "GUD6 - GUD6-Block" },
    { 0x32,                                 "GUD7 - GUD7-Block" },
    { 0x33,                                 "GUD8 - GUD8-Block" },
    { 0x34,                                 "GUD9 - GUD9-Block" },
    { 0x35,                                 "PA - Channel-specific protection zones" },
    { 0x36,                                 "GD1 - SGUD-Block GD1" },
    { 0x37,                                 "NIB - State data: Nibbling" },
    { 0x38,                                 "ETP - Types of events" },
    { 0x39,                                 "ETPD - Data lists for protocolling" },
    { 0x3a,                                 "SYNACT - Channel-specific synchronous actions" },
    { 0x3b,                                 "DIAGN - Diagnostic data" },
    { 0x3c,                                 "VSYN - Channel-specific user variables for synchronous actions" },
    { 0x3d,                                 "TUS - Tool data: user monitoring data" },
    { 0x3e,                                 "TUM - Tool data: user magazine data" },
    { 0x3f,                                 "TUP - Tool data: user magatine place data" },
    { 0x40,                                 "TF - Parametrizing, return parameters of _N_TMGETT, _N_TSEARC" },
    { 0x41,                                 "FB - Channel-specific base frames" },
    { 0x42,                                 "SSP2 - State data: Spindle" },
    { 0x43,                                 "PUD - programmglobale Benutzerdaten" },
    { 0x44,                                 "TOS - Edge-related location-dependent fine total offsets" },
    { 0x45,                                 "TOST - Edge-related location-dependent fine total offsets, transformed" },
    { 0x46,                                 "TOE - Edge-related coarse total offsets, setup offsets" },
    { 0x47,                                 "TOET - Edge-related coarse total offsets, transformed setup offsets" },
    { 0x48,                                 "AD - Adapter data" },
    { 0x49,                                 "TOT - Edge data: Transformed offset data" },
    { 0x4a,                                 "AEV - Working offsets: Directory" },
    { 0x4b,                                 "YFAFL - NCK instruction groups (Fanuc)" },
    { 0x4c,                                 "FS - System-Frame" },
    { 0x4d,                                 "SD - Servo data" },
    { 0x4e,                                 "TAD - Application-specific data" },
    { 0x4f,                                 "TAO - Aplication-specific cutting edge data" },
    { 0x50,                                 "TAS - Application-specific monitoring data" },
    { 0x51,                                 "TAM - Application-specific magazine data" },
    { 0x52,                                 "TAP - Application-specific magazine location data" },
    { 0x53,                                 "MEM - Unknown" },
    { 0x54,                                 "SALUC - Alarm actions: List in reverse chronological order" },
    { 0x55,                                 "AUXFU - Auxiliary functions" },
    { 0x56,                                 "TDC - Tool/Tools" },
    { 0x57,                                 "CP - Generic coupling" },
    { 0x6e,                                 "SDME - Unknown" },
    { 0x6f,                                 "SPARPI - Program pointer on interruption" },
    { 0x70,                                 "SEGA - State data: Geometry axes in tool offset memory (extended)" },
    { 0x71,                                 "SEMA - State data: Machine axes (extended)" },
    { 0x72,                                 "SSP - State data: Spindle" },
    { 0x73,                                 "SGA - State data: Geometry axes in tool offset memory" },
    { 0x74,                                 "SMA - State data: Machine axes" },
    { 0x75,                                 "SALAL - Alarms: List organized according to time" },
    { 0x76,                                 "SALAP - Alarms: List organized according to priority" },
    { 0x77,                                 "SALA - Alarms: List organized according to time" },
    { 0x78,                                 "SSYNAC - Synchronous actions" },
    { 0x79,                                 "SPARPF - Program pointers for block search and stop run" },
    { 0x7a,                                 "SPARPP - Program pointer in automatic operation" },
    { 0x7b,                                 "SNCF - Active G functions" },
    { 0x7d,                                 "SPARP - Part program information" },
    { 0x7e,                                 "SINF - Part-program-specific status data" },
    { 0x7f,                                 "S - State data" },
    { 0x80,                                 "0x80 - Unknown" },
    { 0x81,                                 "0x81 - Unknown" },
    { 0x82,                                 "0x82 - Unknown" },
    { 0x83,                                 "0x83 - Unknown" },
    { 0x84,                                 "0x84 - Unknown" },
    { 0x85,                                 "0x85 - Unknown" },
    { 0xfd,                                 "0 - Internal" },
    { 0,                                    NULL }
};
static value_string_ext nck_module_names_ext = VALUE_STRING_EXT_INIT(nck_module_names);

static gint hf_s7comm_tia1200_item_reserved1 = -1;          /* 1 Byte Reserved (always 0xff?) */
static gint hf_s7comm_tia1200_item_area1 = -1;              /* 2 Byte2 Root area (DB or IQMCT) */
static gint hf_s7comm_tia1200_item_area2 = -1;              /* 2 Bytes detail area (I/Q/M/C/T) */
static gint hf_s7comm_tia1200_item_area2unknown = -1;       /* 2 Bytes detail area for possible unknown or not seen areas */
static gint hf_s7comm_tia1200_item_dbnumber = -1;           /* 2 Bytes DB number */
static gint hf_s7comm_tia1200_item_crc = -1;                /* 4 Bytes CRC */

static gint hf_s7comm_tia1200_substructure_item = -1;       /* Substructure */
static gint hf_s7comm_tia1200_var_lid_flags = -1;           /* LID Flags */
static gint hf_s7comm_tia1200_item_value = -1;

/**************************************************************************
 **************************************************************************/

/* Header Block */
static gint hf_s7comm_header = -1;
static gint hf_s7comm_header_protid = -1;                   /* Header Byte  0 */
static gint hf_s7comm_header_rosctr = -1;                   /* Header Bytes 1 */
static gint hf_s7comm_header_redid = -1;                    /* Header Bytes 2, 3 */
static gint hf_s7comm_header_pduref = -1;                   /* Header Bytes 4, 5 */
static gint hf_s7comm_header_parlg = -1;                    /* Header Bytes 6, 7 */
static gint hf_s7comm_header_datlg = -1;                    /* Header Bytes 8, 9 */
static gint hf_s7comm_header_errcls = -1;                   /* Header Byte 10, only available at type 2 or 3 */
static gint hf_s7comm_header_errcod = -1;                   /* Header Byte 11, only available at type 2 or 3 */
/* Parameter Block */
static gint hf_s7comm_param = -1;
static gint hf_s7comm_param_errcod = -1;                    /* Parameter part: Error code */
static gint hf_s7comm_param_service = -1;                   /* Parameter part: service */
static gint hf_s7comm_param_itemcount = -1;                 /* Parameter part: item count */
static gint hf_s7comm_param_data = -1;                      /* Parameter part: data */
static gint hf_s7comm_param_neg_pdu_length = -1;            /* Parameter part: Negotiate PDU length */
static gint hf_s7comm_param_setup_reserved1 = -1;           /* Parameter part: Reserved byte in communication setup pdu*/

static gint hf_s7comm_param_maxamq_calling = -1;            /* Parameter part: Max AmQ calling */
static gint hf_s7comm_param_maxamq_called = -1;             /* Parameter part: Max AmQ called */

/* Item data */
static gint hf_s7comm_param_item = -1;
static gint hf_s7comm_param_subitem = -1;                   /* Substructure */
static gint hf_s7comm_item_varspec = -1;                    /* Variable specification */
static gint hf_s7comm_item_varspec_length = -1;             /* Length of following address specification */
static gint hf_s7comm_item_syntax_id = -1;                  /* Syntax Id */
static gint hf_s7comm_item_transport_size = -1;             /* Transport size, 1 Byte*/
static gint hf_s7comm_item_length = -1;                     /* length, 2 Bytes*/
static gint hf_s7comm_item_db = -1;                         /* DB/M/E/A, 2 Bytes */
static gint hf_s7comm_item_area = -1;                       /* Area code, 1 byte */
static gint hf_s7comm_item_address = -1;                    /* Bit address, 3 Bytes */
static gint hf_s7comm_item_address_byte = -1;               /* address: Byte address */
static gint hf_s7comm_item_address_bit = -1;                /* address: Bit address */
static gint hf_s7comm_item_address_nr = -1;                 /* address: Timer/Counter/block number */
/* Special variable read with Syntax-Id 0xb0 (DBREAD) */
static gint hf_s7comm_item_dbread_numareas = -1;            /* Number of areas following, 1 Byte*/
static gint hf_s7comm_item_dbread_length = -1;              /* length, 1 Byte*/
static gint hf_s7comm_item_dbread_db = -1;                  /* DB number, 2 Bytes*/
static gint hf_s7comm_item_dbread_startadr = -1;            /* Start address, 2 Bytes*/
/* NCK access with Syntax-Id 0x82 */
static gint hf_s7comm_item_nck_areaunit = -1;               /* Bitmask: aaauuuuu: a=area, u=unit */
static gint hf_s7comm_item_nck_area = -1;
static gint hf_s7comm_item_nck_unit = -1;
static gint hf_s7comm_item_nck_column = -1;
static gint hf_s7comm_item_nck_line = -1;
static gint hf_s7comm_item_nck_module = -1;
static gint hf_s7comm_item_nck_linecount = -1;

static gint hf_s7comm_data = -1;
static gint hf_s7comm_data_returncode = -1;                 /* return code, 1 byte */
static gint hf_s7comm_data_transport_size = -1;             /* transport size 1 byte */
static gint hf_s7comm_data_length = -1;                     /* Length of data, 2 Bytes */

static gint hf_s7comm_data_item = -1;

static gint hf_s7comm_readresponse_data = -1;
static gint hf_s7comm_data_fillbyte = -1;

/* timefunction: s7 timestamp */
static gint hf_s7comm_data_ts = -1;
static gint hf_s7comm_data_ts_reserved = -1;
static gint hf_s7comm_data_ts_year1 = -1;                   /* first byte of BCD coded year, should be ignored */
static gint hf_s7comm_data_ts_year2 = -1;                   /* second byte of BCD coded year, if 00...89 then it's 2000...2089, else 1990...1999*/
static gint hf_s7comm_data_ts_month = -1;
static gint hf_s7comm_data_ts_day = -1;
static gint hf_s7comm_data_ts_hour = -1;
static gint hf_s7comm_data_ts_minute = -1;
static gint hf_s7comm_data_ts_second = -1;
static gint hf_s7comm_data_ts_millisecond = -1;
static gint hf_s7comm_data_ts_weekday = -1;

/* userdata, block services */
static gint hf_s7comm_userdata_data = -1;

static gint hf_s7comm_userdata_param_head = -1;
static gint hf_s7comm_userdata_param_len = -1;
static gint hf_s7comm_userdata_param_reqres2 = -1;          /* unknown */
static gint hf_s7comm_userdata_param_type = -1;
static gint hf_s7comm_userdata_param_funcgroup = -1;
static gint hf_s7comm_userdata_param_subfunc_prog = -1;
static gint hf_s7comm_userdata_param_subfunc_cyclic = -1;
static gint hf_s7comm_userdata_param_subfunc_block = -1;
static gint hf_s7comm_userdata_param_subfunc_cpu = -1;
static gint hf_s7comm_userdata_param_subfunc_sec = -1;
static gint hf_s7comm_userdata_param_subfunc_time = -1;
static gint hf_s7comm_userdata_param_subfunc = -1;          /* for all other subfunctions */
static gint hf_s7comm_userdata_param_seq_num = -1;
static gint hf_s7comm_userdata_param_dataunitref = -1;
static gint hf_s7comm_userdata_param_dataunit = -1;

/* block functions, list blocks of type */
static gint hf_s7comm_ud_blockinfo_block_type = -1;         /* Block type, 1 byte, stringlist blocktype_names */
static gint hf_s7comm_ud_blockinfo_block_num = -1;          /* Block number, 2 bytes as int */
static gint hf_s7comm_ud_blockinfo_block_cnt = -1;          /* Count, 2 bytes as int */
static gint hf_s7comm_ud_blockinfo_block_flags = -1;        /* Block flags (unknown), 1 byte */
static gint hf_s7comm_ud_blockinfo_block_lang = -1;         /* Block language, 1 byte, stringlist blocklanguage_names */
/* block functions, get block infos */
static gint hf_s7comm_ud_blockinfo_block_num_ascii = -1;    /* Block number, 5 bytes, ASCII*/
static gint hf_s7comm_ud_blockinfo_filesys = -1;            /* Filesystem, 1 byte, ASCII*/
static gint hf_s7comm_ud_blockinfo_res_const1 = -1;         /* Constant 1, 1 byte, HEX*/
static gint hf_s7comm_ud_blockinfo_res_infolength = -1;     /* Length of Info, 2 bytes as int */
static gint hf_s7comm_ud_blockinfo_res_unknown2 = -1;       /* Unknown blockinfo 2, 2 bytes, HEX*/
static gint hf_s7comm_ud_blockinfo_res_const3 = -1;         /* Constant 3, 2 bytes, ASCII */
static gint hf_s7comm_ud_blockinfo_res_unknown = -1;        /* Unknown byte(s) */
static gint hf_s7comm_ud_blockinfo_subblk_type = -1;        /* Subblk type, 1 byte, stringlist subblktype_names */
static gint hf_s7comm_ud_blockinfo_load_mem_len = -1;       /* Length load memory, 4 bytes, int */
static gint hf_s7comm_ud_blockinfo_blocksecurity = -1;      /* Block Security, 4 bytes, stringlist blocksecurity_names*/
static gint hf_s7comm_ud_blockinfo_interface_timestamp = -1;/* Interface Timestamp, string */
static gint hf_s7comm_ud_blockinfo_code_timestamp = -1;     /* Code Timestamp, string */
static gint hf_s7comm_ud_blockinfo_ssb_len = -1;            /* SSB length, 2 bytes, int */
static gint hf_s7comm_ud_blockinfo_add_len = -1;            /* ADD length, 2 bytes, int */
static gint hf_s7comm_ud_blockinfo_localdata_len = -1;      /* Length localdata, 2 bytes, int */
static gint hf_s7comm_ud_blockinfo_mc7_len = -1;            /* Length MC7 code, 2 bytes, int */
static gint hf_s7comm_ud_blockinfo_author = -1;             /* Author, 8 bytes, ASCII */
static gint hf_s7comm_ud_blockinfo_family = -1;             /* Family, 8 bytes, ASCII */
static gint hf_s7comm_ud_blockinfo_headername = -1;         /* Name (Header), 8 bytes, ASCII */
static gint hf_s7comm_ud_blockinfo_headerversion = -1;      /* Version (Header), 8 bytes, ASCII */
static gint hf_s7comm_ud_blockinfo_checksum = -1;           /* Block checksum, 2 bytes, HEX */
static gint hf_s7comm_ud_blockinfo_reserved1 = -1;          /* Reserved 1, 4 bytes, HEX */
static gint hf_s7comm_ud_blockinfo_reserved2 = -1;          /* Reserved 2, 4 bytes, HEX */

static gint hf_s7comm_userdata_blockinfo_flags = -1;        /* Some flags in Block info response */
static gint hf_s7comm_userdata_blockinfo_linked = -1;       /* Some flags in Block info response */
static gint hf_s7comm_userdata_blockinfo_standard_block = -1;
static gint hf_s7comm_userdata_blockinfo_nonretain = -1;    /* Some flags in Block info response */
static gint ett_s7comm_userdata_blockinfo_flags = -1;
static const int *s7comm_userdata_blockinfo_flags_fields[] = {
    &hf_s7comm_userdata_blockinfo_linked,
    &hf_s7comm_userdata_blockinfo_standard_block,
    &hf_s7comm_userdata_blockinfo_nonretain,
    NULL
};

/* Programmer commands, diagnostic data */
static gint hf_s7comm_diagdata_req_askheadersize = -1;      /* Ask header size, 2 bytes as int */
static gint hf_s7comm_diagdata_req_asksize = -1;            /* Ask size, 2 bytes as int */
static gint hf_s7comm_diagdata_req_unknown = -1;            /* for all unknown bytes */
static gint hf_s7comm_diagdata_req_answersize = -1;         /* Answer size, 2 bytes as int */
static gint hf_s7comm_diagdata_req_block_type = -1;         /* Block type, 1 byte, stringlist subblktype_names */
static gint hf_s7comm_diagdata_req_block_num = -1;          /* Block number, 2 bytes as int */
static gint hf_s7comm_diagdata_req_startaddr_awl = -1;      /* Start address AWL, 2 bytes as int */
static gint hf_s7comm_diagdata_req_saz = -1;                /* Step address counter (SAZ), 2 bytes as int */
static gint hf_s7comm_diagdata_req_number_of_lines = -1;    /* Number of lines, 1 byte as int */
static gint hf_s7comm_diagdata_req_line_address = -1;       /* Address, 2 bytes as int */

/* Flags for requested registers in diagnostic data telegrams */
static gint hf_s7comm_diagdata_registerflag = -1;           /* Registerflags */
static gint hf_s7comm_diagdata_registerflag_stw = -1;       /* STW = Status word */
static gint hf_s7comm_diagdata_registerflag_accu1 = -1;     /* Accumulator 1 */
static gint hf_s7comm_diagdata_registerflag_accu2 = -1;     /* Accumulator 2 */
static gint hf_s7comm_diagdata_registerflag_ar1 = -1;       /* Addressregister 1 */
static gint hf_s7comm_diagdata_registerflag_ar2 = -1;       /* Addressregister 2 */
static gint hf_s7comm_diagdata_registerflag_db1 = -1;       /* Datablock register 1 */
static gint hf_s7comm_diagdata_registerflag_db2 = -1;       /* Datablock register 2 */
static gint ett_s7comm_diagdata_registerflag = -1;
static const int *s7comm_diagdata_registerflag_fields[] = {
    &hf_s7comm_diagdata_registerflag_stw,
    &hf_s7comm_diagdata_registerflag_accu1,
    &hf_s7comm_diagdata_registerflag_accu2,
    &hf_s7comm_diagdata_registerflag_ar1,
    &hf_s7comm_diagdata_registerflag_ar2,
    &hf_s7comm_diagdata_registerflag_db1,
    &hf_s7comm_diagdata_registerflag_db2,
    NULL
};

/* Function 0x28 (PLC control functions) */
static gint hf_s7comm_data_plccontrol_part1_unknown = -1;   /* Unknown bytes */
static gint hf_s7comm_data_plccontrol_part1_len = -1;       /* Length part 1 in bytes, 2 Bytes Int */
static gint hf_s7comm_data_plccontrol_argument = -1;        /* Argument, 2 Bytes as char */
static gint hf_s7comm_data_plccontrol_block_cnt = -1;       /* Number of blocks, 1 Byte as int */
static gint hf_s7comm_data_plccontrol_part1_unknown2 = -1;  /* Unknown 1 byte */
static gint hf_s7comm_data_plccontrol_block_unknown = -1;   /* Unknown 1 byte, as ASCII */
static gint hf_s7comm_data_plccontrol_block_type = -1;      /* Block type, 1 Byte, stringlist blocktype_names */
static gint hf_s7comm_data_plccontrol_block_num = -1;       /* Block number, 5 Bytes as ASCII */
static gint hf_s7comm_data_plccontrol_dest_filesys = -1;    /* Destination filesystem, 1 Byte as ASCII */
static gint hf_s7comm_data_plccontrol_part2_len = -1;       /* Length part 2 in bytes, 1 Byte as Int */
static gint hf_s7comm_data_plccontrol_pi_service = -1;      /* PI (program invocation) Service, String as ASCII */

/* block control functions */
static gint hf_s7comm_data_blockcontrol_unknown1 = -1;      /* for all unknown bytes in blockcontrol */
static gint hf_s7comm_data_blockcontrol_errorcode = -1;     /* Error code 2 bytes as int, 0 is no error */
static gint hf_s7comm_data_blockcontrol_part1_len = -1;     /* Length of part 1, 1 byte as int */
static gint hf_s7comm_data_blockcontrol_file_ident = -1;    /* File identifier, as ASCII */
static gint hf_s7comm_data_blockcontrol_block_unknown = -1; /* unknown prefix before block type, ASCII */
static gint hf_s7comm_data_blockcontrol_block_type = -1;    /* Block type, 1 Byte, stringlist blocktype_names */
static gint hf_s7comm_data_blockcontrol_block_num = -1;     /* Block number, 5 Bytes, als ASCII */
static gint hf_s7comm_data_blockcontrol_dest_filesys = -1;  /* Destination filesystem, 1 Byte, ASCII */
static gint hf_s7comm_data_blockcontrol_part2_len = -1;     /* Length part 2 in bytes, 1 Byte Int */
static gint hf_s7comm_data_blockcontrol_part2_unknown = -1; /* Unknown char, ASCII */
static gint hf_s7comm_data_blockcontrol_loadmem_len = -1;   /* Length load memory in bytes, ASCII */
static gint hf_s7comm_data_blockcontrol_mc7code_len = -1;   /* Length of MC7 code in bytes, ASCII */

/* Variable table */
static gint hf_s7comm_vartab_data_type = -1;                /* Type of data, 1 byte, stringlist userdata_prog_vartab_type_names */
static gint hf_s7comm_vartab_byte_count = -1;               /* Byte count, 2 bytes, int */
static gint hf_s7comm_vartab_unknown = -1;                  /* Unknown byte(s), hex */
static gint hf_s7comm_vartab_item_count = -1;               /* Item count, 2 bytes, int */
static gint hf_s7comm_vartab_req_memory_area = -1;          /* Memory area, 1 byte, stringlist userdata_prog_vartab_area_names  */
static gint hf_s7comm_vartab_req_repetition_factor = -1;    /* Repetition factor, 1 byte as int */
static gint hf_s7comm_vartab_req_db_number = -1;            /* DB number, 2 bytes as int */
static gint hf_s7comm_vartab_req_startaddress = -1;         /* Startaddress, 2 bytes as int */

/* cyclic data */
static gint hf_s7comm_cycl_interval_timebase = -1;          /* Interval timebase, 1 byte, int */
static gint hf_s7comm_cycl_interval_time = -1;              /* Interval time, 1 byte, int */

/* PBC, Programmable Block Functions */
static gint hf_s7comm_pbc_unknown = -1;                     /* unknown, 1 byte */
static gint hf_s7comm_pbc_r_id = -1;                        /* Request ID R_ID, 4 bytes as hex */

/* These are the ids of the subtrees that we are creating */
static gint ett_s7comm = -1;                                /* S7 communication tree, parent of all other subtree */
static gint ett_s7comm_header = -1;                         /* Subtree for header block */
static gint ett_s7comm_param = -1;                          /* Subtree for parameter block */
static gint ett_s7comm_param_item = -1;                     /* Subtree for items in parameter block */
static gint ett_s7comm_param_subitem = -1;                  /* Subtree for subitems under items in parameter block */
static gint ett_s7comm_data = -1;                           /* Subtree for data block */
static gint ett_s7comm_data_item = -1;                      /* Subtree for an item in data block */
static gint ett_s7comm_item_address = -1;                   /* Subtree for an address (byte/bit) */

static const char mon_names[][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

/*******************************************************************************************************
 *
 * Converts a siemens special timestamp to a string of 25+1 bytes length (e.g. "Apr 15, 2009 12:49:30.520").
 * The timestamp is 6 bytes long, one word is the number of days since 1.1.1984, and 4 bytes millisecods of the day
 *
 *******************************************************************************************************/
static void
s7comm_get_timestring_from_s7time(tvbuff_t *tvb, guint offset, char *str, gint max)
{
    guint16 days;
    guint32 day_msec;
    struct tm *mt;
    time_t t;

    day_msec = tvb_get_ntohl(tvb, offset);
    days = tvb_get_ntohs(tvb, offset + 4);

    t = 441763200L;             /* 1.1.1984 00:00:00 */
    t += days * (24*60*60);
    t += day_msec / 1000;
    mt = gmtime(&t);
    str[0] = '\0';
    if (mt != NULL) {
        g_snprintf(str, max, "%s %2d, %d %02d:%02d:%02d.%03d", mon_names[mt->tm_mon], mt->tm_mday,
            mt->tm_year + 1900, mt->tm_hour, mt->tm_min, mt->tm_sec, day_msec % 1000);
    }
}

/*******************************************************************************************************
 *
 * Helper for time functions
 * Get int from bcd
 *
 *******************************************************************************************************/
static guint8
s7comm_guint8_from_bcd(guint8 i)
{
    return 10 * (i /16) + (i % 16);
}

/*******************************************************************************************************
 *
 * Helper for time functions
 * Add a BCD coded timestamp (10 Bytes length) to tree
 *
 *******************************************************************************************************/
static guint32
s7comm_add_timestamp_to_tree(tvbuff_t *tvb,
                             proto_tree *tree,
                             guint32 offset,
                             gboolean append_text)
{
    guint8 timestamp[10];
    guint8 i;
    guint8 tmp;
    guint8 year_org;
    guint16 msec;
    nstime_t tv;
    proto_item *item = NULL;
    proto_item *time_tree = NULL;
    struct tm mt;

    /* The low nibble of byte 10 is weekday, the high nibble the LSD of msec */
    for (i = 0;i < 9; i++) {
        timestamp[i] = s7comm_guint8_from_bcd(tvb_get_guint8(tvb, offset + i));
    }
    tmp = tvb_get_guint8(tvb, offset + 9) >> 4;
    timestamp[9] = s7comm_guint8_from_bcd(tmp);

    msec = (guint16)timestamp[8] * 10 + (guint16)timestamp[9];
    year_org = timestamp[1];
    /* year special: ignore the first byte, since some cpus give 1914 for 2014
     * if second byte is below 89, it's 2000..2089, if over 90 it's 1990..1999
     */
    if (timestamp[2] < 89) {
        timestamp[1] = 20;
    }
    /* convert time to nstime_t */
    mt.tm_year = (timestamp[1] * 100 + timestamp[2]) - 1900;
    mt.tm_mon = timestamp[3] - 1;
    mt.tm_mday = timestamp[4];
    mt.tm_hour = timestamp[5];
    mt.tm_min = timestamp[6];
    mt.tm_sec = timestamp[7];
    mt.tm_isdst = -1;
    tv.secs = mktime(&mt);
    tv.nsecs = msec * 1000000;
    item = proto_tree_add_time_format(tree, hf_s7comm_data_ts, tvb, offset, 10, &tv,
        "S7 Timestamp: %s %2d, %d %02d:%02d:%02d.%03d", mon_names[mt.tm_mon], mt.tm_mday,
        mt.tm_year + 1900, mt.tm_hour, mt.tm_min, mt.tm_sec,
        msec);
    time_tree = proto_item_add_subtree(item, ett_s7comm_data_item);

    /* timefunction: s7 timestamp */
    proto_tree_add_uint(time_tree, hf_s7comm_data_ts_reserved, tvb, offset, 1, timestamp[0]);
    offset += 1;
    proto_tree_add_uint(time_tree, hf_s7comm_data_ts_year1, tvb, offset, 1, year_org);
    offset += 1;
    proto_tree_add_uint(time_tree, hf_s7comm_data_ts_year2, tvb, offset, 1, timestamp[2]);
    offset += 1;
    proto_tree_add_uint(time_tree, hf_s7comm_data_ts_month, tvb, offset, 1, timestamp[3]);
    offset += 1;
    proto_tree_add_uint(time_tree, hf_s7comm_data_ts_day, tvb, offset, 1, timestamp[4]);
    offset += 1;
    proto_tree_add_uint(time_tree, hf_s7comm_data_ts_hour, tvb, offset, 1, timestamp[5]);
    offset += 1;
    proto_tree_add_uint(time_tree, hf_s7comm_data_ts_minute, tvb, offset, 1, timestamp[6]);
    offset += 1;
    proto_tree_add_uint(time_tree, hf_s7comm_data_ts_second, tvb, offset, 1, timestamp[7]);
    offset += 1;
    proto_tree_add_uint(time_tree, hf_s7comm_data_ts_millisecond, tvb, offset, 2, msec);
    proto_tree_add_item(time_tree, hf_s7comm_data_ts_weekday, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;

    if (append_text == TRUE) {
        proto_item_append_text(tree, "(Timestamp: %s %2d, %d %02d:%02d:%02d.%03d)", mon_names[mt.tm_mon], mt.tm_mday,
            mt.tm_year + 1900, mt.tm_hour, mt.tm_min, mt.tm_sec,
            msec);
    }
    return offset;
}

/*******************************************************************************************************
 *
 * Generate a comma separated string for registerflags
 *
 *******************************************************************************************************/
static void
make_registerflag_string(gchar *str, guint8 flags, gint max)
{
    g_strlcpy(str, "", max);
    if (flags & 0x01) g_strlcat(str, "STW, ", max);
    if (flags & 0x02) g_strlcat(str, "ACCU1, ", max);
    if (flags & 0x04) g_strlcat(str, "ACCU2, ", max);
    if (flags & 0x08) g_strlcat(str, "AR1, ", max);
    if (flags & 0x10) g_strlcat(str, "AR2, ", max);
    if (flags & 0x20) g_strlcat(str, "DB1, ", max);
    if (flags & 0x40) g_strlcat(str, "DB2, ", max);
    if (strlen(str) > 2)
        str[strlen(str) - 2 ] = '\0';
}

/*******************************************************************************************************
 *
 * Dissect the parameter details of a read/write request (Items)
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_param_item(tvbuff_t *tvb,
                          guint32 offset,
                          proto_tree *sub_tree,
                          guint8 item_no)
{
    guint32 a_address = 0;
    guint32 bytepos = 0;
    guint32 bitpos = 0;
    guint8 t_size = 0;
    guint16 len = 0;
    guint16 db = 0;
    guint16 i;
    guint8 area = 0;
    proto_item *item = NULL;
    proto_tree *item_tree = NULL;
    proto_tree *sub_item_tree = NULL;
    proto_item *address_item = NULL;
    proto_tree *address_item_tree = NULL;
    guint8 number_of_areas = 0;

    guint8 var_spec_type = 0;
    guint8 var_spec_length = 0;
    guint8 var_spec_syntax_id = 0;
    proto_item *sub_item = NULL;
    guint16 tia_var_area1 = 0;
    guint16 tia_var_area2 = 0;
    guint8 tia_lid_flags = 0;
    guint32 tia_value = 0;

    guint8 nck_area = 0;
    guint8 nck_unit = 0;
    guint16 nck_column = 0;
    guint16 nck_line = 0;
    guint8 nck_module = 0;

    /* At first check type and length of variable specification */
    var_spec_type = tvb_get_guint8(tvb, offset);
    var_spec_length = tvb_get_guint8(tvb, offset + 1);
    var_spec_syntax_id = tvb_get_guint8(tvb, offset + 2);

    /* Classic S7:  type = 0x12, len=10, syntax-id=0x10 for ANY-Pointer
     * TIA S7-1200: type = 0x12, len=14, syntax-id=0xb2 (symbolic addressing??)
     * Drive-ES Starter with routing: type = 0x12, len=10, syntax-id=0xa2 for ANY-Pointer
     */

    /* Insert a new tree for every item */
    item = proto_tree_add_item(sub_tree, hf_s7comm_param_item, tvb, offset, var_spec_length + 2, ENC_NA);
    item_tree = proto_item_add_subtree(item, ett_s7comm_param_item);

    proto_item_append_text(item, " [%d]:", item_no + 1);

    /* Item head, constant 3 bytes */
    proto_tree_add_item(item_tree, hf_s7comm_item_varspec, tvb, offset, 1, ENC_BIG_ENDIAN);
    offset += 1;
    proto_tree_add_item(item_tree, hf_s7comm_item_varspec_length, tvb, offset, 1, ENC_BIG_ENDIAN);
    offset += 1;
    proto_tree_add_item(item_tree, hf_s7comm_item_syntax_id, tvb, offset, 1, ENC_BIG_ENDIAN);
    offset += 1;

    /****************************************************************************/
    /************************** Step 7 Classic 300 400 **************************/
    if (var_spec_type == 0x12 && var_spec_length == 10 && var_spec_syntax_id == S7COMM_SYNTAXID_S7ANY) {
        /* Transport size, 1 byte */
        t_size = tvb_get_guint8(tvb, offset);
        proto_tree_add_uint(item_tree, hf_s7comm_item_transport_size, tvb, offset, 1, t_size);
        offset += 1;
        /* Length, 2 bytes */
        len = tvb_get_ntohs(tvb, offset);
        proto_tree_add_uint(item_tree, hf_s7comm_item_length, tvb, offset, 2, len);
        offset += 2;
        /* DB number, 2 bytes */
        db = tvb_get_ntohs(tvb, offset);
        proto_tree_add_uint(item_tree, hf_s7comm_item_db, tvb, offset, 2, db);
        offset += 2;
        /* Area, 1 byte */
        area = tvb_get_guint8(tvb, offset);
        proto_tree_add_uint(item_tree, hf_s7comm_item_area, tvb, offset, 1, area);
        offset += 1;
        /* Address, 3 bytes */
        a_address = tvb_get_ntoh24(tvb, offset);
        address_item = proto_tree_add_uint(item_tree, hf_s7comm_item_address, tvb, offset, 3, a_address);
        address_item_tree = proto_item_add_subtree(address_item, ett_s7comm_item_address);
        bytepos = a_address / 8;
        bitpos = a_address % 8;
        /* build a full address to show item data directly beside the item */
        switch (area) {
            case (S7COMM_AREA_P):
                proto_item_append_text(item_tree, " (P");
                break;
            case (S7COMM_AREA_INPUTS):
                proto_item_append_text(item_tree, " (I");
                break;
            case (S7COMM_AREA_OUTPUTS):
                proto_item_append_text(item_tree, " (Q");
                break;
            case (S7COMM_AREA_FLAGS):
                proto_item_append_text(item_tree, " (M");
                break;
            case (S7COMM_AREA_DB):
                proto_item_append_text(item_tree, " (DB%d.DBX", db);
                break;
            case (S7COMM_AREA_DI):
                proto_item_append_text(item_tree, " (DI%d.DIX", db);
                break;
            case (S7COMM_AREA_LOCAL):
                proto_item_append_text(item_tree, " (L");
                break;
            case (S7COMM_AREA_COUNTER):
                proto_item_append_text(item_tree, " (C");
                break;
            case (S7COMM_AREA_TIMER):
                proto_item_append_text(item_tree, " (T");
                break;
            default:
                proto_item_append_text(item_tree, " (unknown area");
                break;
        }
        if (area == S7COMM_AREA_TIMER || area == S7COMM_AREA_COUNTER) {
            proto_item_append_text(item_tree, " %d)", a_address);
            proto_tree_add_uint(address_item_tree, hf_s7comm_item_address_nr, tvb, offset, 3, a_address);
        } else {
            proto_tree_add_uint(address_item_tree, hf_s7comm_item_address_byte, tvb, offset, 3, a_address);
            proto_tree_add_uint(address_item_tree, hf_s7comm_item_address_bit, tvb, offset, 3, a_address);
            proto_item_append_text(item_tree, " %d.%d %s %d)",
                bytepos, bitpos, val_to_str(t_size, item_transportsizenames, "Unknown transport size: 0x%02x"), len);
        }
        offset += 3;
    /****************************************************************************/
    /******************** S7-400 special address mode (kind of cyclic read) *****/
    /* The response to this kind of request can't be decoded, because in the response
     * the data fields don't contain any header information. There is only one byte
     */
    } else if (var_spec_type == 0x12 && var_spec_length >= 7 && var_spec_syntax_id == S7COMM_SYNTAXID_DBREAD) {
        /* Number of data area specifications following, 1 Byte */
        number_of_areas = tvb_get_guint8(tvb, offset);
        proto_tree_add_uint(item_tree, hf_s7comm_item_dbread_numareas, tvb, offset, 1, number_of_areas);
        proto_item_append_text(item_tree, " (%d Data-Areas of Syntax-Id DBREAD)", number_of_areas);
        offset += 1;
        for (i = 1; i <= number_of_areas; i++) {
            sub_item = proto_tree_add_item(item_tree, hf_s7comm_param_subitem, tvb, offset, 5, ENC_NA);
            sub_item_tree = proto_item_add_subtree(sub_item, ett_s7comm_param_subitem);
            /* Number of Bytes to read, 1 Byte */
            len = tvb_get_guint8(tvb, offset);
            proto_tree_add_uint(sub_item_tree, hf_s7comm_item_dbread_length, tvb, offset, 1, len);
            offset += 1;
            /* DB number, 2 Bytes */
            db = tvb_get_ntohs(tvb, offset);
            proto_tree_add_uint(sub_item_tree, hf_s7comm_item_dbread_db, tvb, offset, 2, db);
            offset += 2;
            /* Start address, 2 Bytes */
            bytepos = tvb_get_ntohs(tvb, offset);
            proto_tree_add_uint(sub_item_tree, hf_s7comm_item_dbread_startadr, tvb, offset, 2, bytepos);
            offset += 2;
            /* Display as pseudo S7-Any Format */
            proto_item_append_text(sub_item, " [%d]: (DB%d.DBB %d BYTE %d)", i, db, bytepos, len);
        }
    /****************************************************************************/
    /******************** TIA S7 1200 symbolic address mode *********************/
    } else if (var_spec_type == 0x12 && var_spec_length >= 14 && var_spec_syntax_id == S7COMM_SYNTAXID_1200SYM) {
        proto_item_append_text(item_tree, " 1200 symbolic address");
        /* first byte in address seems always be 0xff */
        proto_tree_add_item(item_tree, hf_s7comm_tia1200_item_reserved1, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
        /* When Bytes 2/3 == 0, then Bytes 4/5 defines the area as known from classic 300/400 address mode
         * when Bytes 2/3 == 0x8a0e then bytes 4/5 are containing the DB number
         */
        tia_var_area1 = tvb_get_ntohs(tvb, offset);
        proto_tree_add_uint(item_tree, hf_s7comm_tia1200_item_area1, tvb, offset, 2, tia_var_area1);
        offset += 2;
        tia_var_area2 = tvb_get_ntohs(tvb, offset);
        if (tia_var_area1 == S7COMM_TIA1200_VAR_ITEM_AREA1_IQMCT) {
            proto_tree_add_uint(item_tree, hf_s7comm_tia1200_item_area2, tvb, offset, 2, tia_var_area2);
            proto_item_append_text(item_tree, " - Accessing %s", val_to_str(tia_var_area2, tia1200_var_item_area2_names, "Unknown IQMCT Area: 0x%04x"));
            offset += 2;
        } else if (tia_var_area1 == S7COMM_TIA1200_VAR_ITEM_AREA1_DB) {
            proto_tree_add_uint(item_tree, hf_s7comm_tia1200_item_dbnumber, tvb, offset, 2, tia_var_area2);
            proto_item_append_text(item_tree, " - Accessing DB%d", tia_var_area2);
            offset += 2;
        } else {
            /* for current unknown areas, I don't know if there are other valid areas */
            proto_tree_add_uint(item_tree, hf_s7comm_tia1200_item_area2unknown, tvb, offset, 2, tia_var_area2);
            proto_item_append_text(item_tree, " - Unknown area specification");
            offset += 2;
        }
        proto_tree_add_item(item_tree, hf_s7comm_tia1200_item_crc, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;

        for (i = 0; i < (var_spec_length - 10) / 4; i++) {
            /* Insert a new tree for every sub-struct */
            sub_item = proto_tree_add_item(item_tree, hf_s7comm_tia1200_substructure_item, tvb, offset, 4, ENC_NA);
            sub_item_tree = proto_item_add_subtree(sub_item, ett_s7comm_param_subitem);
            tia_lid_flags = tvb_get_guint8(tvb, offset) >> 4;
            proto_tree_add_item(sub_item_tree, hf_s7comm_tia1200_var_lid_flags, tvb, offset, 1, ENC_BIG_ENDIAN);
            tia_value = tvb_get_ntohl(tvb, offset) & 0x0fffffff;
            proto_item_append_text(sub_item, " [%d]: %s, Value: %u", i + 1,
                val_to_str(tia_lid_flags, tia1200_var_lid_flag_names, "Unknown flags: 0x%02x"),
                tia_value
            );
            proto_tree_add_item(sub_item_tree, hf_s7comm_tia1200_item_value, tvb, offset, 4, ENC_BIG_ENDIAN);
            offset += 4;
        }
    /****************************************************************************/
    /******************** Sinumerik NCK access **********************************/
    } else if (var_spec_type == 0x12 && var_spec_length == 8 && var_spec_syntax_id == S7COMM_SYNTAXID_NCK) {
        area = tvb_get_guint8(tvb, offset);
        nck_area = area >> 5;
        nck_unit = area & 0x1f;
        proto_tree_add_item(item_tree, hf_s7comm_item_nck_areaunit, tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(item_tree, hf_s7comm_item_nck_area, tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(item_tree, hf_s7comm_item_nck_unit, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
        nck_column = tvb_get_ntohs(tvb, offset);
        proto_tree_add_item(item_tree, hf_s7comm_item_nck_column, tvb, offset, 2, ENC_BIG_ENDIAN);
        offset += 2;
        nck_line = tvb_get_ntohs(tvb, offset);
        proto_tree_add_item(item_tree, hf_s7comm_item_nck_line, tvb, offset, 2, ENC_BIG_ENDIAN);
        offset += 2;
        nck_module = tvb_get_guint8(tvb, offset);
        proto_tree_add_item(item_tree, hf_s7comm_item_nck_module, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
        proto_tree_add_item(item_tree, hf_s7comm_item_nck_linecount, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
        proto_item_append_text(item_tree, " (NCK Area:%d Unit:%d Column:%d Line:%d Module:0x%02x)",
            nck_area, nck_unit, nck_column, nck_line, nck_module);
    }
    else {
        /* var spec, length and syntax id are still added to tree here */
        offset += var_spec_length - 1;
        proto_item_append_text(item_tree, " Unknown variable specification");
    }
    return offset;
}

/*******************************************************************************************************
 *
 * Decode parameter part of a PDU for setup communication
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_pdu_setup_communication(tvbuff_t *tvb,
                                     proto_tree *tree,
                                     guint32 offset)
{
    proto_tree_add_item(tree, hf_s7comm_param_setup_reserved1, tvb, offset, 1, ENC_BIG_ENDIAN);
    offset += 1;
    proto_tree_add_item(tree, hf_s7comm_param_maxamq_calling, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;
    proto_tree_add_item(tree, hf_s7comm_param_maxamq_called, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;
    proto_tree_add_item(tree, hf_s7comm_param_neg_pdu_length, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;
    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: Response -> Function Write  -> Data part
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_response_write_data(tvbuff_t *tvb,
                                 proto_tree *tree,
                                 guint8 item_count,
                                 guint32 offset)
{
    guint8 ret_val = 0;
    guint8 i = 0;
    proto_item *item = NULL;
    proto_tree *item_tree = NULL;

    for (i = 1; i <= item_count; i++) {
        ret_val = tvb_get_guint8(tvb, offset);
        /* Insert a new tree for every item */
        item = proto_tree_add_item(tree, hf_s7comm_data_item, tvb, offset, 1, ENC_NA);
        item_tree = proto_item_add_subtree(item, ett_s7comm_data_item);
        proto_item_append_text(item, " [%d]: (%s)", i, val_to_str(ret_val, s7comm_item_return_valuenames, "Unknown code: 0x%02x"));
        proto_tree_add_uint(item_tree, hf_s7comm_data_returncode, tvb, offset, 1, ret_val);
        offset += 1;
    }
    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: Response -> Function Read  -> Data part
 *           Request  -> Function Write -> Data part
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_response_read_data(tvbuff_t *tvb,
                                 proto_tree *tree,
                                 guint8 item_count,
                                 guint32 offset)
{
    guint8 ret_val = 0;
    guint8 tsize = 0;
    guint16 len = 0, len2 = 0;
    guint16 head_len = 4;           /* 1 byte res-code, 1 byte transp-size, 2 bytes len */
    guint8 i = 0;
    proto_item *item = NULL;
    proto_tree *item_tree = NULL;

    for (i = 1; i <= item_count; i++) {
        ret_val = tvb_get_guint8(tvb, offset);
        if (ret_val == S7COMM_ITEM_RETVAL_RESERVED ||
            ret_val == S7COMM_ITEM_RETVAL_DATA_OK ||
            ret_val == S7COMM_ITEM_RETVAL_DATA_ERR
            ) {
            tsize = tvb_get_guint8(tvb, offset + 1);
            len = tvb_get_ntohs(tvb, offset + 2);
            /* calculate length in bytes */
            if (tsize == S7COMM_DATA_TRANSPORT_SIZE_BBIT ||
                tsize == S7COMM_DATA_TRANSPORT_SIZE_BBYTE ||
                tsize == S7COMM_DATA_TRANSPORT_SIZE_BINT
                ) {     /* given length is in number of bits */
                if (len % 8) { /* len is not a multiple of 8, then round up to next number */
                    len /= 8;
                    len = len + 1;
                } else {
                    len /= 8;
                }
            }

            /* the PLC places extra bytes at the end of all but last result, if length is not a multiple of 2 */
            if ((len % 2) && (i < item_count)) {
                len2 = len + 1;
            } else {
                len2 = len;
            }
        }
        /* Insert a new tree for every item */
        item = proto_tree_add_item(tree, hf_s7comm_data_item, tvb, offset, len + head_len, ENC_NA);
        item_tree = proto_item_add_subtree(item, ett_s7comm_data_item);
        proto_item_append_text(item, " [%d]: (%s)", i, val_to_str(ret_val, s7comm_item_return_valuenames, "Unknown code: 0x%02x"));

        proto_tree_add_uint(item_tree, hf_s7comm_data_returncode, tvb, offset, 1, ret_val);
        proto_tree_add_uint(item_tree, hf_s7comm_data_transport_size, tvb, offset + 1, 1, tsize);
        proto_tree_add_uint(item_tree, hf_s7comm_data_length, tvb, offset + 2, 2, len);
        offset += head_len;

        if (ret_val == S7COMM_ITEM_RETVAL_DATA_OK || ret_val == S7COMM_ITEM_RETVAL_RESERVED) {
            proto_tree_add_item(item_tree, hf_s7comm_readresponse_data, tvb, offset, len, ENC_NA);
            offset += len;
            if (len != len2) {
                proto_tree_add_item(item_tree, hf_s7comm_data_fillbyte, tvb, offset, 1, ENC_BIG_ENDIAN);
                offset += 1;
            }
        }
    }
    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: Request or Response -> Function 0x28 (PLC control functions)
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_plc_controls_param_hex28(tvbuff_t *tvb,
                      packet_info *pinfo,
                      proto_tree *tree,
                      guint32 offset)
{
    guint16 len;
    guint8 count;
    guint8 i;
    guint8 *str;

    /* The first byte 0x28 is checked and inserted to tree outside, so skip it here */
    offset += 1;

    /* First part is unknown, 7 bytes */
    proto_tree_add_item(tree, hf_s7comm_data_plccontrol_part1_unknown, tvb, offset, 7, ENC_NA);
    offset += 7;
    /* Part 1 */
    len = tvb_get_ntohs(tvb, offset);
    proto_tree_add_uint(tree, hf_s7comm_data_plccontrol_part1_len, tvb, offset, 2, len);
    offset += 2;
    /* no block function, cold start e.g. */
    if (len == 2) {
        /* C = cold start */
        proto_tree_add_item(tree, hf_s7comm_data_plccontrol_argument, tvb, offset, 2, ENC_ASCII|ENC_NA);
        offset +=2;
    } else if (len > 2) {
        count = tvb_get_guint8(tvb, offset);            /* number of blocks following */
        proto_tree_add_uint(tree, hf_s7comm_data_plccontrol_block_cnt, tvb, offset, 1, count);
        offset += 1;
        /* Next byte reserved? is 0x00 */
        proto_tree_add_item(tree, hf_s7comm_data_plccontrol_part1_unknown2, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
        for (i = 0; i < count; i++) {
            /* First byte of block type seems to be every time '0' as single char*/
            proto_tree_add_item(tree, hf_s7comm_data_plccontrol_block_unknown, tvb, offset, 1, ENC_ASCII|ENC_NA);
            offset +=1;
            proto_tree_add_item(tree, hf_s7comm_data_plccontrol_block_type, tvb, offset, 1, ENC_BIG_ENDIAN);
            col_append_fstr(pinfo->cinfo, COL_INFO, " Type:[%s]", val_to_str(tvb_get_guint8(tvb, offset), blocktype_names, "Unknown Block type: 0x%02x"));
            offset += 1;
            proto_tree_add_item(tree, hf_s7comm_data_plccontrol_block_num, tvb, offset, 5, ENC_ASCII|ENC_NA);
            str = tvb_get_string_enc(wmem_packet_scope(), tvb, offset, 5, ENC_ASCII);
            col_append_fstr(pinfo->cinfo, COL_INFO, " No.:[%s]", str);
            offset += 5;
            /* 'P', 'B' or 'A' is following
             Destination filesystem?
                P = passive filesystem
                A = active filesystem?
             */
            proto_tree_add_item(tree, hf_s7comm_data_plccontrol_dest_filesys, tvb, offset, 1, ENC_ASCII|ENC_NA);
            offset += 1;
        }
    }
    /* Part 2 */
    len = tvb_get_guint8(tvb, offset);
    proto_tree_add_uint(tree, hf_s7comm_data_plccontrol_part2_len, tvb, offset, 1, len);
    offset += 1;
    /* Function (PI_SERVICE) as string  (program invocation)
     *    Known functions:
     *   _INSE = Activate a module
     *   _DELE = Delete a passive module
     *   _PROGRAM = Start/Stop the PLC
     *   _PLC_MEMORYRESET = Reset the PLC memory
     */
    proto_tree_add_item(tree, hf_s7comm_data_plccontrol_pi_service, tvb, offset, len, ENC_ASCII|ENC_NA);
    offset += len;

    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: Request or Response -> Function 0x29 (PLC control functions -> STOP)
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_plc_controls_param_hex29(tvbuff_t *tvb,
                      proto_tree *tree,
                      guint32 offset)
{
    guint8 len;

    /* The first byte 0x29 is checked and inserted to tree outside, so skip it here */
    offset += 1;
    /* Meaning of first 5 bytes (Part 1) is unknown */
    proto_tree_add_item(tree, hf_s7comm_data_plccontrol_part1_unknown, tvb, offset, 5, ENC_NA);
    offset += 5;
    /* Part 2 */
    len = tvb_get_guint8(tvb, offset);
    proto_tree_add_uint(tree, hf_s7comm_data_plccontrol_part2_len, tvb, offset, 1, len);
    offset += 1;
    /* Function as string */
    proto_tree_add_item(tree, hf_s7comm_data_plccontrol_pi_service, tvb, offset, len, ENC_ASCII|ENC_NA);
    offset += len;

    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: Request or Response -> Function 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f (block control functions)
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_plc_controls_param_hex1x(tvbuff_t *tvb,
                      packet_info *pinfo,
                      proto_tree *tree,
                      guint16 plength,
                      guint32 offset)
{
    guint8 len;
    guint8 function;
    guint8 *str;

    function = tvb_get_guint8(tvb, offset);
    offset += 1;

    /* Meaning of first byte is unknown */
    proto_tree_add_item(tree, hf_s7comm_data_blockcontrol_unknown1, tvb, offset, 1, ENC_NA);
    offset += 1;
    /* These 2 bytes seems to be an error code. If an upload fails, this value is also shown in Manager as errorcode. Zero on success. */
    proto_tree_add_item(tree, hf_s7comm_data_blockcontrol_errorcode, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;
    /* unknown 4 bytes */
    proto_tree_add_item(tree, hf_s7comm_data_blockcontrol_unknown1, tvb, offset, 4, ENC_NA);
    offset += 4;
    if (plength <= 8) {
        /* Upload or End upload functions have no other data */
        return offset;
    }

    /* Part 1: Block information*/
    len = tvb_get_guint8(tvb, offset);
    proto_tree_add_uint(tree, hf_s7comm_data_blockcontrol_part1_len, tvb, offset, 1, len);
    offset += 1;
    /* Prefix
     *   File identifier:
     *   _ means: "complete module"
     *   $ means: "Module header for up-loading"
     */
    proto_tree_add_item(tree, hf_s7comm_data_blockcontrol_file_ident, tvb, offset, 1, ENC_ASCII|ENC_NA);
    offset += 1;
    /* First byte of block type is every time '0' */
    proto_tree_add_item(tree, hf_s7comm_data_blockcontrol_block_unknown, tvb, offset, 1, ENC_ASCII|ENC_NA);
    offset += 1;
    proto_tree_add_item(tree, hf_s7comm_data_blockcontrol_block_type, tvb, offset, 1, ENC_BIG_ENDIAN);
    col_append_fstr(pinfo->cinfo, COL_INFO, " Type:[%s]", val_to_str(tvb_get_guint8(tvb, offset), blocktype_names, "Unknown Block type: 0x%02x"));
    offset += 1;

    str = tvb_get_string_enc(wmem_packet_scope(), tvb, offset, 5, ENC_ASCII);
    proto_tree_add_item(tree, hf_s7comm_data_blockcontrol_block_num, tvb, offset, 5, ENC_ASCII|ENC_NA);
    col_append_fstr(pinfo->cinfo, COL_INFO, " No.:[%s]", str);
    offset += 5;
    /* 'P', 'B' or 'A' is following */
    proto_tree_add_item(tree, hf_s7comm_data_blockcontrol_dest_filesys, tvb, offset, 1, ENC_ASCII|ENC_NA);
    offset += 1;

    /* Part 2, only available in "request download" */
    if (function == S7COMM_FUNCREQUESTDOWNLOAD && plength > 18) {
        len = tvb_get_guint8(tvb, offset);
        proto_tree_add_uint(tree, hf_s7comm_data_blockcontrol_part2_len, tvb, offset, 1, len);
        offset += 1;
        /* first byte unknown '1' */
        proto_tree_add_item(tree, hf_s7comm_data_blockcontrol_part2_unknown, tvb, offset, 1, ENC_ASCII|ENC_NA);
        offset += 1;
        proto_tree_add_item(tree, hf_s7comm_data_blockcontrol_loadmem_len, tvb, offset, 6, ENC_ASCII|ENC_NA);
        offset += 6;
        proto_tree_add_item(tree, hf_s7comm_data_blockcontrol_mc7code_len, tvb, offset, 6, ENC_ASCII|ENC_NA);
        offset += 6;
    }
    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: User Data -> Function group 1 -> Programmer commands -> Request diagnostic data (0x13 or 0x01)
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_ud_prog_reqdiagdata(tvbuff_t *tvb,
                                    proto_tree *data_tree,
                                    guint8 subfunc,             /* Subfunction */
                                    guint32 offset)             /* Offset on data part +4 */
{
    proto_item *item = NULL;
    proto_tree *item_tree = NULL;
    guint16 line_nr;
    guint16 line_cnt;
    guint16 ask_size;
    guint16 item_size = 4;
    guint8 registerflags;
    gchar str_flags[80];

    proto_tree_add_item(data_tree, hf_s7comm_diagdata_req_askheadersize, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;
    ask_size = tvb_get_ntohs(tvb, offset);
    proto_tree_add_uint(data_tree, hf_s7comm_diagdata_req_asksize, tvb, offset, 2, ask_size);
    offset += 2;
    proto_tree_add_item(data_tree, hf_s7comm_diagdata_req_unknown, tvb, offset, 6, ENC_NA);
    offset += 6;
    proto_tree_add_item(data_tree, hf_s7comm_diagdata_req_answersize, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;
    proto_tree_add_item(data_tree, hf_s7comm_diagdata_req_unknown, tvb, offset, 13, ENC_NA);
    offset += 13;
    proto_tree_add_item(data_tree, hf_s7comm_diagdata_req_block_type, tvb, offset, 1, ENC_BIG_ENDIAN);
    offset += 1;
    proto_tree_add_item(data_tree, hf_s7comm_diagdata_req_block_num, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;
    proto_tree_add_item(data_tree, hf_s7comm_diagdata_req_startaddr_awl, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;
    proto_tree_add_item(data_tree, hf_s7comm_diagdata_req_saz, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;
    proto_tree_add_item(data_tree, hf_s7comm_diagdata_req_unknown, tvb, offset, 1, ENC_NA);
    offset += 1;
    if (subfunc == 0x13) {
        line_cnt = tvb_get_guint8(tvb, offset);
        proto_tree_add_uint(data_tree, hf_s7comm_diagdata_req_number_of_lines, tvb, offset, 2, line_cnt);
        offset += 1;
        proto_tree_add_item(data_tree, hf_s7comm_diagdata_req_unknown, tvb, offset, 1, ENC_NA);
        offset += 1;
    } else {
        line_cnt = (ask_size - 2) / 2;
    }
    proto_tree_add_bitmask(data_tree, tvb, offset, hf_s7comm_diagdata_registerflag,
        ett_s7comm_diagdata_registerflag, s7comm_diagdata_registerflag_fields, ENC_BIG_ENDIAN);
    offset += 1;

    if (subfunc == 0x13) {
        item_size = 4;
    } else {
        item_size = 2;
    }
    for (line_nr = 0; line_nr < line_cnt; line_nr++) {

        item = proto_tree_add_item(data_tree, hf_s7comm_data_item, tvb, offset, item_size, ENC_NA);
        item_tree = proto_item_add_subtree(item, ett_s7comm_data_item);
        if (subfunc == 0x13) {
            proto_tree_add_item(item_tree, hf_s7comm_diagdata_req_line_address, tvb, offset, 2, ENC_BIG_ENDIAN);
            offset += 2;
        }
        proto_tree_add_item(item_tree, hf_s7comm_diagdata_req_unknown, tvb, offset, 1, ENC_NA);
        offset += 1;

        registerflags = tvb_get_guint8(tvb, offset);
        make_registerflag_string(str_flags, registerflags, sizeof(str_flags));
        proto_item_append_text(item, " [%d]: (%s)", line_nr+1, str_flags);
        proto_tree_add_bitmask(item_tree, tvb, offset, hf_s7comm_diagdata_registerflag,
            ett_s7comm_diagdata_registerflag, s7comm_diagdata_registerflag_fields, ENC_BIG_ENDIAN);
        offset += 1;
    }

    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: User Data -> Function group 1 -> Programmer commands -> Variable table -> request
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_ud_prog_vartab_req_item(tvbuff_t *tvb,
                          guint32 offset,
                          proto_tree *sub_tree,
                          guint16 item_no)
{
    guint32 bytepos = 0;
    guint16 len = 0;
    guint16 db = 0;
    guint8 area = 0;
    proto_item *item = NULL;

    /* Insert a new tree with 6 bytes for every item */
    item = proto_tree_add_item(sub_tree, hf_s7comm_param_item, tvb, offset, 6, ENC_NA);

    sub_tree = proto_item_add_subtree(item, ett_s7comm_param_item);

    proto_item_append_text(item, " [%d]:", item_no + 1);

    /* Area, 1 byte */
    area = tvb_get_guint8(tvb, offset);
    proto_tree_add_item(sub_tree, hf_s7comm_vartab_req_memory_area, tvb, offset, 1, ENC_BIG_ENDIAN);
    offset += 1;

    /* Length (repetition factor), 1 byte */
    len = tvb_get_guint8(tvb, offset);
    proto_tree_add_uint(sub_tree, hf_s7comm_vartab_req_repetition_factor, tvb, offset, 1, len);
    offset += 1;

    /* DB number, 2 bytes */
    db = tvb_get_ntohs(tvb, offset);
    proto_tree_add_uint(sub_tree, hf_s7comm_vartab_req_db_number, tvb, offset, 2, db);
    offset += 2;

    /* byte offset, 2 bytes */
    bytepos = tvb_get_ntohs(tvb, offset);
    proto_tree_add_uint(sub_tree, hf_s7comm_vartab_req_startaddress, tvb, offset, 2, bytepos);
    offset += 2;

    /* build a full address to show item data directly beside the item */
    switch (area) {
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_MB:
            proto_item_append_text(sub_tree, " (M%d.0 BYTE %d)", bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_MW:
            proto_item_append_text(sub_tree, " (M%d.0 WORD %d)", bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_MD:
            proto_item_append_text(sub_tree, " (M%d.0 DWORD %d)", bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_EB:
            proto_item_append_text(sub_tree, " (I%d.0 BYTE %d)", bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_EW:
            proto_item_append_text(sub_tree, " (I%d.0 WORD %d)", bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_ED:
            proto_item_append_text(sub_tree, " (I%d.0 DWORD %d)", bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_AB:
            proto_item_append_text(sub_tree, " (Q%d.0 BYTE %d)", bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_AW:
            proto_item_append_text(sub_tree, " (Q%d.0 WORD %d)", bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_AD:
            proto_item_append_text(sub_tree, " (Q%d.0 DWORD %d)", bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_PEB:
            proto_item_append_text(sub_tree, " (PI%d.0 BYTE %d)", bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_PEW:
            proto_item_append_text(sub_tree, " (PI%d.0 WORD %d)", bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_PED:
            proto_item_append_text(sub_tree, " (PI%d.0 DWORD %d)", bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBB:
            proto_item_append_text(sub_tree, " (DB%d.DX%d.0 BYTE %d)", db, bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBW:
            proto_item_append_text(sub_tree, " (DB%d.DX%d.0 WORD %d)", db, bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBD:
            proto_item_append_text(sub_tree, " (DB%d.DX%d.0 DWORD %d)", db, bytepos, len);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_T:
            /* it's possible to read multiple timers */
            if (len >1)
                proto_item_append_text(sub_tree, " (T %d..%d)", bytepos, bytepos + len - 1);
            else
                proto_item_append_text(sub_tree, " (T %d)", bytepos);
            break;
        case S7COMM_UD_SUBF_PROG_VARTAB_AREA_C:
            /* it's possible to read multiple counters */
            if (len >1)
                proto_item_append_text(sub_tree, " (C %d..%d)", bytepos, bytepos + len - 1);
            else
                proto_item_append_text(sub_tree, " (C %d)", bytepos);
            break;
    }
    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: User Data -> Function group 1 -> Programmer commands -> Variable table -> response
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_ud_prog_vartab_res_item(tvbuff_t *tvb,
                          guint32 offset,
                          proto_tree *sub_tree,
                          guint16 item_no)
{
    guint16 len = 0, len2 = 0;
    guint8 ret_val = 0;
    guint8 tsize = 0;
    guint8 head_len = 4;

    proto_item *item = NULL;

    ret_val = tvb_get_guint8(tvb, offset);
    if (ret_val == S7COMM_ITEM_RETVAL_RESERVED ||
        ret_val == S7COMM_ITEM_RETVAL_DATA_OK ||
        ret_val == S7COMM_ITEM_RETVAL_DATA_ERR
        ) {
        tsize = tvb_get_guint8(tvb, offset + 1);
        len = tvb_get_ntohs(tvb, offset + 2);

        if (tsize == S7COMM_DATA_TRANSPORT_SIZE_BBYTE || tsize == S7COMM_DATA_TRANSPORT_SIZE_BINT) {
            len /= 8;
        }
        /* the PLC places extra bytes at the end if length is not a multiple of 2 */
        if (len % 2) {
            len2 = len + 1;
        }else {
            len2 = len;
        }
    }
    /* Insert a new tree for every item */
    item = proto_tree_add_item(sub_tree, hf_s7comm_data_item, tvb, offset, len + head_len, ENC_NA);
    sub_tree = proto_item_add_subtree(item, ett_s7comm_data_item);

    proto_item_append_text(item, " [%d]: (%s)", item_no + 1, val_to_str(ret_val, s7comm_item_return_valuenames, "Unknown code: 0x%02x"));

    proto_tree_add_uint(sub_tree, hf_s7comm_data_returncode, tvb, offset, 1, ret_val);
    proto_tree_add_uint(sub_tree, hf_s7comm_data_transport_size, tvb, offset + 1, 1, tsize);
    proto_tree_add_uint(sub_tree, hf_s7comm_data_length, tvb, offset + 2, 2, len);

    offset += head_len;
    if (ret_val == S7COMM_ITEM_RETVAL_DATA_OK || ret_val == S7COMM_ITEM_RETVAL_RESERVED) {
        proto_tree_add_item(sub_tree, hf_s7comm_readresponse_data, tvb, offset, len, ENC_NA);
        offset += len;
        if (len != len2) {
            proto_tree_add_item(sub_tree, hf_s7comm_data_fillbyte, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
        }
    }
    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: User Data -> Function group 5 -> Security functions?
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_ud_security_subfunc(tvbuff_t *tvb,
                                    proto_tree *data_tree,
                                    guint16 dlength,            /* length of data part given in header */
                                    guint32 offset)             /* Offset on data part +4 */
{
    /* Display dataset as raw bytes. Maybe this part can be extended with further knowledge. */
    proto_tree_add_item(data_tree, hf_s7comm_userdata_data, tvb, offset, dlength - 4, ENC_NA);
    offset += dlength;

    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: User Data -> Function group 6 -> PBC, Programmable Block Functions (e.g. BSEND/BRECV)
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_ud_pbc_subfunc(tvbuff_t *tvb,
                             proto_tree *data_tree,
                             guint16 dlength,                   /* length of data part given in header */
                             guint32 offset)                    /* Offset on data part +4 */
{
    proto_tree_add_item(data_tree, hf_s7comm_item_varspec, tvb, offset, 1, ENC_BIG_ENDIAN);
    offset += 1;
    proto_tree_add_item(data_tree, hf_s7comm_item_varspec_length, tvb, offset, 1, ENC_BIG_ENDIAN);
    offset += 1;
    proto_tree_add_item(data_tree, hf_s7comm_item_syntax_id, tvb, offset, 1, ENC_BIG_ENDIAN);
    offset += 1;
    proto_tree_add_item(data_tree, hf_s7comm_pbc_unknown, tvb, offset, 1, ENC_BIG_ENDIAN);
    offset += 1;
    proto_tree_add_item(data_tree, hf_s7comm_pbc_r_id, tvb, offset, 4, ENC_BIG_ENDIAN);
    offset += 4;
    /* Only in the first telegram of possible several segments, an int16 of full data length is following.
     * As the dissector can't check this, don't display the information
     * and display the data as payload bytes.
     */
    dlength = dlength - 4 - 8;  /* 4 bytes data header, 8 bytes varspec */
    if (dlength > 0) {
        proto_tree_add_item(data_tree, hf_s7comm_userdata_data, tvb, offset, dlength, ENC_NA);
        offset += dlength;
    }

    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: User Data -> Function group 7 -> time functions
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_ud_time_subfunc(tvbuff_t *tvb,
                                    proto_tree *data_tree,
                                    guint8 type,                /* Type of data (request/response) */
                                    guint8 subfunc,             /* Subfunction */
                                    guint8 ret_val,             /* Return value in data part */
                                    guint16 dlength,            /* length of data part given in header */
                                    guint32 offset)             /* Offset on data part +4 */
{
    gboolean know_data = FALSE;

    switch (subfunc) {
        case S7COMM_UD_SUBF_TIME_READ:
        case S7COMM_UD_SUBF_TIME_READF:
            if (type == S7COMM_UD_TYPE_RES) {                   /*** Response ***/
                if (ret_val == S7COMM_ITEM_RETVAL_DATA_OK) {
                    proto_item_append_text(data_tree, ": ");
                    offset = s7comm_add_timestamp_to_tree(tvb, data_tree, offset, TRUE);
                }
                know_data = TRUE;
            }
            break;
        case S7COMM_UD_SUBF_TIME_SET:
        case S7COMM_UD_SUBF_TIME_SET2:
            if (type == S7COMM_UD_TYPE_REQ) {                   /*** Request ***/
                if (ret_val == S7COMM_ITEM_RETVAL_DATA_OK) {
                    proto_item_append_text(data_tree, ": ");
                    offset = s7comm_add_timestamp_to_tree(tvb, data_tree, offset, TRUE);
                }
                know_data = TRUE;
            }
            break;
        default:
            break;
    }

    if (know_data == FALSE && dlength > 4) {
        proto_tree_add_item(data_tree, hf_s7comm_userdata_data, tvb, offset, dlength - 4, ENC_NA);
        offset += dlength;
    }
    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: User Data -> Function group 3 -> block functions
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_ud_block_subfunc(tvbuff_t *tvb,
                                    packet_info *pinfo,
                                    proto_tree *data_tree,
                                    guint8 type,                /* Type of data (request/response) */
                                    guint8 subfunc,             /* Subfunction */
                                    guint8 ret_val,             /* Return value in data part */
                                    guint8 tsize,               /* transport size in data part */
                                    guint16 len,                /* length given in data part */
                                    guint16 dlength,            /* length of data part given in header */
                                    guint32 offset)             /* Offset on data part +4 */
{
    guint16 count;
    guint16 i;
    guint8 *pBlocknumber;
    guint16 blocknumber;
    guint8 blocktype;
    gboolean know_data = FALSE;
    proto_item *item = NULL;
    proto_tree *item_tree = NULL;
    char str_timestamp[30];
    char str_number[10];
    char str_version[10];

    switch (subfunc) {
        /*************************************************
         * List blocks
         */
        case S7COMM_UD_SUBF_BLOCK_LIST:
            if (type == S7COMM_UD_TYPE_REQ) {                       /*** Request ***/
                /* Is this a possible combination? Never seen it... */

            } else if (type == S7COMM_UD_TYPE_RES) {                /*** Response ***/
                count = len / 4;
                for(i = 0; i < count; i++) {
                    /* Insert a new tree of 4 byte length for every item */
                    item = proto_tree_add_item(data_tree, hf_s7comm_data_item, tvb, offset, 4, ENC_NA);
                    item_tree = proto_item_add_subtree(item, ett_s7comm_data_item);
                    offset += 1; /* skip first byte */
                    proto_item_append_text(item, " [%d]: (Block type %s)", i+1, val_to_str(tvb_get_guint8(tvb, offset), blocktype_names, "Unknown Block type: 0x%02x"));
                    proto_tree_add_item(item_tree, hf_s7comm_ud_blockinfo_block_type, tvb, offset, 1, ENC_BIG_ENDIAN);
                    offset += 1;
                    proto_tree_add_item(item_tree, hf_s7comm_ud_blockinfo_block_cnt, tvb, offset, 2, ENC_BIG_ENDIAN);
                    offset += 2;
                }
                know_data = TRUE;
            }
            break;
        /*************************************************
         * List blocks of type
         */
        case S7COMM_UD_SUBF_BLOCK_LISTTYPE:
            if (type == S7COMM_UD_TYPE_REQ) {                       /*** Request ***/
                if (tsize != S7COMM_DATA_TRANSPORT_SIZE_NULL) {
                    offset += 1; /* skip first byte */
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_block_type, tvb, offset, 1, ENC_BIG_ENDIAN);
                    col_append_fstr(pinfo->cinfo, COL_INFO, " Type:[%s]",
                        val_to_str(tvb_get_guint8(tvb, offset), blocktype_names, "Unknown Block type: 0x%02x"));
                    proto_item_append_text(data_tree, ": (%s)",
                        val_to_str(tvb_get_guint8(tvb, offset), blocktype_names, "Unknown Block type: 0x%02x"));
                    offset += 1;
                }
                know_data = TRUE;

            }else if (type == S7COMM_UD_TYPE_RES) {                 /*** Response ***/
                if (tsize != S7COMM_DATA_TRANSPORT_SIZE_NULL) {
                    count = len / 4;

                    for(i = 0; i < count; i++) {
                        /* Insert a new tree of 4 byte length for every item */
                        item = proto_tree_add_item(data_tree, hf_s7comm_data_item, tvb, offset, 4, ENC_NA);
                        item_tree = proto_item_add_subtree(item, ett_s7comm_data_item);

                        proto_item_append_text(item, " [%d]: (Block number %d)", i+1, tvb_get_ntohs(tvb, offset));
                        proto_tree_add_item(item_tree, hf_s7comm_ud_blockinfo_block_num, tvb, offset, 2, ENC_BIG_ENDIAN);
                        offset += 2;
                        /* The first Byte is unknown, kind of flags? */
                        proto_tree_add_item(item_tree, hf_s7comm_ud_blockinfo_block_flags, tvb, offset, 1, ENC_BIG_ENDIAN);
                        offset += 1;
                        proto_tree_add_item(item_tree, hf_s7comm_ud_blockinfo_block_lang, tvb, offset, 1, ENC_BIG_ENDIAN);
                        offset += 1;
                    }
                }
                know_data = TRUE;
            }
            break;
        /*************************************************
         * Get block infos
         */
        case S7COMM_UD_SUBF_BLOCK_BLOCKINFO:
            if (type == S7COMM_UD_TYPE_REQ) {                       /*** Request ***/
                if (tsize != S7COMM_DATA_TRANSPORT_SIZE_NULL) {
                    /* 8 Bytes of Data follow, 1./ 2. type, 3-7 blocknumber as ascii number */
                    offset += 1; /* skip first byte */
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_block_type, tvb, offset, 1, ENC_BIG_ENDIAN);
                    proto_item_append_text(data_tree, ": (Block type: %s",
                        val_to_str(tvb_get_guint8(tvb, offset), blocktype_names, "Unknown Block type: 0x%02x"));
                    /* Add block type and number to info column */
                    col_append_fstr(pinfo->cinfo, COL_INFO, " Type:[%s]",
                        val_to_str(tvb_get_guint8(tvb, offset), blocktype_names, "Unknown Block type: 0x%02x"));
                    offset += 1;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_block_num_ascii, tvb, offset, 5, ENC_ASCII|ENC_NA);
                    pBlocknumber = tvb_get_string_enc(wmem_packet_scope(), tvb, offset, 5, ENC_ASCII);
                    col_append_fstr(pinfo->cinfo, COL_INFO, " No.:[%s]", pBlocknumber);
                    proto_item_append_text(data_tree, ", Number: %s)", pBlocknumber);
                    offset += 5;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_filesys, tvb, offset, 1, ENC_ASCII|ENC_NA);
                    offset += 1;
                }
                know_data = TRUE;

            }else if (type == S7COMM_UD_TYPE_RES) {             /*** Response ***/
                /* 78 Bytes */
                if (ret_val == S7COMM_ITEM_RETVAL_DATA_OK) {
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_res_const1, tvb, offset, 1, ENC_BIG_ENDIAN);
                    offset += 1;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_block_type, tvb, offset, 1, ENC_BIG_ENDIAN);
                    offset += 1;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_res_infolength, tvb, offset, 2, ENC_BIG_ENDIAN);
                    offset += 2;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_res_unknown2, tvb, offset, 2, ENC_BIG_ENDIAN);
                    offset += 2;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_res_const3, tvb, offset, 2, ENC_ASCII|ENC_NA);
                    offset += 2;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_res_unknown, tvb, offset, 1, ENC_NA);
                    offset += 1;

                    /* Configuration flags or Bits?
                     * Bits: 0 0 0 0   0 0 0 0   0 0 0 0   0 0 0 0
                     * Pos : 31 ..                             ..0
                     *
                     * Bit : 0 -> DB Linked = true
                     * Bit : 5 -> DB Non Retain = true
                     * Standard FC/FC/DB -> 0x0101        0x0100 -> is this bit (8) in FBs for multiinstance?
                     * SFC:  0x0009  SFB: 0x0109 or 0x010d (e.g. SFB8, 414)
                     */

                    proto_tree_add_bitmask(data_tree, tvb, offset, hf_s7comm_userdata_blockinfo_flags,
                        ett_s7comm_userdata_blockinfo_flags, s7comm_userdata_blockinfo_flags_fields, ENC_BIG_ENDIAN);
                    offset += 1;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_block_lang, tvb, offset, 1, ENC_BIG_ENDIAN);
                    offset += 1;
                    blocktype = tvb_get_guint8(tvb, offset);
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_subblk_type, tvb, offset, 1, ENC_BIG_ENDIAN);
                    /* Add block type and number to info column */
                    col_append_fstr(pinfo->cinfo, COL_INFO, " Type:[%s]",
                        val_to_str(blocktype, subblktype_names, "Unknown Subblk type: 0x%02x"));
                    proto_item_append_text(data_tree, ": (Subblk type: %s",
                        val_to_str(blocktype, subblktype_names, "Unknown Subblk type: 0x%02x"));
                    offset += 1;
                    blocknumber = tvb_get_ntohs(tvb, offset);
                    proto_tree_add_uint(data_tree, hf_s7comm_ud_blockinfo_block_num, tvb, offset, 2, blocknumber);
                    g_snprintf(str_number, sizeof(str_number), "%05d", blocknumber);
                    col_append_fstr(pinfo->cinfo, COL_INFO, " No.:[%s]", str_number);
                    proto_item_append_text(data_tree, ", Number: %05d)", blocknumber);
                    offset += 2;
                    /* "Length Load mem" -> the length in Step7 Manager seems to be this length +6 bytes */
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_load_mem_len, tvb, offset, 4, ENC_BIG_ENDIAN);
                    offset += 4;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_blocksecurity, tvb, offset, 4, ENC_BIG_ENDIAN);
                    offset += 4;
                    s7comm_get_timestring_from_s7time(tvb, offset, str_timestamp, sizeof(str_timestamp));
                    proto_tree_add_string(data_tree, hf_s7comm_ud_blockinfo_code_timestamp, tvb, offset, 6, str_timestamp);
                    offset += 6;
                    s7comm_get_timestring_from_s7time(tvb, offset, str_timestamp, sizeof(str_timestamp));
                    proto_tree_add_string(data_tree, hf_s7comm_ud_blockinfo_interface_timestamp, tvb, offset, 6, str_timestamp);
                    offset += 6;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_ssb_len, tvb, offset, 2, ENC_BIG_ENDIAN);
                    offset += 2;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_add_len, tvb, offset, 2, ENC_BIG_ENDIAN);
                    offset += 2;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_localdata_len, tvb, offset, 2, ENC_BIG_ENDIAN);
                    offset += 2;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_mc7_len, tvb, offset, 2, ENC_BIG_ENDIAN);
                    offset += 2;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_author, tvb, offset, 8, ENC_ASCII|ENC_NA);
                    offset += 8;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_family, tvb, offset, 8, ENC_ASCII|ENC_NA);
                    offset += 8;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_headername, tvb, offset, 8, ENC_ASCII|ENC_NA);
                    offset += 8;
                    g_snprintf(str_version, sizeof(str_version), "%d.%d", ((tvb_get_guint8(tvb, offset) & 0xf0) >> 4), tvb_get_guint8(tvb, offset) & 0x0f);
                    proto_tree_add_string(data_tree, hf_s7comm_ud_blockinfo_headerversion, tvb, offset, 1, str_version);
                    offset += 1;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_res_unknown, tvb, offset, 1, ENC_NA);
                    offset += 1;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_checksum, tvb, offset, 2, ENC_BIG_ENDIAN);
                    offset += 2;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_reserved1, tvb, offset, 4, ENC_BIG_ENDIAN);
                    offset += 4;
                    proto_tree_add_item(data_tree, hf_s7comm_ud_blockinfo_reserved2, tvb, offset, 4, ENC_BIG_ENDIAN);
                    offset += 4;
                }
                know_data = TRUE;
            }
            break;
        default:
            break;
    }
    if (know_data == FALSE && dlength > 4) {
        proto_tree_add_item(data_tree, hf_s7comm_userdata_data, tvb, offset, dlength - 4, ENC_NA);
        offset += dlength;
    }
    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: User Data -> Function group 2 -> cyclic data
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_ud_cyclic_subfunc(tvbuff_t *tvb,
                                    proto_tree *data_tree,
                                    guint8 type,                /* Type of data (request/response) */
                                    guint8 subfunc,             /* Subfunction */
                                    guint16 dlength,            /* length of data part given in header */
                                    guint32 offset)             /* Offset on data part +4 */
{
    gboolean know_data = FALSE;
    guint32 offset_old;
    guint32 len_item;
    guint8 item_count;
    guint8 i;

    switch (subfunc)
    {
        case S7COMM_UD_SUBF_CYCLIC_MEM:
            item_count = tvb_get_guint8(tvb, offset + 1);     /* first byte reserved??? */
            proto_tree_add_uint(data_tree, hf_s7comm_param_itemcount, tvb, offset, 2, item_count);
            offset += 2;
            if (type == S7COMM_UD_TYPE_REQ) {                   /* Request to PLC to send cyclic data */
                proto_tree_add_item(data_tree, hf_s7comm_cycl_interval_timebase, tvb, offset, 1, ENC_BIG_ENDIAN);
                offset += 1;
                proto_tree_add_item(data_tree, hf_s7comm_cycl_interval_time, tvb, offset, 1, ENC_BIG_ENDIAN);
                offset += 1;
                /* parse item data */
                for (i = 0; i < item_count; i++) {
                    offset_old = offset;
                    offset = s7comm_decode_param_item(tvb, offset, data_tree, i);
                    /* if length is not a multiple of 2 and this is not the last item, then add a fill-byte */
                    len_item = offset - offset_old;
                    if ((len_item % 2) && (i < item_count)) {
                        offset += 1;
                    }
                }

            } else if (type == S7COMM_UD_TYPE_RES || type == S7COMM_UD_TYPE_PUSH) {   /* Response from PLC with the requested data */
                /* parse item data */
                offset = s7comm_decode_response_read_data(tvb, data_tree, item_count, offset);
            }
            know_data = TRUE;
            break;
    }

    if (know_data == FALSE && dlength > 4) {
        proto_tree_add_item(data_tree, hf_s7comm_userdata_data, tvb, offset, dlength - 4, ENC_NA);
        offset += dlength;
    }
    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: User Data -> Function group 1 -> Programmer commands
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_ud_prog_subfunc(tvbuff_t *tvb,
                                    proto_tree *data_tree,
                                    guint8 type,                /* Type of data (request/response) */
                                    guint8 subfunc,             /* Subfunction */
                                    guint16 dlength,            /* length of data part given in header */
                                    guint32 offset)             /* Offset on data part +4 */
{
    gboolean know_data = FALSE;

    guint8 data_type;
    guint16 byte_count;
    guint16 item_count;
    guint16 i;

    switch(subfunc)
    {
        case S7COMM_UD_SUBF_PROG_REQDIAGDATA1:
        case S7COMM_UD_SUBF_PROG_REQDIAGDATA2:
            /* start variable table or block online view */
            /* TODO: Can only handle requests/response, not the "following" telegrams because it's necessary to correlate them
                with the previous request */
            if (type != S7COMM_UD_TYPE_PUSH) {
                offset = s7comm_decode_ud_prog_reqdiagdata(tvb, data_tree, subfunc, offset);
                know_data = TRUE;
            }
            break;

        case S7COMM_UD_SUBF_PROG_VARTAB1:
            /* online status in variable table */
            offset += 1; /* 1 Byte const 0, skip */
            data_type = tvb_get_guint8(tvb, offset);         /* 1 Byte type: 0x14 = Request, 0x04 = Response */
            proto_tree_add_uint(data_tree, hf_s7comm_vartab_data_type, tvb, offset, 1, data_type);
            offset += 1;

            byte_count = tvb_get_ntohs(tvb, offset);            /* 2 Bytes: Number of bytes of item-data including item-count */
            proto_tree_add_uint(data_tree, hf_s7comm_vartab_byte_count, tvb, offset, 2, byte_count);
            offset += 2;

            switch (data_type)
            {
                case S7COMM_UD_SUBF_PROG_VARTAB_TYPE_REQ:
                    /*** Request of data areas ***/

                    /* 20 Bytes unknown part */
                    proto_tree_add_item(data_tree, hf_s7comm_vartab_unknown, tvb, offset, 20, ENC_NA);
                    offset += 20;

                    item_count = tvb_get_ntohs(tvb, offset);    /* 2 Bytes header: number of items following */
                    proto_tree_add_uint(data_tree, hf_s7comm_vartab_item_count, tvb, offset, 2, item_count);
                    offset += 2;

                    /* parse item data */
                    for (i = 0; i < item_count; i++) {
                        offset = s7comm_decode_ud_prog_vartab_req_item(tvb, offset, data_tree, i);
                    }
                    know_data = TRUE;
                    break;

                case S7COMM_UD_SUBF_PROG_VARTAB_TYPE_RES:
                    /*** Response of PLC to requested data-areas ***/

                    /* 4 Bytes unknown part */
                    proto_tree_add_item(data_tree, hf_s7comm_vartab_unknown, tvb, offset, 4, ENC_NA);
                    offset += 4;

                    item_count = tvb_get_ntohs(tvb, offset);    /* 2 Bytes: number of items following */
                    proto_tree_add_uint(data_tree, hf_s7comm_vartab_item_count, tvb, offset, 2, item_count);
                    offset += 2;

                    /* parse item data */
                    for (i = 0; i < item_count; i++) {
                        offset = s7comm_decode_ud_prog_vartab_res_item(tvb, offset, data_tree, i);
                    }
                    know_data = TRUE;
                    break;
            }
    }

    if (know_data == FALSE && dlength > 4) {
        proto_tree_add_item(data_tree, hf_s7comm_userdata_data, tvb, offset, dlength - 4, ENC_NA);
        offset += dlength;
    }
    return offset;
}

/*******************************************************************************************************
 *******************************************************************************************************
 *
 * PDU Type: User Data
 *
 *******************************************************************************************************
 *******************************************************************************************************/
static guint32
s7comm_decode_ud(tvbuff_t *tvb,
                       packet_info *pinfo,
                       proto_tree *tree,
                       guint16 plength,
                       guint16 dlength,
                       guint32 offset)
{
    proto_item *item = NULL;
    proto_tree *param_tree = NULL;
    proto_tree *data_tree = NULL;

    guint8 ret_val;
    guint8 tsize;
    guint16 len;
    guint32 offset_temp;

    guint8 type;
    guint8 funcgroup;
    guint8 subfunc;
    guint8 data_unit_ref = 0;
    guint8 last_data_unit = 0;

    /* Add parameter tree */
    item = proto_tree_add_item(tree, hf_s7comm_param, tvb, offset, plength, ENC_NA);
    param_tree = proto_item_add_subtree(item, ett_s7comm_param);

    /* Try do decode some functions...
     * Some functions may use data that does't fit one telegram
     */
    offset_temp = offset;   /* Save offset */
    /* 3 bytes constant head */
    proto_tree_add_item(param_tree, hf_s7comm_userdata_param_head, tvb, offset_temp, 3, ENC_BIG_ENDIAN);
    offset_temp += 3;
    /* 1 byte length of following parameter (8 or 12 bytes) */
    proto_tree_add_item(param_tree, hf_s7comm_userdata_param_len, tvb, offset_temp, 1, ENC_BIG_ENDIAN);
    offset_temp += 1;
    /* 1 byte unknown, maybe indicating request/response */
    proto_tree_add_item(param_tree, hf_s7comm_userdata_param_reqres2, tvb, offset_temp, 1, ENC_BIG_ENDIAN);
    offset_temp += 1;
    /* High nibble (following/request/response) */
    type = (tvb_get_guint8(tvb, offset_temp) & 0xf0) >> 4;
    funcgroup = (tvb_get_guint8(tvb, offset_temp) & 0x0f);
    proto_tree_add_item(param_tree, hf_s7comm_userdata_param_type, tvb, offset_temp, 1, ENC_BIG_ENDIAN);

    col_append_fstr(pinfo->cinfo, COL_INFO, " Function:[%s] -> [%s]",
        val_to_str(type, userdata_type_names, "Unknown type: 0x%02x"),
        val_to_str(funcgroup, userdata_functiongroup_names, "Unknown function: 0x%02x")
        );

    proto_item_append_text(param_tree, ": (%s)", val_to_str(type, userdata_type_names, "Unknown type: 0x%02x"));
    proto_item_append_text(param_tree, " ->(%s)", val_to_str(funcgroup, userdata_functiongroup_names, "Unknown function: 0x%02x"));

    /* Low nibble function group  */
    proto_tree_add_item(param_tree, hf_s7comm_userdata_param_funcgroup, tvb, offset_temp, 1, ENC_BIG_ENDIAN);
    offset_temp += 1;
    /* 1 Byte subfunction  */
    subfunc = tvb_get_guint8(tvb, offset_temp);
    switch (funcgroup){
        case S7COMM_UD_FUNCGROUP_PROG:
            proto_tree_add_uint(param_tree, hf_s7comm_userdata_param_subfunc_prog, tvb, offset_temp, 1, subfunc);
            col_append_fstr(pinfo->cinfo, COL_INFO, " -> [%s]",
                val_to_str(subfunc, userdata_prog_subfunc_names, "Unknown subfunc: 0x%02x"));
            proto_item_append_text(param_tree, " ->(%s)", val_to_str(subfunc, userdata_prog_subfunc_names, "Unknown subfunc: 0x%02x"));
            break;
        case S7COMM_UD_FUNCGROUP_CYCLIC:
            proto_tree_add_uint(param_tree, hf_s7comm_userdata_param_subfunc_cyclic, tvb, offset_temp, 1, subfunc);
            col_append_fstr(pinfo->cinfo, COL_INFO, " -> [%s]",
                val_to_str(subfunc, userdata_cyclic_subfunc_names, "Unknown subfunc: 0x%02x"));
            proto_item_append_text(param_tree, " ->(%s)", val_to_str(subfunc, userdata_cyclic_subfunc_names, "Unknown subfunc: 0x%02x"));
            break;
        case S7COMM_UD_FUNCGROUP_BLOCK:
            proto_tree_add_uint(param_tree, hf_s7comm_userdata_param_subfunc_block, tvb, offset_temp, 1, subfunc);
            col_append_fstr(pinfo->cinfo, COL_INFO, " -> [%s]",
                val_to_str(subfunc, userdata_block_subfunc_names, "Unknown subfunc: 0x%02x"));
            proto_item_append_text(param_tree, " ->(%s)", val_to_str(subfunc, userdata_block_subfunc_names, "Unknown subfunc: 0x%02x"));
            break;
        case S7COMM_UD_FUNCGROUP_CPU:
            proto_tree_add_uint(param_tree, hf_s7comm_userdata_param_subfunc_cpu, tvb, offset_temp, 1, subfunc);
            col_append_fstr(pinfo->cinfo, COL_INFO, " -> [%s]",
                val_to_str(subfunc, userdata_cpu_subfunc_names, "Unknown subfunc: 0x%02x"));
            proto_item_append_text(param_tree, " ->(%s)", val_to_str(subfunc, userdata_cpu_subfunc_names, "Unknown subfunc: 0x%02x"));
            break;
        case S7COMM_UD_FUNCGROUP_SEC:
            proto_tree_add_uint(param_tree, hf_s7comm_userdata_param_subfunc_sec, tvb, offset_temp, 1, subfunc);
            col_append_fstr(pinfo->cinfo, COL_INFO, " -> [%s]",
                val_to_str(subfunc, userdata_sec_subfunc_names, "Unknown subfunc: 0x%02x"));
            proto_item_append_text(param_tree, " ->(%s)", val_to_str(subfunc, userdata_sec_subfunc_names, "Unknown subfunc: 0x%02x"));
            break;
        case S7COMM_UD_FUNCGROUP_TIME:
            proto_tree_add_uint(param_tree, hf_s7comm_userdata_param_subfunc_time, tvb, offset_temp, 1, subfunc);
            col_append_fstr(pinfo->cinfo, COL_INFO, " -> [%s]",
                val_to_str(subfunc, userdata_time_subfunc_names, "Unknown subfunc: 0x%02x"));
            proto_item_append_text(param_tree, " ->(%s)", val_to_str(subfunc, userdata_time_subfunc_names, "Unknown subfunc: 0x%02x"));
            break;
        default:
            proto_tree_add_uint(param_tree, hf_s7comm_userdata_param_subfunc, tvb, offset_temp, 1, subfunc);
            break;
    }
    offset_temp += 1;
    /* 1 Byte sequence number  */
    proto_tree_add_item(param_tree, hf_s7comm_userdata_param_seq_num, tvb, offset_temp, 1, ENC_BIG_ENDIAN);
    offset_temp += 1;
    if (plength >= 12) {
        /* 1 Byte data unit reference. If packet is fragmented, all packets with this number belong together */
        data_unit_ref = tvb_get_guint8(tvb, offset_temp);
        proto_tree_add_item(param_tree, hf_s7comm_userdata_param_dataunitref, tvb, offset_temp, 1, ENC_BIG_ENDIAN);
        offset_temp += 1;
        /* 1 Byte fragmented flag, if this is not the last data unit (telegram is fragmented) this is != 0 */
        last_data_unit = tvb_get_guint8(tvb, offset_temp);
        proto_tree_add_item(param_tree, hf_s7comm_userdata_param_dataunit, tvb, offset_temp, 1, ENC_BIG_ENDIAN);
        offset_temp += 1;
        proto_tree_add_item(param_tree, hf_s7comm_param_errcod, tvb, offset_temp, 2, ENC_BIG_ENDIAN);
    }

    /**********************************
     * Add data tree
     */
    offset += plength;          /* set offset to the beginning of the data part */
    item = proto_tree_add_item(tree, hf_s7comm_data, tvb, offset, dlength, ENC_NA);
    data_tree = proto_item_add_subtree(item, ett_s7comm_data);

    /* the first 4 bytes of the  data part of a userdata telegram are the same for all types */
    if (dlength >= 4) {
        ret_val = tvb_get_guint8(tvb, offset);

        proto_tree_add_uint(data_tree, hf_s7comm_data_returncode, tvb, offset, 1, ret_val);
        offset += 1;
        /* Not definitely known part, kind of "transport size"? constant 0x09, 1 byte
         * The position is the same as in a data response/write telegram,
         */
        tsize = tvb_get_guint8(tvb, offset);
        proto_tree_add_uint(data_tree, hf_s7comm_data_transport_size, tvb, offset, 1, tsize);
        offset += 1;
        len = tvb_get_ntohs(tvb, offset);
        proto_tree_add_uint(data_tree, hf_s7comm_data_length, tvb, offset, 2, len);
        offset += 2;

        /* Call function to decode the rest of the data part
         * decode only when there is a data part length greater 4 bytes
         */
        if (dlength > 4) {
            switch (funcgroup){
                case S7COMM_UD_FUNCGROUP_PROG:
                    offset = s7comm_decode_ud_prog_subfunc(tvb, data_tree, type, subfunc, dlength, offset);
                    break;
                case S7COMM_UD_FUNCGROUP_CYCLIC:
                    offset = s7comm_decode_ud_cyclic_subfunc(tvb, data_tree, type, subfunc, dlength, offset);
                    break;
                case S7COMM_UD_FUNCGROUP_BLOCK:
                    offset = s7comm_decode_ud_block_subfunc(tvb, pinfo, data_tree, type, subfunc, ret_val, tsize, len, dlength, offset);
                    break;
                case S7COMM_UD_FUNCGROUP_CPU:
                    if (subfunc == S7COMM_UD_SUBF_CPU_READSZL) {
                        offset = s7comm_decode_ud_cpu_szl_subfunc(tvb, pinfo, data_tree, type, ret_val, len, dlength, data_unit_ref, last_data_unit, offset);
                    } else {
                        /* print other currently unknown data as raw bytes */
                        proto_tree_add_item(data_tree, hf_s7comm_userdata_data, tvb, offset, dlength - 4, ENC_NA);
                    }
                    break;
                case S7COMM_UD_FUNCGROUP_SEC:
                    offset = s7comm_decode_ud_security_subfunc(tvb, data_tree, dlength, offset);
                    break;
                case S7COMM_UD_FUNCGROUP_PBC:
                    offset = s7comm_decode_ud_pbc_subfunc(tvb, data_tree, dlength, offset);
                    break;
                case S7COMM_UD_FUNCGROUP_TIME:
                    offset = s7comm_decode_ud_time_subfunc(tvb, data_tree, type, subfunc, ret_val, dlength, offset);
                    break;
                default:
                    break;
            }
        }
    }

    return offset;
}

/*******************************************************************************************************
 *
 * PDU Type: Request or Response
 *
 *******************************************************************************************************/
static guint32
s7comm_decode_req_resp(tvbuff_t *tvb,
                      packet_info *pinfo,
                      proto_tree *tree,
                      guint16 plength,
                      guint16 dlength,
                      guint32 offset,
                      guint8 rosctr)
{
    proto_item *item = NULL;
    proto_tree *param_tree = NULL;
    proto_tree *data_tree = NULL;
    guint8 function = 0;
    guint8 item_count = 0;
    guint8 i;
    guint32 offset_old;
    guint32 len;

    if (plength > 0) {
        /* Add parameter tree */
        item = proto_tree_add_item(tree, hf_s7comm_param, tvb, offset, plength, ENC_NA);
        param_tree = proto_item_add_subtree(item, ett_s7comm_param);
        /* Analyze function */
        function = tvb_get_guint8(tvb, offset);
        /* add param.function to info column */
        col_append_fstr(pinfo->cinfo, COL_INFO, " Function:[%s]", val_to_str(function, param_functionnames, "Unknown function: 0x%02x"));
        proto_tree_add_uint(param_tree, hf_s7comm_param_service, tvb, offset, 1, function);
        /* show param.function code at the tree */
        proto_item_append_text(param_tree, ": (%s)", val_to_str(function, param_functionnames, "Unknown function: 0x%02x"));
        offset += 1;

        if (rosctr == S7COMM_ROSCTR_JOB) {
            switch (function){
                case S7COMM_SERV_READVAR:
                case S7COMM_SERV_WRITEVAR:
                    item_count = tvb_get_guint8(tvb, offset);
                    proto_tree_add_uint(param_tree, hf_s7comm_param_itemcount, tvb, offset, 1, item_count);
                    offset += 1;
                    /* parse item data */
                    for (i = 0; i < item_count; i++) {
                        offset_old = offset;
                        offset = s7comm_decode_param_item(tvb, offset, param_tree, i);
                        /* if length is not a multiple of 2 and this is not the last item, then add a fill-byte */
                        len = offset - offset_old;
                        if ((len % 2) && (i < item_count)) {
                            offset += 1;
                        }
                    }
                    /* in write-function there is a data part */
                    if ((function == S7COMM_SERV_WRITEVAR) && (dlength > 0)) {
                        item = proto_tree_add_item(tree, hf_s7comm_data, tvb, offset, dlength, ENC_NA);
                        data_tree = proto_item_add_subtree(item, ett_s7comm_data);
                        /* Add returned data to data-tree */
                        offset = s7comm_decode_response_read_data(tvb, data_tree, item_count, offset);
                    }
                    break;
                case S7COMM_SERV_SETUPCOMM:
                    offset = s7comm_decode_pdu_setup_communication(tvb, param_tree, offset);
                    break;
                /* Special functions */
                case S7COMM_FUNCREQUESTDOWNLOAD:
                case S7COMM_FUNCDOWNLOADBLOCK:
                case S7COMM_FUNCDOWNLOADENDED:
                case S7COMM_FUNCSTARTUPLOAD:
                case S7COMM_FUNCUPLOAD:
                case S7COMM_FUNCENDUPLOAD:
                    offset = s7comm_decode_plc_controls_param_hex1x(tvb, pinfo, param_tree, plength, offset -1);
                    break;
                case S7COMM_FUNC_PLC_CONTROL:
                    offset = s7comm_decode_plc_controls_param_hex28(tvb, pinfo, param_tree, offset -1);
                    break;
                case S7COMM_FUNC_PLC_STOP:
                    offset = s7comm_decode_plc_controls_param_hex29(tvb, param_tree, offset -1);
                    break;

                default:
                    /* Print unknown part as raw bytes */
                    if (plength > 1) {
                        proto_tree_add_item(param_tree, hf_s7comm_param_data, tvb, offset, plength - 1, ENC_NA);
                    }
                    offset += plength - 1; /* 1 byte function code */
                    if (dlength > 0) {
                        /* Add data tree
                         * First 2 bytes in data seem to be a length indicator of (dlength -4 ), so next 2 bytes
                         * seem to indicate something else. But I'm not sure, so leave it as it is.....
                         */
                        item = proto_tree_add_item(tree, hf_s7comm_data, tvb, offset, dlength, ENC_NA);
                        data_tree = proto_item_add_subtree(item, ett_s7comm_data);
                        proto_tree_add_item(data_tree, hf_s7comm_readresponse_data, tvb, offset, dlength, ENC_NA);
                        offset += dlength;
                    }
                    break;
            }
        } else if (rosctr == S7COMM_ROSCTR_ACK_DATA) {
            switch (function){
                case S7COMM_SERV_READVAR:
                case S7COMM_SERV_WRITEVAR:
                    /* This is a read-response, so the requested data may follow when address in request was ok */
                    item_count = tvb_get_guint8(tvb, offset);
                    proto_tree_add_uint(param_tree, hf_s7comm_param_itemcount, tvb, offset, 1, item_count);
                    offset += 1;
                    /* Add data tree */
                    item = proto_tree_add_item(tree, hf_s7comm_data, tvb, offset, dlength, ENC_NA);
                    data_tree = proto_item_add_subtree(item, ett_s7comm_data);
                    /* Add returned data to data-tree */
                    if ((function == S7COMM_SERV_READVAR) && (dlength > 0)) {
                        offset = s7comm_decode_response_read_data(tvb, data_tree, item_count, offset);
                    } else if ((function == S7COMM_SERV_WRITEVAR) && (dlength > 0)) {
                        offset = s7comm_decode_response_write_data(tvb, data_tree, item_count, offset);
                    }
                    break;
                case S7COMM_SERV_SETUPCOMM:
                    offset = s7comm_decode_pdu_setup_communication(tvb, param_tree, offset);
                    break;
                default:
                    /* Print unknown part as raw bytes */
                    if (plength > 1) {
                        proto_tree_add_item(param_tree, hf_s7comm_param_data, tvb, offset, plength - 1, ENC_NA);
                    }
                    offset += plength - 1; /* 1 byte function code */
                    if (dlength > 0) {
                        /* Add data tree
                         * First 2 bytes in data seem to be a length indicator of (dlength -4 ), so next 2 bytes
                         * seem to indicate something else. But I'm not sure, so leave it as it is.....
                         */
                        item = proto_tree_add_item(tree, hf_s7comm_data, tvb, offset, dlength, ENC_NA);
                        data_tree = proto_item_add_subtree(item, ett_s7comm_data);
                        proto_tree_add_item(data_tree, hf_s7comm_readresponse_data, tvb, offset, dlength, ENC_NA);
                        offset += dlength;
                    }
                    break;
            }
        }
    }
    return offset;
}

/*******************************************************************************************************
 *******************************************************************************************************
 *
 * S7-Protocol (main tree)
 *
 *******************************************************************************************************
 *******************************************************************************************************/
static gboolean
dissect_s7comm(tvbuff_t *tvb,
                packet_info *pinfo,
                proto_tree *tree,
                void *data _U_)
{
    proto_item *s7comm_item = NULL;
    proto_item *s7comm_sub_item = NULL;
    proto_tree *s7comm_tree = NULL;
    proto_tree *s7comm_header_tree = NULL;

    guint32 offset = 0;

    guint8 rosctr = 0;
    guint8 hlength = 10;                /* Header 10 Bytes, when type 2 or 3 (Response) -> 12 Bytes */
    guint16 plength = 0;
    guint16 dlength = 0;

    /*----------------- Heuristic Checks - Begin */
    /* 1) check for minimum length */
    if(tvb_captured_length(tvb) < S7COMM_MIN_TELEGRAM_LENGTH)
        return FALSE;
    /* 2) first byte must be 0x32 */
    if (tvb_get_guint8(tvb, 0) != S7COMM_PROT_ID)
        return FALSE;
    /* 3) second byte is a type field and only can contain values between 0x01-0x07 (1/2/3/7) */
    if (tvb_get_guint8(tvb, 1) < 0x01 || tvb_get_guint8(tvb, 1) > 0x07)
        return FALSE;
    /*----------------- Heuristic Checks - End */

    col_set_str(pinfo->cinfo, COL_PROTOCOL, PROTO_TAG_S7COMM);
    col_clear(pinfo->cinfo, COL_INFO);

    rosctr = tvb_get_guint8(tvb, 1);                            /* Get the type byte */
    if (rosctr == 2 || rosctr == 3) hlength = 12;               /* Header 10 Bytes, when type 2 or 3 (response) -> 12 Bytes */

    /* display some infos in info-column of wireshark */
    col_add_fstr(pinfo->cinfo, COL_INFO, "ROSCTR:[%-8s]", val_to_str(rosctr, rosctr_names, "Unknown: 0x%02x"));

    s7comm_item = proto_tree_add_item(tree, proto_s7comm, tvb, 0, -1, ENC_NA);
    s7comm_tree = proto_item_add_subtree(s7comm_item, ett_s7comm);

    /* insert header tree */
    s7comm_sub_item = proto_tree_add_item(s7comm_tree, hf_s7comm_header,
                      tvb, offset, hlength, ENC_NA);

    /* insert sub-items in header tree */
    s7comm_header_tree = proto_item_add_subtree(s7comm_sub_item, ett_s7comm_header);

    /* Protocol Identifier, constant 0x32 */
    proto_tree_add_item(s7comm_header_tree, hf_s7comm_header_protid, tvb, offset, 1, ENC_BIG_ENDIAN);
    offset += 1;

    /* ROSCTR (Remote Operating Service Control) - PDU Type */
    proto_tree_add_uint(s7comm_header_tree, hf_s7comm_header_rosctr, tvb, offset, 1, rosctr);
    /* Show pdu type beside the header tree */
    proto_item_append_text(s7comm_header_tree, ": (%s)", val_to_str(rosctr, rosctr_names, "Unknown ROSCTR: 0x%02x"));
    offset += 1;
    /* Redundacy ID, reserved */
    proto_tree_add_item(s7comm_header_tree, hf_s7comm_header_redid, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;
    /* Protocol Data Unit Reference */
    proto_tree_add_item(s7comm_header_tree, hf_s7comm_header_pduref, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;
    /* Parameter length */
    plength = tvb_get_ntohs(tvb, offset);
    proto_tree_add_uint(s7comm_header_tree, hf_s7comm_header_parlg, tvb, offset, 2, plength);
    offset += 2;
    /* Data length */
    dlength = tvb_get_ntohs(tvb, offset);
    proto_tree_add_uint(s7comm_header_tree, hf_s7comm_header_datlg, tvb, offset, 2, dlength);
    offset += 2;
    /* when type is 2 or 3 there are 2 bytes with errorclass and errorcode */
    if (hlength == 12) {
        proto_tree_add_item(s7comm_header_tree, hf_s7comm_header_errcls, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
        proto_tree_add_item(s7comm_header_tree, hf_s7comm_header_errcod, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
    }

    switch (rosctr) {
        case S7COMM_ROSCTR_JOB:
        case S7COMM_ROSCTR_ACK_DATA:
            s7comm_decode_req_resp(tvb, pinfo, s7comm_tree, plength, dlength, offset, rosctr);
            break;
        case S7COMM_ROSCTR_USERDATA:
            s7comm_decode_ud(tvb, pinfo, s7comm_tree, plength, dlength, offset);
            break;
    }
    /*else {  Unknown pdu, maybe passed to another dissector? }
    */
    return TRUE;
}

/*******************************************************************************************************
 *******************************************************************************************************/
void
proto_register_s7comm (void)
{
    /* format:
     * {&(field id), {name, abbrev, type, display, strings, bitmask, blurb, HFILL}}.
     */
    static hf_register_info hf[] = {
        { &hf_s7comm_header,
        { "Header", "s7comm.header", FT_NONE, BASE_NONE, NULL, 0x0,
          "This is the header of S7 communication", HFILL }},
        { &hf_s7comm_header_protid,
        { "Protocol Id", "s7comm.header.protid", FT_UINT8, BASE_HEX, NULL, 0x0,
          "Protocol Identification, 0x32 for S7", HFILL }},
        { &hf_s7comm_header_rosctr,
        { "ROSCTR", "s7comm.header.rosctr", FT_UINT8, BASE_DEC, VALS(rosctr_names), 0x0,
          "Remote Operating Service Control", HFILL }},
        { &hf_s7comm_header_redid,
        { "Redundancy Identification (Reserved)", "s7comm.header.redid", FT_UINT16, BASE_HEX, NULL, 0x0,
          "Redundancy Identification (Reserved), should be always 0x0000", HFILL }},
        { &hf_s7comm_header_pduref,
        { "Protocol Data Unit Reference", "s7comm.header.pduref", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_header_parlg,
        { "Parameter length", "s7comm.header.parlg", FT_UINT16, BASE_DEC, NULL, 0x0,
          "Specifies the entire length of the parameter block in bytes", HFILL }},
        { &hf_s7comm_header_datlg,
        { "Data length", "s7comm.header.datlg", FT_UINT16, BASE_DEC, NULL, 0x0,
          "Specifies the entire length of the data block in bytes", HFILL }},
        { &hf_s7comm_header_errcls,
        { "Error class", "s7comm.header.errcls", FT_UINT8, BASE_HEX, VALS(errcls_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_header_errcod,
        { "Error code", "s7comm.header.errcod", FT_UINT8, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},

        { &hf_s7comm_param,
        { "Parameter", "s7comm.param", FT_NONE, BASE_NONE, NULL, 0x0,
          "This is the parameter part of S7 communication", HFILL }},
        { &hf_s7comm_param_errcod,
        { "Error code", "s7comm.param.errcod", FT_UINT16, BASE_HEX, VALS(param_errcode_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_param_service,
        { "Function", "s7comm.param.func", FT_UINT8, BASE_HEX, VALS(param_functionnames), 0x0,
          "Indicates the function of parameter/data", HFILL }},
        { &hf_s7comm_param_maxamq_calling,
        { "Max AmQ (parallel jobs with ack) calling", "s7comm.param.maxamq_calling", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_param_maxamq_called,
        { "Max AmQ (parallel jobs with ack) called", "s7comm.param.maxamq_called", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_param_setup_reserved1,
        { "Reserved", "s7comm.param.setup_reserved1", FT_UINT8, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_param_neg_pdu_length,
        { "PDU length", "s7comm.param.pdu_length", FT_UINT16, BASE_DEC, NULL, 0x0,
          "Negotiated PDU length", HFILL }},
        { &hf_s7comm_param_itemcount,
        { "Item count", "s7comm.param.itemcount", FT_UINT8, BASE_DEC, NULL, 0x0,
          "Number of Items in parameter/data part", HFILL }},
        { &hf_s7comm_param_data,
        { "Parameter data", "s7comm.param.data", FT_BYTES, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_param_item,
        { "Item", "s7comm.param.item", FT_NONE, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_param_subitem,
        { "Subitem", "s7comm.param.subitem", FT_NONE, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_item_varspec,
        { "Variable specification", "s7comm.param.item.varspec", FT_UINT8, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_item_varspec_length,
        { "Length of following address specification", "s7comm.param.item.varspec_length", FT_UINT8, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_item_syntax_id,
        { "Syntax Id", "s7comm.param.item.syntaxid", FT_UINT8, BASE_HEX, VALS(item_syntaxid_names), 0x0,
          "Syntax Id, format type of following address specification", HFILL }},
        { &hf_s7comm_item_transport_size,
        { "Transport size", "s7comm.param.item.transp_size", FT_UINT8, BASE_DEC, VALS(item_transportsizenames), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_item_length,
        { "Length", "s7comm.param.item.length", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_item_db,
        { "DB number", "s7comm.param.item.db", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_item_area,
        { "Area", "s7comm.param.item.area", FT_UINT8, BASE_HEX, VALS(item_areanames), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_item_address,
        { "Address", "s7comm.param.item.address", FT_UINT24, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_item_address_byte,
        { "Byte Address", "s7comm.param.item.address.byte", FT_UINT24, BASE_DEC, NULL, 0x7fff8,
          NULL, HFILL }},
        { &hf_s7comm_item_address_bit,
        { "Bit Address", "s7comm.param.item.address.bit", FT_UINT24, BASE_DEC, NULL, 0x000007,
          NULL, HFILL }},
        { &hf_s7comm_item_address_nr,
        { "Number (T/C/BLOCK)", "s7comm.param.item.address.number", FT_UINT24, BASE_DEC, NULL, 0x00ffff,
          NULL, HFILL }},
        /* Special variable read with Syntax-Id 0xb0 (DBREAD) */
        { &hf_s7comm_item_dbread_numareas,
        { "Number of areas", "s7comm.param.item.dbread.numareas", FT_UINT8, BASE_DEC, NULL, 0x0,
          "Number of area specifications following", HFILL }},
        { &hf_s7comm_item_dbread_length,
        { "Bytes to read", "s7comm.param.item.dbread.length", FT_UINT8, BASE_DEC, NULL, 0x0,
          "Number of bytes to read", HFILL }},
        { &hf_s7comm_item_dbread_db,
        { "DB number", "s7comm.param.item.dbread.db", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_item_dbread_startadr,
        { "Start address", "s7comm.param.item.dbread.startaddress", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        /* NCK access with Syntax-Id 0x82 */
        { &hf_s7comm_item_nck_areaunit,
        { "NCK Area/Unit", "s7comm.param.item.nck.area_unit", FT_UINT8, BASE_HEX, NULL, 0x0,
          "NCK Area/Unit: Bitmask aaauuuuu: a=area, u=unit", HFILL }},
        { &hf_s7comm_item_nck_area,
        { "NCK Area", "s7comm.param.item.nck.area", FT_UINT8, BASE_DEC, VALS(nck_area_names), 0xe0,
          NULL, HFILL }},
        { &hf_s7comm_item_nck_unit,
        { "NCK Unit", "s7comm.param.item.nck.unit", FT_UINT8, BASE_DEC, NULL, 0x1f,
          NULL, HFILL }},
        { &hf_s7comm_item_nck_column,
        { "NCK Column number", "s7comm.param.item.nck.column", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_item_nck_line,
        { "NCK Line number", "s7comm.param.item.nck.line", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_item_nck_module,
        { "NCK Module", "s7comm.param.item.nck.module", FT_UINT8, BASE_HEX | BASE_EXT_STRING, &nck_module_names_ext, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_item_nck_linecount,
        { "NCK Linecount", "s7comm.param.item.nck.linecount", FT_UINT8, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},

        { &hf_s7comm_data,
        { "Data", "s7comm.data", FT_NONE, BASE_NONE, NULL, 0x0,
          "This is the data part of S7 communication", HFILL }},
        { &hf_s7comm_data_returncode,
        { "Return code", "s7comm.data.returncode", FT_UINT8, BASE_HEX, VALS(s7comm_item_return_valuenames), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_transport_size,
        { "Transport size", "s7comm.data.transportsize", FT_UINT8, BASE_HEX, VALS(data_transportsizenames), 0x0,
          "Data type / Transport size. If 3, 4 or 5 the following length gives the number of bits, otherwise the number of bytes.", HFILL }},
        { &hf_s7comm_data_length,
        { "Length", "s7comm.data.length", FT_UINT16, BASE_DEC, NULL, 0x0,
          "Length of data", HFILL }},

        { &hf_s7comm_data_item,
        { "Item", "s7comm.data.item", FT_NONE, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},

        { &hf_s7comm_readresponse_data,
        { "Data", "s7comm.resp.data", FT_BYTES, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_fillbyte,
        { "Fill byte", "s7comm.data.fillbyte", FT_UINT8, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},

        { &hf_s7comm_userdata_data,
        { "Data", "s7comm.data.userdata", FT_BYTES, BASE_NONE, NULL, 0x0,
          "Userdata data", HFILL }},

        /* Userdata parameter 8/12 Bytes len*/
        { &hf_s7comm_userdata_param_head,
        { "Parameter head", "s7comm.param.userdata.head", FT_UINT24, BASE_HEX, NULL, 0x0,
          "Header before parameter (constant 0x000112)", HFILL }},
        { &hf_s7comm_userdata_param_len,
        { "Parameter length", "s7comm.param.userdata.length", FT_UINT8, BASE_DEC, NULL, 0x0,
          "Length of following parameter data (without head)", HFILL }},
        { &hf_s7comm_userdata_param_reqres2,
        { "Unknown (Request/Response)", "s7comm.param.userdata.reqres1", FT_UINT8, BASE_HEX, NULL, 0x0,
          "Unknown part, possible request/response (0x11, 0x12), but not in programmer commands", HFILL }},

        { &hf_s7comm_userdata_param_type,
        { "Type", "s7comm.param.userdata.type", FT_UINT8, BASE_DEC, VALS(userdata_type_names), 0xf0,
          "Type of parameter", HFILL }},

        { &hf_s7comm_userdata_param_funcgroup,
        { "Function group", "s7comm.param.userdata.funcgroup", FT_UINT8, BASE_DEC, VALS(userdata_functiongroup_names), 0x0f,
          NULL, HFILL }},

        { &hf_s7comm_userdata_param_subfunc_prog,
        { "Subfunction", "s7comm.param.userdata.subfunc", FT_UINT8, BASE_DEC, VALS(userdata_prog_subfunc_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_userdata_param_subfunc_cyclic,
        { "Subfunction", "s7comm.param.userdata.subfunc", FT_UINT8, BASE_DEC, VALS(userdata_cyclic_subfunc_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_userdata_param_subfunc_block,
        { "Subfunction", "s7comm.param.userdata.subfunc", FT_UINT8, BASE_DEC, VALS(userdata_block_subfunc_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_userdata_param_subfunc_cpu,
        { "Subfunction", "s7comm.param.userdata.subfunc", FT_UINT8, BASE_DEC, VALS(userdata_cpu_subfunc_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_userdata_param_subfunc_sec,
        { "Subfunction", "s7comm.param.userdata.subfunc", FT_UINT8, BASE_DEC, VALS(userdata_sec_subfunc_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_userdata_param_subfunc_time,
        { "Subfunction", "s7comm.param.userdata.subfunc", FT_UINT8, BASE_DEC, VALS(userdata_time_subfunc_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_userdata_param_subfunc,
        { "Subfunction", "s7comm.param.userdata.subfunc", FT_UINT8, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},

        { &hf_s7comm_userdata_param_seq_num,
        { "Sequence number", "s7comm.param.userdata.seq_num", FT_UINT8, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},

        { &hf_s7comm_userdata_param_dataunitref,
        { "Data unit reference number", "s7comm.param.userdata.dataunitref", FT_UINT8, BASE_DEC, NULL, 0x0,
          "Data unit reference number if PDU is fragmented", HFILL }},

        { &hf_s7comm_userdata_param_dataunit,
        { "Last data unit", "s7comm.param.userdata.lastdataunit", FT_UINT8, BASE_HEX, VALS(userdata_lastdataunit_names), 0x0,
          NULL, HFILL }},

        /* block functions / info */
        { &hf_s7comm_ud_blockinfo_block_type,
        { "Block type", "s7comm.blockinfo.blocktype", FT_UINT8, BASE_DEC, VALS(blocktype_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_block_cnt,
        { "Block count", "s7comm.blockinfo.block_count", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_block_num,
        { "Block number", "s7comm.blockinfo.block_num", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_block_flags,
        { "Block flags (unknown)", "s7comm.blockinfo.flags", FT_UINT8, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_block_lang,
        { "Block language", "s7comm.blockinfo.block_lang", FT_UINT8, BASE_DEC, VALS(blocklanguage_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_block_num_ascii,
        { "Block number", "s7comm.data.blockinfo.block_number", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_filesys,
        { "Filesystem", "s7comm.data.blockinfo.filesys", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_res_const1,
        { "Constant 1", "s7comm.blockinfo.res_const1", FT_UINT8, BASE_HEX, NULL, 0x0,
          "Possible constant 1", HFILL }},
        { &hf_s7comm_ud_blockinfo_res_infolength,
        { "Length of Info", "s7comm.blockinfo.res_infolength", FT_UINT16, BASE_DEC, NULL, 0x0,
          "Length of Info in bytes", HFILL }},
        { &hf_s7comm_ud_blockinfo_res_unknown2,
        { "Unknown blockinfo 2", "s7comm.blockinfo.res_unknown2", FT_UINT16, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_res_const3,
        { "Constant 3", "s7comm.blockinfo.res_const3", FT_STRING, BASE_NONE, NULL, 0x0,
          "Possible constant 3, seems to be always 'pp'", HFILL }},
        { &hf_s7comm_ud_blockinfo_res_unknown,
        { "Unknown byte(s) blockinfo", "s7comm.blockinfo.res_unknown", FT_BYTES, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_subblk_type,
        { "Subblk type", "s7comm.blockinfo.subblk_type", FT_UINT8, BASE_DEC, VALS(subblktype_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_load_mem_len,
        { "Length load memory", "s7comm.blockinfo.load_mem_len", FT_UINT32, BASE_DEC, NULL, 0x0,
          "Length of load memory in bytes", HFILL }},
        { &hf_s7comm_ud_blockinfo_blocksecurity,
        { "Block Security", "s7comm.blockinfo.blocksecurity", FT_UINT32, BASE_DEC, VALS(blocksecurity_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_interface_timestamp,
        { "Interface timestamp", "s7comm.blockinfo.interface_timestamp", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_code_timestamp,
        { "Code timestamp", "s7comm.blockinfo.code_timestamp", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_ssb_len,
        { "SSB length", "s7comm.blockinfo.ssb_len", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_add_len,
        { "ADD length", "s7comm.blockinfo.add_len", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_localdata_len,
        { "Localdata length", "s7comm.blockinfo.localdata_len", FT_UINT16, BASE_DEC, NULL, 0x0,
          "Length of localdata in bytes", HFILL }},
        { &hf_s7comm_ud_blockinfo_mc7_len,
        { "MC7 code length", "s7comm.blockinfo.mc7_len", FT_UINT16, BASE_DEC, NULL, 0x0,
          "Length of MC7 code in bytes", HFILL }},
        { &hf_s7comm_ud_blockinfo_author,
        { "Author", "s7comm.blockinfo.author", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_family,
        { "Family", "s7comm.blockinfo.family", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_headername,
        { "Name (Header)", "s7comm.blockinfo.headername", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_headerversion,
        { "Version (Header)", "s7comm.blockinfo.headerversion", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_checksum,
        { "Block checksum", "s7comm.blockinfo.checksum", FT_UINT16, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_reserved1,
        { "Reserved 1", "s7comm.blockinfo.reserved1", FT_UINT32, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_ud_blockinfo_reserved2,
        { "Reserved 2", "s7comm.blockinfo.reserved2", FT_UINT32, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},

        /* Flags in blockinfo response */
        { &hf_s7comm_userdata_blockinfo_flags,
        { "Block flags", "s7comm.param.userdata.blockinfo.flags", FT_UINT8, BASE_HEX, NULL, 0xff,
          "Some block configuration flags", HFILL }},
         /* Bit : 0 -> DB Linked = true */
        { &hf_s7comm_userdata_blockinfo_linked,
        { "Linked", "s7comm.param.userdata.blockinfo.linked", FT_BOOLEAN, 8, TFS(&tfs_yes_no), 0x01,
          NULL, HFILL }},
        /* Bit : 1 -> Standard block = true */
        { &hf_s7comm_userdata_blockinfo_standard_block,
        { "Standard block", "s7comm.param.userdata.blockinfo.standard_block", FT_BOOLEAN, 8, TFS(&tfs_yes_no), 0x02,
          NULL, HFILL }},
        /* Bit : 5 -> DB Non Retain = true */
        { &hf_s7comm_userdata_blockinfo_nonretain,
        { "Non Retain", "s7comm.param.userdata.blockinfo.nonretain", FT_BOOLEAN, 8, TFS(&tfs_yes_no), 0x08,
          NULL, HFILL }},

        /* Programmer commands, diagnostic data */
        { &hf_s7comm_diagdata_req_askheadersize,
        { "Ask header size", "s7comm.diagdata.req.askheadersize", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_diagdata_req_asksize,
        { "Ask size", "s7comm.diagdata.req.asksize", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_diagdata_req_unknown,
        { "Unknown byte(s) diagdata", "s7comm.diagdata.req.unknown", FT_BYTES, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_diagdata_req_answersize,
        { "Answer size", "s7comm.diagdata.req.answersize", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_diagdata_req_block_type,
        { "Block type", "s7comm.diagdata.req.blocktype", FT_UINT8, BASE_DEC, VALS(subblktype_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_diagdata_req_block_num,
        { "Block number", "s7comm.diagdata.req.blocknumber", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_diagdata_req_startaddr_awl,
        { "Start address AWL", "s7comm.diagdata.req.startaddr_awl", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_diagdata_req_saz,
        { "Step address counter (SAZ)", "s7comm.diagdata.req.saz", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_diagdata_req_number_of_lines,
        { "Number of lines", "s7comm.diagdata.req.number_of_lines", FT_UINT8, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_diagdata_req_line_address,
        { "Address", "s7comm.diagdata.req.line_address", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},

         /* Flags for requested registers in diagnostic data telegrams */
        { &hf_s7comm_diagdata_registerflag,
        { "Registers", "s7comm.diagdata.register", FT_UINT8, BASE_HEX, NULL, 0x00,
          "Requested registers", HFILL }},
        { &hf_s7comm_diagdata_registerflag_stw,
        { "STW", "s7comm.diagdata.register.stw", FT_BOOLEAN, 8, NULL, 0x01,
          "STW / Status word", HFILL }},
        { &hf_s7comm_diagdata_registerflag_accu1,
        { "ACCU1", "s7comm.diagdata.register.accu1", FT_BOOLEAN, 8, NULL, 0x02,
          "ACCU1 / Accumulator 1", HFILL }},
        { &hf_s7comm_diagdata_registerflag_accu2,
        { "ACCU2", "s7comm.diagdata.register.accu2", FT_BOOLEAN, 8, NULL, 0x04,
          "ACCU2 / Accumulator 2", HFILL }},
        { &hf_s7comm_diagdata_registerflag_ar1,
        { "AR1", "s7comm.diagdata.register.ar1", FT_BOOLEAN, 8, NULL, 0x08,
          "AR1 / Addressregister 1", HFILL }},
        { &hf_s7comm_diagdata_registerflag_ar2,
        { "AR2", "s7comm.diagdata.register.ar2", FT_BOOLEAN, 8, NULL, 0x10,
          "AR2 / Addressregister 2", HFILL }},
        { &hf_s7comm_diagdata_registerflag_db1,
        { "DB1", "s7comm.diagdata.register.db1", FT_BOOLEAN, 8, NULL, 0x20,
          "DB1 (global)/ Datablock register 1", HFILL }},
        { &hf_s7comm_diagdata_registerflag_db2,
        { "DB2", "s7comm.diagdata.register.db2", FT_BOOLEAN, 8, NULL, 0x40,
          "DB2 (instance) / Datablock register 2", HFILL }},

        /* timefunction: s7 timestamp */
        { &hf_s7comm_data_ts,
        { "S7 Timestamp", "s7comm.data.ts", FT_ABSOLUTE_TIME, ABSOLUTE_TIME_LOCAL, NULL, 0x00,
          "S7 Timestamp, BCD coded", HFILL }},
        { &hf_s7comm_data_ts_reserved,
        { "S7 Timestamp - Reserved", "s7comm.data.ts_reserved", FT_UINT8, BASE_HEX, NULL, 0x00,
          "S7 Timestamp: Reserved byte", HFILL }},
        { &hf_s7comm_data_ts_year1,
        { "S7 Timestamp - Year 1", "s7comm.data.ts_year1", FT_UINT8, BASE_DEC, NULL, 0x00,
          "S7 Timestamp: BCD coded year thousands/hundreds, should be ignored (19 or 20)", HFILL }},
        { &hf_s7comm_data_ts_year2,
        { "S7 Timestamp - Year 2", "s7comm.data.ts_year2", FT_UINT8, BASE_DEC, NULL, 0x00,
          "S7 Timestamp: BCD coded year, if 00...89 then it's 2000...2089, else 1990...1999", HFILL }},
        { &hf_s7comm_data_ts_month,
        { "S7 Timestamp - Month", "s7comm.data.ts_month", FT_UINT8, BASE_DEC, NULL, 0x00,
          "S7 Timestamp: BCD coded month", HFILL }},
        { &hf_s7comm_data_ts_day,
        { "S7 Timestamp - Day", "s7comm.data.ts_day", FT_UINT8, BASE_DEC, NULL, 0x00,
          "S7 Timestamp: BCD coded day", HFILL }},
        { &hf_s7comm_data_ts_hour,
        { "S7 Timestamp - Hour", "s7comm.data.ts_hour", FT_UINT8, BASE_DEC, NULL, 0x00,
          "S7 Timestamp: BCD coded hour", HFILL }},
        { &hf_s7comm_data_ts_minute,
        { "S7 Timestamp - Minute", "s7comm.data.ts_minute", FT_UINT8, BASE_DEC, NULL, 0x00,
          "S7 Timestamp: BCD coded minute", HFILL }},
        { &hf_s7comm_data_ts_second,
        { "S7 Timestamp - Second", "s7comm.data.ts_second", FT_UINT8, BASE_DEC, NULL, 0x00,
          "S7 Timestamp: BCD coded second", HFILL }},
        { &hf_s7comm_data_ts_millisecond,
        { "S7 Timestamp - Milliseconds", "s7comm.data.ts_millisecond", FT_UINT16, BASE_DEC, NULL, 0x00,
          "S7 Timestamp: BCD coded milliseconds (left 3 nibbles)", HFILL }},
        { &hf_s7comm_data_ts_weekday,
        { "S7 Timestamp - Weekday", "s7comm.data.ts_weekday", FT_UINT16, BASE_DEC, VALS(weekdaynames), 0x000f,
          "S7 Timestamp: Weekday number (right nibble, 1=Su,2=Mo,..)", HFILL }},

        /* Function 0x28 (PLC control functions) ans 0x29 */
        { &hf_s7comm_data_plccontrol_part1_unknown,
        { "Part 1 unknown bytes", "s7comm.data.plccontrol.part1_unknown", FT_BYTES, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_plccontrol_part1_len,
        { "Length part 1", "s7comm.data.plccontrol.part1_len", FT_UINT16, BASE_DEC, NULL, 0x0,
          "Length of part 1 in bytes", HFILL }},
        { &hf_s7comm_data_plccontrol_argument,
        { "Argument", "s7comm.data.plccontrol.argument", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_plccontrol_block_cnt,
        { "Number of blocks", "s7comm.data.plccontrol.block_cnt", FT_UINT8, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_plccontrol_part1_unknown2,
        { "Unknown byte", "s7comm.data.plccontrol.part1_unknown2", FT_UINT8, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_plccontrol_block_unknown,
        { "Unknown char before Block type", "s7comm.data.plccontrol.block_unknown", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_plccontrol_block_type,
        { "Block type", "s7comm.data.plccontrol.block_type", FT_UINT8, BASE_DEC, VALS(blocktype_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_plccontrol_block_num,
        { "Block number", "s7comm.data.plccontrol.block_number", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_plccontrol_dest_filesys,
        { "Destination filesystem", "s7comm.data.plccontrol.dest_filesys", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_plccontrol_part2_len,
        { "Length part 2", "s7comm.data.plccontrol.part2_len", FT_UINT8, BASE_DEC, NULL, 0x0,
          "Length of part 2 in bytes", HFILL }},
        { &hf_s7comm_data_plccontrol_pi_service,
        { "PI (program invocation) Service", "s7comm.data.plccontrol.pi_service", FT_STRING, BASE_NONE, NULL, 0x0,
          "Known: _INSE = Activate a module, _DELE = Delete a passive module, _PROGRAM = Start/Stop the PLC, _PLC_MEMORYRESET = Reset the PLC memory" , HFILL }},

        /* block control functions */
        { &hf_s7comm_data_blockcontrol_unknown1,
        { "Unknown byte(s) in blockcontrol", "s7comm.data.blockcontrol.unknown1", FT_BYTES, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_blockcontrol_errorcode,
        { "Errorcode", "s7comm.data.blockcontrol.errorcode", FT_UINT16, BASE_HEX, NULL, 0x0,
          "Errorcode, 0 on success", HFILL }},
        { &hf_s7comm_data_blockcontrol_part1_len,
        { "Length part 1", "s7comm.data.blockcontrol.part1_len", FT_UINT8, BASE_DEC, NULL, 0x0,
          "Length of part 1 in bytes", HFILL }},
        { &hf_s7comm_data_blockcontrol_file_ident,
        { "File identifier", "s7comm.data.blockcontrol.file_identifier", FT_STRING, BASE_NONE, NULL, 0x0,
          "File identifier: '_'=complete module; '$'=Module header for up-loading", HFILL }},
        { &hf_s7comm_data_blockcontrol_block_unknown,
        { "Unknown char before Block type", "s7comm.data.blockcontrol.block_unknown", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_blockcontrol_block_type,
        { "Block type", "s7comm.data.blockcontrol.block_type", FT_UINT8, BASE_DEC, VALS(blocktype_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_blockcontrol_block_num,
        { "Block number", "s7comm.data.blockcontrol.block_number", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_blockcontrol_dest_filesys,
        { "Destination filesystem", "s7comm.data.blockcontrol.dest_filesys", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_blockcontrol_part2_len,
        { "Length part 2", "s7comm.data.blockcontrol.part2_len", FT_UINT8, BASE_DEC, NULL, 0x0,
          "Length of part 2 in bytes", HFILL }},
        { &hf_s7comm_data_blockcontrol_part2_unknown,
        { "Unknown char before load mem", "s7comm.data.blockcontrol.part2_unknown", FT_STRING, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_data_blockcontrol_loadmem_len,
        { "Length of load memory", "s7comm.data.blockcontrol.loadmem_len", FT_STRING, BASE_NONE, NULL, 0x0,
          "Length of load memory in bytes", HFILL }},
        { &hf_s7comm_data_blockcontrol_mc7code_len,
        { "Length of MC7 code", "s7comm.data.blockcontrol.mc7code_len", FT_STRING, BASE_NONE, NULL, 0x0,
          "Length of MC7 code in bytes", HFILL }},

        /* Variable table */
        { &hf_s7comm_vartab_data_type,
        { "Type of data", "s7comm.vartab.data_type", FT_UINT8, BASE_DEC, VALS(userdata_prog_vartab_type_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_vartab_byte_count,
        { "Byte count", "s7comm.vartab.byte_count", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_vartab_unknown,
        { "Unknown byte(s) vartab", "s7comm.vartab.unknown", FT_BYTES, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_vartab_item_count,
        { "Item count", "s7comm.vartab.item_count", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_vartab_req_memory_area,
        { "Memory area", "s7comm.vartab.req.memory_area", FT_UINT8, BASE_DEC, VALS(userdata_prog_vartab_area_names), 0x0,
          NULL, HFILL }},
        { &hf_s7comm_vartab_req_repetition_factor,
        { "Repetition factor", "s7comm.vartab.req.repetition_factor", FT_UINT8, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_vartab_req_db_number,
        { "DB number", "s7comm.vartab.req.db_number", FT_UINT16, BASE_DEC, NULL, 0x0,
          "DB number, when area is DB", HFILL }},
        { &hf_s7comm_vartab_req_startaddress,
        { "Startaddress", "s7comm.vartab.req.startaddress", FT_UINT16, BASE_DEC, NULL, 0x0,
          "Startaddress / byteoffset", HFILL }},

        /* cyclic data */
        { &hf_s7comm_cycl_interval_timebase,
        { "Interval timebase", "s7comm.cyclic.interval_timebase", FT_UINT8, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_cycl_interval_time,
        { "Interval time", "s7comm.cyclic.interval_time", FT_UINT8, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},

        /* PBC, Programmable Block Functions */
        { &hf_s7comm_pbc_unknown,
        { "PBC BSEND/BRECV unknown", "s7comm.pbc.bsend.unknown", FT_UINT8, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_pbc_r_id,
        { "PBC BSEND/BRECV R_ID", "s7comm.pbc.req.bsend.r_id", FT_UINT32, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},

        /* TIA Portal stuff */
        { &hf_s7comm_tia1200_item_reserved1,
        { "1200 sym Reserved", "s7comm.tiap.item.reserved1", FT_UINT8, BASE_HEX, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_tia1200_item_area1,
        { "1200 sym root area 1", "s7comm.tiap.item.area1", FT_UINT16, BASE_HEX, VALS(tia1200_var_item_area1_names), 0x0,
          "Area from where to read: DB or Inputs, Outputs, etc.", HFILL }},
        { &hf_s7comm_tia1200_item_area2,
        { "1200 sym root area 2", "s7comm.tiap.item.area2", FT_UINT16, BASE_HEX, VALS(tia1200_var_item_area2_names), 0x0,
          "Specifies the area from where to read", HFILL }},
        { &hf_s7comm_tia1200_item_area2unknown,
        { "1200 sym root area 2 unknown", "s7comm.tiap.item.area2unknown", FT_UINT16, BASE_HEX, NULL, 0x0,
          "For current unknown areas", HFILL }},
        { &hf_s7comm_tia1200_item_dbnumber,
        { "1200 sym root DB number", "s7comm.tiap.item.dbnumber", FT_UINT16, BASE_DEC, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_tia1200_item_crc,
        { "1200 sym CRC", "s7comm.tiap.item.crc", FT_UINT32, BASE_HEX, NULL, 0x0,
          "CRC generated out of symbolic name with (x^32+x^31+x^30+x^29+x^28+x^26+x^23+x^21+x^19+x^18+x^15+x^14+x^13+x^12+x^9+x^8+x^4+x+1)", HFILL }},
        { &hf_s7comm_tia1200_var_lid_flags,
        { "LID flags", "s7comm.tiap.item.lid_flags", FT_UINT8, BASE_DEC, VALS(tia1200_var_lid_flag_names), 0xf0,
          NULL, HFILL }},
        { &hf_s7comm_tia1200_substructure_item,
        { "Substructure", "s7comm.tiap.item.substructure", FT_NONE, BASE_NONE, NULL, 0x0,
          NULL, HFILL }},
        { &hf_s7comm_tia1200_item_value,
        { "Value", "s7comm.tiap.item.value", FT_UINT32, BASE_DEC, NULL, 0x0fffffff,
          NULL, HFILL }},
    };

    static gint *ett[] = {
        &ett_s7comm,
        &ett_s7comm_header,
        &ett_s7comm_param,
        &ett_s7comm_param_item,
        &ett_s7comm_param_subitem,
        &ett_s7comm_data,
        &ett_s7comm_data_item,
        &ett_s7comm_item_address,
        &ett_s7comm_diagdata_registerflag,
        &ett_s7comm_userdata_blockinfo_flags,
    };

    proto_s7comm = proto_register_protocol (
            "S7 Communication",         /* name */
            "S7COMM",                   /* short name */
            "s7comm"                    /* abbrev */
            );

    proto_register_field_array(proto_s7comm, hf, array_length (hf));

    s7comm_register_szl_types(proto_s7comm);

    proto_register_subtree_array(ett, array_length (ett));
}

/* Register this protocol */
void
proto_reg_handoff_s7comm(void)
{
    /* register ourself as an heuristic cotp (ISO 8073) payload dissector */
    heur_dissector_add("cotp", dissect_s7comm, "S7 Communication over COTP", "s7comm_cotp", proto_s7comm, HEURISTIC_ENABLE);
    heur_dissector_add("cotp_is", dissect_s7comm, "S7 Communication over COTP", "s7comm_cotp_is", proto_s7comm, HEURISTIC_ENABLE);
}

/*
 * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 4
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=4 tabstop=8 expandtab:
 * :indentSize=4:tabSize=8:noTabs=true:
 */
