/* packet-elcom.c
 * Routines for elcom packet dissection
 * Copyright 2008, 2011 juha.takala@iki.fi (Juha Takala)
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * Copied from packet-imap.c
 *
 * 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.
 *
 * I found the protocol specification at
 *  http://www.sintef.no/upload/Energiforskning/Energisystemer/ELCOM%2090.pdf
 */

#include "config.h"

#include <epan/packet.h>

#define TCP_PORT_ELCOM        5997

/* Application level: */
#define A_CONRQ        0x04
#define A_CONRS        0x05

/* Presentation level: */
#define P_CONRQ        0x00
#define P_CONRS        0x10
#define P_RELRQ        0x20
#define P_RELRS        0x30
#define P_DATRQ        0x80

#define TC_REQ         0x40
#define TC_RSP         0x41

#define LOWADR_LEN 17
#define SUFFIX_LEN  2
#define TOTAL_LEN (LOWADR_LEN + SUFFIX_LEN + 2)

#if 0 /* ??? */
#define ELCOM_UNKNOWN_ENDIAN 0
#define ELCOM_LITTLE_ENDIAN  1
#define ELCOM_BIG_ENDIAN     2
#endif

void proto_register_elcom(void);
void proto_reg_handoff_elcom(void);

static int proto_elcom = -1;
static int hf_elcom_response = -1;
static int hf_elcom_request = -1;

static int hf_elcom_length = -1;
static int hf_elcom_type = -1;

static int hf_elcom_initiator = -1;
static int hf_elcom_initiator_endian = -1;
static int hf_elcom_initiator_ip = -1;
static int hf_elcom_initiator_port = -1;
static int hf_elcom_initiator_suff = -1;

static int hf_elcom_responder = -1;
static int hf_elcom_responder_endian = -1;
static int hf_elcom_responder_ip = -1;
static int hf_elcom_responder_port = -1;
static int hf_elcom_responder_suff = -1;

static int hf_elcom_userdata = -1;
static int hf_elcom_userdata_length = -1;
static int hf_elcom_userdata_pduid = -1;
static int hf_elcom_userdata_version = -1;
static int hf_elcom_userdata_result = -1;
static int hf_elcom_userdata_restmark = -1;
static int hf_elcom_userdata_cf = -1;

static int hf_elcom_datarequest = -1;
static int hf_elcom_datarequest_grouptype = -1;
static int hf_elcom_datarequest_result = -1;
static int hf_elcom_datarequest_groupnumber = -1;
static int hf_elcom_datarequest_grouppriority = -1;
static int hf_elcom_datarequest_groupsize = -1;
static int hf_elcom_datarequest_groupindex1 = -1;
static int hf_elcom_datarequest_groupindex2 = -1;
static int hf_elcom_datarequest_oid = -1;

static int hf_elcom_release_reason = -1;
static int hf_elcom_release_result = -1;

static int hf_elcom_strangeleftover = -1;

static gint ett_elcom = -1;
static gint ett_elcom_initiator = -1;
static gint ett_elcom_responder = -1;
static gint ett_elcom_userdata = -1;
static gint ett_elcom_datarequest = -1;

static gboolean elcom_show_hex = TRUE;

static const value_string endian_vals[] = {
        {0x0002, "Big"},
        {0x0200, "Little"},
        {0, NULL }
};

static const value_string suffix_vals[] = {
        {'A', "Control"},
        {'B', "Unsolicited"},
        {'C', "Periodic"},
        {'D', "Requested, scheduling"},
        {'E', "Requested, present/archived"},
        {'G', "Supervisory"},
        {'F', "Test"},
        {0, NULL }
};

static const value_string userdata_pduid_vals[] = {
        {0x04, "Connect Request"},
        {0x05, "Connect Response"},
        {0, NULL }
};

static const value_string userdata_version_vals[] = {
        {0x00, "Class 0, v0"},
        {0x01, "Class 1, v0"},
        {0x02, "Class 2, v0"},
        {0x12, "Class 2, v1"},
        {0x13, "Class 3, v1"},
        {0, NULL }
};

static const value_string userdata_result_vals[] = {
        {0x00, "OK"},
        {0, NULL }
};

static const value_string datarequest_grouptype_vals[] = {
        {TC_REQ, "Test Connection Request"},
        {TC_RSP, "Test Connection Response"},
        {0, NULL }
};

static const value_string datarequest_result_vals[] = {
        {0x00, "OK"},
        {0, NULL }
};

static const value_string type_vals[] = {
        {P_CONRQ, "Connect Request"},
        {P_CONRS, "Connect Response"},
        {P_RELRQ, "Release Request"},
        {P_RELRS, "Release Response"},
        {P_DATRQ, "Data Request"},
        {0, NULL }
};

static gint
dissect_lower_address(proto_item *ti_arg, gint ett_arg,
                      tvbuff_t *tvb, gint arg_offset,
                      int hf_endian, int hf_ip, int hf_port, int hf_suff)
{
        gint        offset = arg_offset;
        guint8      len1, len2;
        guint8     *suffix;
        proto_tree *tree;
        proto_item *ti;

        tree = proto_item_add_subtree(ti_arg, ett_arg);

        /*
         * Coding of address:
         * ELCOM-90 TRA3825.02 User Element conventions, p. 5-2 and Appendix G
         */
        len1 = tvb_get_guint8(tvb, offset);
        if (tvb_captured_length_remaining(tvb, offset+len1+1) <= 0)
                return offset;
        len2 = tvb_get_guint8(tvb, offset+len1+1);
        if (tvb_reported_length_remaining(tvb, offset+len1+len2+2) <= 0)
                return offset;
        if ((len1 != LOWADR_LEN) || (len2 != SUFFIX_LEN)) {
                proto_item_append_text(tree, " Invalid structure");
                return offset;
        }


        /* Show pre stuff */
        if (0x82 != tvb_get_guint8(tvb, offset+1)) {
                proto_item_append_text(tree, " Not IPV4 address");
                return offset;
        }
        offset += 2;


        /* endian */
        proto_tree_add_item(tree, hf_endian, tvb, offset, 2, ENC_BIG_ENDIAN);
        offset += 2;

        /* port */
        proto_tree_add_item(tree, hf_port, tvb, offset, 2, ENC_BIG_ENDIAN);
        offset += 2;

        /* ip-addr */
        proto_tree_add_item(tree, hf_ip, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;

        offset += 8;                /* skip the zero bytes */

        /* SUFFIX */
        suffix = tvb_get_string_enc(wmem_packet_scope(), tvb, offset+1, len2, ENC_ASCII);
        /* hf_suff FIELDTYPE must be FT_UINT_STRING */
        ti = proto_tree_add_item(tree, hf_suff, tvb, offset, 1, ENC_ASCII|ENC_BIG_ENDIAN);
        offset += len2+1;

        if (!(suffix[0] == 'A' || suffix[0] == 'B')) {
                proto_item_append_text(ti, " (invalid)");
                return offset;
        }

        proto_item_append_text(ti, " (%s)", val_to_str_const(suffix[1], suffix_vals, "<<-- WHAT?") );
        return offset;
}

static gint
dissect_userdata(proto_item *ti_arg, gint ett_arg, tvbuff_t *tvb, gint arg_offset)
{
        gint        offset = arg_offset;
        guint8      flen, lenbytes;
        guint8      year, month, day, hour, min, sec;
        guint16     msec;
        proto_tree *tree;
        proto_item *ti;

        tree = proto_item_add_subtree(ti_arg, ett_arg);

        /* length of User Data, should be 1 byte field ... */
        flen     = tvb_get_guint8(tvb, offset);
        lenbytes = 1;

        /* ... but sometimes it seems to be 2 bytes; try to be clever */
        if (flen == 0) {
                flen = tvb_get_guint8(tvb, offset+1);
                lenbytes = 2;
        }
        if (flen == 0 || flen > 79) /* invalid */
                return offset;

        ti = proto_tree_add_item(tree, hf_elcom_userdata_length, tvb, offset, lenbytes, ENC_BIG_ENDIAN);
        offset += lenbytes;
        if (lenbytes == 2) {
                proto_item_append_text(ti, " (2 bytes, should be 1 byte)");
        }

        if (tvb_reported_length_remaining(tvb, offset) <= 0)
                return offset;

        proto_tree_add_item(tree, hf_elcom_userdata_pduid, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset++;

        if (tvb_reported_length_remaining(tvb, offset) <= 0)
                return offset;

        proto_tree_add_item(tree, hf_elcom_userdata_version, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset++;


        if (tvb_reported_length_remaining(tvb, offset) <= 0)
                return offset;

        proto_tree_add_item(tree, hf_elcom_userdata_result, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset++;

        /* show the rest */
        /*        tree2 = proto_tree_add_subtree(tree, tvb, offset, -1, "User Data"); */

        if (tvb_reported_length_remaining(tvb, offset) <= 0)
                return offset;
        ti = proto_tree_add_item(tree, hf_elcom_userdata_restmark, tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_item_append_text(ti, " <-- '0' = no restart etc.");
        offset +=1;

        if (tvb_reported_length_remaining(tvb, offset+8) <= 0)
                return offset;
        year  = tvb_get_guint8(tvb, offset);
        month = tvb_get_guint8(tvb, offset+1);
        day   = tvb_get_guint8(tvb, offset+2);
        hour  = tvb_get_guint8(tvb, offset+3);
        min   = tvb_get_guint8(tvb, offset+4);
        sec   = tvb_get_guint8(tvb, offset+5);
        msec  = tvb_get_ntohs(tvb, offset+6);

        proto_tree_add_none_format(tree, hf_elcom_userdata_cf, tvb, offset, 8,
                                   "Control Field: %4d-%02d-%02d %02d:%02d:%02d.%d",
                                   year+1900, month, day, hour, min, sec, msec);

        offset += 12;
        if (tvb_reported_length_remaining(tvb, offset+12) > 0) {
                proto_item_append_text(ti, " Security info: ");
        }
        /* security info field, if present */
        while (tvb_reported_length_remaining(tvb, offset) > 0) {
                proto_item_append_text(ti, elcom_show_hex ? " %02x" : " %03o",
                                       tvb_get_guint8(tvb, offset));
                offset++;
        }

        return offset;
}

static gint
dissect_datarequest(proto_item *ti_arg, gint ett_arg, tvbuff_t *tvb, gint arg_offset)
{
        gint        offset = arg_offset;
        guint8      gtype, oidlen;
        proto_tree *tree;
        proto_item *ti;

        tree = proto_item_add_subtree(ti_arg, ett_arg);
        if (tvb_reported_length_remaining(tvb, offset) <= 0)
                return offset;

        gtype = tvb_get_guint8(tvb, offset);
        ti = proto_tree_add_item(tree, hf_elcom_datarequest_grouptype,
                                 tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;

        switch (gtype) {

        case TC_REQ:
                /* No more data for this type, suppress the error message */
                break;

        case TC_RSP:

                proto_tree_add_item(tree, hf_elcom_datarequest_result,
                                    tvb, offset, 1, ENC_BIG_ENDIAN);
                offset++;

                break;

        default:
                proto_item_append_text(ti, " <<--- meaning WHAT?");
                return offset;
        }
        if (tvb_reported_length_remaining(tvb, offset) <= 0)
                return offset;

        proto_tree_add_item(tree, hf_elcom_datarequest_groupnumber, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
        if (tvb_reported_length_remaining(tvb, offset) <= 0)
                return offset;

        proto_tree_add_item(tree, hf_elcom_datarequest_grouppriority, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
        if (tvb_reported_length_remaining(tvb, offset) <= 0)
                return offset;

        proto_tree_add_item(tree, hf_elcom_datarequest_groupsize, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
        if (tvb_reported_length_remaining(tvb, offset) <= 0)
                return offset;

        proto_tree_add_item(tree, hf_elcom_datarequest_groupindex1, tvb, offset, 2, ENC_BIG_ENDIAN);
        offset += 2;
        if (tvb_reported_length_remaining(tvb, offset) <= 0)
                return offset;

        proto_tree_add_item(tree, hf_elcom_datarequest_groupindex2, tvb, offset, 2, ENC_BIG_ENDIAN);
        offset += 2;
        if (tvb_reported_length_remaining(tvb, offset) <= 0)
                return offset;

        while (1) {
                oidlen = tvb_get_guint8(tvb, offset);
                if (oidlen == 0) /* normal termination */
                        break;
                if (tvb_reported_length_remaining(tvb, offset+oidlen+1) <= 0)
                        return offset;
                proto_tree_add_item(tree, hf_elcom_datarequest_oid, tvb, offset, 1, ENC_ASCII|ENC_BIG_ENDIAN);
                offset += oidlen+1;
        }
        offset += 1;             /* the loop exited at the 0 length byte */
        if (tvb_reported_length_remaining(tvb, offset) <= 0)
                return offset;

        /* show the rest */
        proto_tree_add_item(tree, hf_elcom_strangeleftover, tvb, offset, -1, ENC_NA);
        return offset;
}

/* XXX: Are all the tests against tvb_reported_length() really the right way to handle invalid fields ?
 *      It seems to me that invalid fields should just add an expert item
 *        or cause a "Malformed" exception.
 */
static int
dissect_elcom(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
        gboolean    is_request, length_ok;
        proto_tree *elcom_tree;
        proto_item *ti, *hidden_item;
        gint        offset = 0;
        guint       elcom_len;
        guint8      elcom_msg_type;
        guint8     *suffix;

        /* Check that there's enough data */
        if (tvb_captured_length(tvb) < 3)
                return 0;

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

        is_request = (pinfo->match_uint == pinfo->destport);
        elcom_len  = tvb_get_ntohs(tvb, 0);
        length_ok  = (tvb_reported_length(tvb) == (elcom_len+2));

        col_add_fstr(pinfo->cinfo, COL_INFO, "%s Len=%d%s",
                             is_request ? "Request" : "Response",
                             elcom_len,
                             length_ok ? "" : " (incorrect)");

        elcom_msg_type = tvb_get_guint8(tvb, 2);
        switch (elcom_msg_type) {
                case P_CONRQ:
                case P_CONRS:

                        /* starting after elcom_len and elcom_msg_type,
                           initiator + responder + userdata fields must be there */
                        if (tvb_captured_length_remaining(tvb, 3+TOTAL_LEN+TOTAL_LEN+3) < 0) return 2;
                        /* check also that those field lengths are valid */
                        if (tvb_get_guint8(tvb, 3)  != LOWADR_LEN) return 2;
                        if (tvb_get_guint8(tvb, 3+1+LOWADR_LEN) != SUFFIX_LEN) return 2;
                        if (tvb_get_guint8(tvb, 3+TOTAL_LEN) != LOWADR_LEN) return 2;
                        if (tvb_get_guint8(tvb, 3+1+TOTAL_LEN+LOWADR_LEN) != SUFFIX_LEN) return 2;

                        /* finally believe that there is valid suffix */
                        suffix = tvb_get_string_enc(wmem_packet_scope(), tvb, 3+2+LOWADR_LEN, 2, ENC_ASCII);
                        col_append_fstr(pinfo->cinfo, COL_INFO, " %s Connect", suffix);
                        break;

                case P_RELRQ:
                case P_RELRS:
                        col_append_str(pinfo->cinfo, COL_INFO, " Release");
                        break;

                case P_DATRQ:
                        col_append_str(pinfo->cinfo, COL_INFO, " Data");
                        break;
                }

                switch (elcom_msg_type) {
                case P_CONRQ:
                case P_RELRQ:
                        col_append_str(pinfo->cinfo, COL_INFO, " Request");
                        break;

                case P_CONRS:
                case P_RELRS:
                        col_append_str(pinfo->cinfo, COL_INFO, " Response");
                        break;
        }

        if (!tree)
                return tvb_captured_length(tvb);

        ti = proto_tree_add_item(tree, proto_elcom, tvb, offset, -1, ENC_NA);
        elcom_tree = proto_item_add_subtree(ti, ett_elcom);

        hidden_item = proto_tree_add_boolean(elcom_tree,
                                             is_request ? hf_elcom_request : hf_elcom_response,
                                             tvb, 0, 0, TRUE);
        PROTO_ITEM_SET_HIDDEN(hidden_item);

        /* 2 first bytes are the frame length */
        offset = 0;
        ti = proto_tree_add_item(elcom_tree, hf_elcom_length, tvb, offset, 2, ENC_BIG_ENDIAN);
        offset = +2;
        if (! length_ok) {
                proto_item_append_text(ti, " (incorrect)");
        }

        elcom_msg_type = tvb_get_guint8(tvb, offset);
        ti = proto_tree_add_item(elcom_tree, hf_elcom_type, tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_item_append_text(elcom_tree, " ( %s)", val_to_str(elcom_msg_type, type_vals, "Unknown %d"));

        offset++;
        if (tvb_reported_length_remaining(tvb, offset) <= 0)
                return offset;

        switch (elcom_msg_type) {
        case P_CONRQ:
        case P_CONRS:
                /*
                 * Connection request/release assiciated PDU's,
                 * /ELCOM-90 P Protocol spec/ p. 85...
                 */

                /* We need the length here, hardcode the LOWADR_LEN = 21 */
                ti = proto_tree_add_item(elcom_tree, hf_elcom_initiator, tvb, offset, TOTAL_LEN, ENC_NA);
                offset = dissect_lower_address(ti, ett_elcom_initiator, tvb, offset,
                                               hf_elcom_initiator_endian,
                                               hf_elcom_initiator_ip,
                                               hf_elcom_initiator_port,
                                               hf_elcom_initiator_suff);
                if (tvb_reported_length_remaining(tvb, offset) <= 0)
                        return offset;

                ti = proto_tree_add_item(elcom_tree, hf_elcom_responder, tvb, offset, TOTAL_LEN, ENC_NA);
                offset = dissect_lower_address(ti, ett_elcom_responder, tvb, offset,
                                               hf_elcom_responder_endian,
                                               hf_elcom_responder_ip,
                                               hf_elcom_responder_port,
                                               hf_elcom_responder_suff);
                if (tvb_reported_length_remaining(tvb, offset) <= 0)
                        return offset;

                /* Rest of the payload is USER-DATA, 0..82 bytes */
                ti = proto_tree_add_item(elcom_tree, hf_elcom_userdata, tvb, offset, -1, ENC_NA);
                offset = dissect_userdata(ti, ett_elcom_userdata, tvb, offset);

                break;

        case P_RELRQ:

                proto_tree_add_item(elcom_tree, hf_elcom_release_reason, tvb, offset, 1, ENC_BIG_ENDIAN);
                offset += 1;

                break;
        case P_RELRS:

                proto_tree_add_item(elcom_tree, hf_elcom_release_result, tvb, offset, 1, ENC_BIG_ENDIAN);

                offset += 1;

                break;

        case P_DATRQ:
                ti = proto_tree_add_item(elcom_tree, hf_elcom_datarequest, tvb, offset, -1, ENC_NA);
                offset = dissect_datarequest(ti, ett_elcom_datarequest, tvb, offset);
                break;

        default:
                proto_item_append_text(ti, " <<--- meaning WHAT??");
                break;
        }

        if (tvb_reported_length_remaining(tvb, offset) > 0)
        {
            /* We should not get here, but if we do, show what is left over: */
            proto_tree_add_item(elcom_tree, hf_elcom_strangeleftover, tvb, offset, -1, ENC_NA);
        }
        return tvb_captured_length(tvb);
}

void
proto_register_elcom(void)
{
        /* Setup list of header fields  See Section 1.6.1 for details*/
        static hf_register_info hf[] = {
                { &hf_elcom_response,
                  { "Response",        "elcom.response",
                    FT_BOOLEAN, BASE_NONE, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_request,
                  { "Request",        "elcom.request",
                    FT_BOOLEAN, BASE_NONE, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_length,
                  { "Length",        "elcom.length",
                    FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_type,
                  { "Type",        "elcom.type",
                    FT_UINT8, BASE_HEX, VALS(type_vals), 0, NULL, HFILL }
                },

                { &hf_elcom_initiator,
                  { "Initiator",        "elcom.initiator",
                    FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_initiator_endian,
                  { "Endian",        "elcom.initiator.endian",
                    FT_UINT16, BASE_HEX, VALS(endian_vals), 0, NULL, HFILL }
                },

                { &hf_elcom_initiator_ip,
                  { "IP",        "elcom.initiator.ip",
                    FT_IPv4, BASE_NONE, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_initiator_port,
                  { "Port",        "elcom.initiator.port",
                    FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_initiator_suff,
                  { "Suffix",        "elcom.initiator.suffix",
                    FT_UINT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_responder,
                  { "Responder",        "elcom.responder",
                    FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_responder_endian,
                  { "Endian",        "elcom.responder.endian",
                    FT_UINT16, BASE_HEX, VALS(endian_vals), 0, NULL, HFILL }
                },

                { &hf_elcom_responder_ip,
                  { "IP",        "elcom.responder.ip",
                    FT_IPv4, BASE_NONE, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_responder_port,
                  { "Port",        "elcom.responder.port",
                    FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_responder_suff,
                  { "Suffix",        "elcom.responder.suffix",
                    FT_UINT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_userdata,
                  { "User Data",        "elcom.userdata",
                    FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_userdata_length,
                  { "Length",        "elcom.userdata.length",
                    FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_userdata_pduid,
                  { "PDU-ID",        "elcom.userdata.pduid",
                    FT_UINT8, BASE_DEC, VALS(userdata_pduid_vals), 0, NULL, HFILL }
                },

                { &hf_elcom_userdata_version,
                  { "Version",        "elcom.userdata.version",
                    FT_UINT8, BASE_DEC, VALS(userdata_version_vals), 0, NULL, HFILL }
                },

                { &hf_elcom_userdata_result,
                  { "Result",        "elcom.userdata.result",
                    FT_UINT8, BASE_DEC, VALS(userdata_result_vals), 0, NULL, HFILL }
                },

                { &hf_elcom_userdata_restmark,
                  { "Restart marking",        "elcom.userdata.response.restartcode",
                    FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_userdata_cf,
                  { "Control Field",        "elcom.userdata.response.controlfield",
                    FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_release_reason,
                  { "Reason",        "elcom.release.reason",
                    FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_release_result,
                  { "Result",        "elcom.release.result",
                    FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_datarequest,
                  { "Data Request",        "elcom.datarequest",
                    FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_datarequest_grouptype,
                  { "Group Type",        "elcom.datarequest.grouptype",
                    FT_UINT8, BASE_DEC, VALS(datarequest_grouptype_vals), 0, NULL, HFILL }
                },

                { &hf_elcom_datarequest_result,
                  { "Result",        "elcom.datarequest.result",
                    FT_UINT8, BASE_DEC, VALS(datarequest_result_vals), 0, NULL, HFILL }
                },

                { &hf_elcom_datarequest_groupnumber,
                  { "Group Number",        "elcom.datarequest.groupnumber",
                    FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_datarequest_grouppriority,
                  { "Group Priority",        "elcom.datarequest.grouppriority",
                    FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_datarequest_groupsize,
                  { "Group Size",        "elcom.datarequest.groupsize",
                    FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_datarequest_groupindex1,
                  { "Group Index1",        "elcom.datarequest.groupindex1",
                    FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_datarequest_groupindex2,
                  { "Group Index2",        "elcom.datarequest.groupindex2",
                    FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
                },

                { &hf_elcom_datarequest_oid,
                  { "Obkect Name",        "elcom.datarequest.oid",
                    FT_UINT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }
                },
                { &hf_elcom_strangeleftover,
                  { "Strange Leftover",        "elcom.leftover",
                    FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }
                }
        };

        /* Setup protocol subtree array */
        static gint *ett[] = {
                &ett_elcom,
                &ett_elcom_initiator,
                &ett_elcom_responder,
                &ett_elcom_userdata,
                &ett_elcom_datarequest
        };

        /* Register the protocol name and description */
        proto_elcom = proto_register_protocol (
                                               "ELCOM Communication Protocol",
                                               "ELCOM",
                                               "elcom"
                                               );

        /* Required function calls to register the header fields and subtrees used */
        proto_register_field_array(proto_elcom, hf, array_length(hf));
        proto_register_subtree_array(ett, array_length(ett));

}

void
proto_reg_handoff_elcom(void)
{
        dissector_handle_t elcom_handle;

        elcom_handle = create_dissector_handle(dissect_elcom, proto_elcom);
        dissector_add_uint("tcp.port", TCP_PORT_ELCOM, elcom_handle);
}

/*
 * Editor modelines
 *
 * Local Variables:
 * c-basic-offset: 8
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * ex: set shiftwidth=8 tabstop=8 expandtab:
 * :indentSize=8:tabSize=8:noTabs=true:
 */
