/* packet-zep.c
 * Dissector  routines for the ZigBee Encapsulation Protocol
 * By Owen Kirby <osk@exegin.com>
 * Copyright 2009 Exegin Technologies Limited
 *
 * 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.
 *------------------------------------------------------------
 *
 *      ZEP Packets must be received in the following format:
 *      |UDP Header|  ZEP Header |IEEE 802.15.4 Packet|
 *      | 8 bytes  | 16/32 bytes |    <= 127 bytes    |
 *------------------------------------------------------------
 *
 *      ZEP v1 Header will have the following format:
 *      |Preamble|Version|Channel ID|Device ID|CRC/LQI Mode|LQI Val|Reserved|Length|
 *      |2 bytes |1 byte |  1 byte  | 2 bytes |   1 byte   |1 byte |7 bytes |1 byte|
 *
 *      ZEP v2 Header will have the following format (if type=1/Data):
 *      |Preamble|Version| Type |Channel ID|Device ID|CRC/LQI Mode|LQI Val|NTP Timestamp|Sequence#|Reserved|Length|
 *      |2 bytes |1 byte |1 byte|  1 byte  | 2 bytes |   1 byte   |1 byte |   8 bytes   | 4 bytes |10 bytes|1 byte|
 *
 *      ZEP v2 Header will have the following format (if type=2/Ack):
 *      |Preamble|Version| Type |Sequence#|
 *      |2 bytes |1 byte |1 byte| 4 bytes |
 *------------------------------------------------------------
 */

#include "config.h"


#include <epan/packet.h>
#include "packet-ntp.h"

#include "packet-zep.h"

/*  Function declarations */
void proto_reg_handoff_zep(void);
void proto_register_zep(void);

/*  Initialize protocol and registered fields. */
static int proto_zep = -1;
static int hf_zep_version = -1;
static int hf_zep_type = -1;
static int hf_zep_channel_id = -1;
static int hf_zep_device_id = -1;
static int hf_zep_lqi_mode = -1;
static int hf_zep_lqi = -1;
static int hf_zep_timestamp = -1;
static int hf_zep_seqno = -1;
static int hf_zep_ieee_length = -1;
static int hf_zep_protocol_id = -1;
static int hf_zep_reserved_field = -1;

/* Initialize protocol subtrees. */
static gint ett_zep = -1;

/*  Dissector handle */
static dissector_handle_t zep_handle;

/*  Subdissector handles */
static dissector_handle_t data_handle;
static dissector_handle_t ieee802154_handle;
static dissector_handle_t ieee802154_ccfcs_handle;

/*FUNCTION:------------------------------------------------------
 *  NAME
 *      dissect_zep
 *  DESCRIPTION
 *      IEEE 802.15.4 packet dissection routine for Wireshark.
 *  PARAMETERS
 *      tvbuff_t *tvb       - pointer to buffer containing raw packet.
 *      packet_info *pinfo  - pointer to packet information fields
 *      proto_tree *tree    - pointer to data tree Wireshark uses to display packet.
 *  RETURNS
 *      void
 *---------------------------------------------------------------
 */
static int dissect_zep(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
    tvbuff_t            *next_tvb;
    proto_item          *proto_root, *pi;
    proto_tree          *zep_tree;
    guint8              ieee_packet_len;
    guint8              zep_header_len;
    zep_info            zep_data;

    dissector_handle_t  next_dissector;

    /*  Determine whether this is a Q51/IEEE 802.15.4 sniffer packet or not */
    if(strcmp(tvb_get_string_enc(wmem_packet_scope(), tvb, 0, 2, ENC_ASCII), ZEP_PREAMBLE)){
        /*  This is not a Q51/ZigBee sniffer packet */
        return 0;
    }

    memset(&zep_data, 0, sizeof(zep_data)); /* Zero all zep_data fields. */

    /*  Extract the protocol version from the ZEP header. */
    zep_data.version = tvb_get_guint8(tvb, 2);
    if (zep_data.version == 1) {
        /* Type indicates a ZEP_v1 packet. */

        zep_header_len = ZEP_V1_HEADER_LEN;
        zep_data.type = 0;
        zep_data.channel_id = tvb_get_guint8(tvb, 3);
        zep_data.device_id = tvb_get_ntohs(tvb, 4);
        zep_data.lqi_mode = tvb_get_guint8(tvb, 6)?1:0;
        zep_data.lqi = tvb_get_guint8(tvb, 7);
        ieee_packet_len = (tvb_get_guint8(tvb, ZEP_V1_HEADER_LEN - 1) & ZEP_LENGTH_MASK);
    }
    else {
        /* At the time of writing, v2 is the latest version of ZEP, assuming
         * anything higher than v2 has identical format. */

        zep_data.type = tvb_get_guint8(tvb, 3);
        if (zep_data.type == ZEP_V2_TYPE_ACK) {
            /* ZEP Ack has only the seqno. */
            zep_header_len = ZEP_V2_ACK_LEN;
            zep_data.seqno = tvb_get_ntohl(tvb, 4);
            ieee_packet_len = 0;
        }
        else {
            /* Although, only type 1 corresponds to data, if another value is present, assume it is dissected the same. */
            zep_header_len = ZEP_V2_HEADER_LEN;
            zep_data.channel_id = tvb_get_guint8(tvb, 4);
            zep_data.device_id = tvb_get_ntohs(tvb, 5);
            zep_data.lqi_mode = tvb_get_guint8(tvb, 7)?1:0;
            zep_data.lqi = tvb_get_guint8(tvb, 8);
            ntp_to_nstime(tvb, 9, &(zep_data.ntp_time));
            zep_data.seqno = tvb_get_ntohl(tvb, 17);
            ieee_packet_len = (tvb_get_guint8(tvb, ZEP_V2_HEADER_LEN - 1) & ZEP_LENGTH_MASK);
        }
    }

#if 0
/*??dat*/
    if (zep_data.ntp_time.secs && zep_data.ntp_time.nsecs) {
        pinfo->abs_ts = zep_data.ntp_time;
    }
#endif

    if(ieee_packet_len < tvb_reported_length(tvb)-zep_header_len){
        /* Packet's length is mis-reported, abort dissection */
        return 0;
    }

    /*  Enter name info protocol field */
    col_set_str(pinfo->cinfo, COL_PROTOCOL, (zep_data.version==1)?"ZEP":"ZEPv2");

    /*  Enter name info protocol field */
    if (!((zep_data.version>=2) && (zep_data.type==ZEP_V2_TYPE_ACK)))
        col_add_fstr(pinfo->cinfo, COL_INFO, "Encapsulated ZigBee Packet [Channel]=%i [Length]=%i", zep_data.channel_id, ieee_packet_len);
    else
        col_add_fstr(pinfo->cinfo, COL_INFO, "Ack, Sequence Number: %i", zep_data.seqno);

    if(tree){
        /*  Create subtree for the ZEP Header */
        if (!((zep_data.version>=2) && (zep_data.type==ZEP_V2_TYPE_ACK))) {
            proto_root = proto_tree_add_protocol_format(tree, proto_zep, tvb, 0, zep_header_len, "ZigBee Encapsulation Protocol, Channel: %i, Length: %i", zep_data.channel_id, ieee_packet_len);
        }
        else {
            proto_root = proto_tree_add_protocol_format(tree, proto_zep, tvb, 0, zep_header_len, "ZigBee Encapsulation Protocol, Ack");
        }
        zep_tree = proto_item_add_subtree(proto_root, ett_zep);

        /*  Display the information in the subtree */
        proto_tree_add_item(zep_tree, hf_zep_protocol_id, tvb, 0, 2, ENC_NA|ENC_ASCII);
        if (zep_data.version==1) {
            proto_tree_add_uint(zep_tree, hf_zep_version, tvb, 2, 1, zep_data.version);
            proto_tree_add_uint(zep_tree, hf_zep_channel_id, tvb, 3, 1, zep_data.channel_id);
            proto_tree_add_uint(zep_tree, hf_zep_device_id, tvb, 4, 2, zep_data.device_id);
            proto_tree_add_boolean_format(zep_tree, hf_zep_lqi_mode, tvb, 6, 1, zep_data.lqi_mode, "LQI/CRC Mode: %s", zep_data.lqi_mode?"CRC":"LQI");
            if(!(zep_data.lqi_mode)){
                proto_tree_add_uint(zep_tree, hf_zep_lqi, tvb, 7, 1, zep_data.lqi);
            }
            proto_tree_add_item(zep_tree, hf_zep_reserved_field, tvb, 7+((zep_data.lqi_mode)?0:1), 7+((zep_data.lqi_mode)?1:0), ENC_NA);
        }
        else {
            proto_tree_add_uint(zep_tree, hf_zep_version, tvb, 2, 1, zep_data.version);
            if (zep_data.type == ZEP_V2_TYPE_ACK) {
                proto_tree_add_uint_format_value(zep_tree, hf_zep_type, tvb, 3, 1, zep_data.type, "%i (Ack)", ZEP_V2_TYPE_ACK);
                proto_tree_add_uint(zep_tree, hf_zep_seqno, tvb, 4, 4, zep_data.seqno);
            }
            else {
                proto_tree_add_uint_format_value(zep_tree, hf_zep_type, tvb, 3, 1, zep_data.type, "%i (%s)", zep_data.type, (zep_data.type==ZEP_V2_TYPE_DATA)?"Data":"Reserved");
                proto_tree_add_uint(zep_tree, hf_zep_channel_id, tvb, 4, 1, zep_data.channel_id);
                proto_tree_add_uint(zep_tree, hf_zep_device_id, tvb, 5, 2, zep_data.device_id);
                proto_tree_add_boolean_format(zep_tree, hf_zep_lqi_mode, tvb, 7, 1, zep_data.lqi_mode, "LQI/CRC Mode: %s", zep_data.lqi_mode?"CRC":"LQI");
                if(!(zep_data.lqi_mode)){
                    proto_tree_add_uint(zep_tree, hf_zep_lqi, tvb, 8, 1, zep_data.lqi);
                }
                pi = proto_tree_add_time(zep_tree, hf_zep_timestamp, tvb, 9, 8, &(zep_data.ntp_time));
                proto_item_append_text(pi, " (%ld.%09ds)", (long)zep_data.ntp_time.secs, zep_data.ntp_time.nsecs);
                proto_tree_add_uint(zep_tree, hf_zep_seqno, tvb, 17, 4, zep_data.seqno);
            }
        }
        if (!((zep_data.version==2) && (zep_data.type==ZEP_V2_TYPE_ACK)))
            proto_tree_add_uint_format_value(zep_tree, hf_zep_ieee_length, tvb, zep_header_len - 1, 1, ieee_packet_len, "%i %s", ieee_packet_len, (ieee_packet_len==1)?"Byte":"Bytes");
    }

    /* Determine which dissector to call next. */
    if (zep_data.lqi_mode) {
        /* CRC present, use standard IEEE dissector. */
        next_dissector = ieee802154_handle;
    }
    else {
        /* ChipCon compliant FCS present. */
        next_dissector = ieee802154_ccfcs_handle;
    }
    if (!next_dissector) {
        /* IEEE 802.15.4 dissectors couldn't be found. */
        next_dissector = data_handle;
    }

    /*  Call the IEEE 802.15.4 dissector */
    if (!((zep_data.version>=2) && (zep_data.type==ZEP_V2_TYPE_ACK))) {
        next_tvb = tvb_new_subset_length(tvb, zep_header_len, ieee_packet_len);
        call_dissector(next_dissector, next_tvb, pinfo, tree);
    }
    return tvb_captured_length(tvb);
} /* dissect_ieee802_15_4 */

/*FUNCTION:------------------------------------------------------
 *  NAME
 *      proto_register_zep
 *  DESCRIPTION
 *      IEEE 802.15.4 protocol registration routine.
 *  PARAMETERS
 *      none
 *  RETURNS
 *      void
 *---------------------------------------------------------------
 */
void proto_register_zep(void)
{
    static hf_register_info hf[] = {
        { &hf_zep_version,
        { "Protocol Version",           "zep.version", FT_UINT8, BASE_DEC, NULL, 0x0,
            "The version of the sniffer.", HFILL }},

        { &hf_zep_type,
        { "Type",                       "zep.type", FT_UINT8, BASE_DEC, NULL, 0x0,
            NULL, HFILL }},

        { &hf_zep_channel_id,
        { "Channel ID",                 "zep.channel_id", FT_UINT8, BASE_DEC, NULL, 0x0,
            "The logical channel on which this packet was detected.", HFILL }},

        { &hf_zep_device_id,
        { "Device ID",                  "zep.device_id", FT_UINT16, BASE_DEC, NULL, 0x0,
            "The ID of the device that detected this packet.", HFILL }},

        { &hf_zep_lqi_mode,
        { "LQI/CRC Mode",               "zep.lqi_mode", FT_BOOLEAN, BASE_NONE, NULL, 0x0,
            "Determines what format the last two bytes of the MAC frame use.", HFILL }},

        { &hf_zep_lqi,
        { "Link Quality Indication",    "zep.lqi", FT_UINT8, BASE_DEC, NULL, 0x0,
            NULL, HFILL }},

        { &hf_zep_timestamp,
        { "Timestamp",                  "zep.time", FT_ABSOLUTE_TIME, ABSOLUTE_TIME_LOCAL, NULL, 0x0,
            NULL, HFILL }},

        { &hf_zep_seqno,
        { "Sequence Number",            "zep.seqno", FT_UINT8, BASE_DEC, NULL, 0x0,
            NULL, HFILL }},

        { &hf_zep_ieee_length,
        { "Length",              "zep.length", FT_UINT8, BASE_DEC, NULL, 0x0,
            "The length (in bytes) of the encapsulated IEEE 802.15.4 MAC frame.", HFILL }},

        { &hf_zep_protocol_id,
        { "Protocol ID String",            "zep.protocol_id", FT_STRING, BASE_NONE, NULL, 0x0,
            NULL, HFILL }},

        { &hf_zep_reserved_field,
        { "Reserved Fields",            "zep.reserved_field", FT_BYTES, BASE_NONE, NULL, 0x0,
            NULL, HFILL }},
    };

    static gint *ett[] = {
        &ett_zep
    };

    /*  Register protocol name and description. */
    proto_zep = proto_register_protocol("ZigBee Encapsulation Protocol", "ZEP", "zep");

    /*  Register header fields and subtrees. */
    proto_register_field_array(proto_zep, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));

    /*  Register dissector with Wireshark. */
    zep_handle = register_dissector("zep", dissect_zep, proto_zep);
} /* proto_register_zep */

/*FUNCTION:------------------------------------------------------
 *  NAME
 *      proto_reg_handoff_zep
 *  DESCRIPTION
 *      Registers the zigbee dissector with Wireshark.
 *      Will be called every time 'apply' is pressed in the preferences menu.
 *  PARAMETERS
 *      none
 *  RETURNS
 *      void
 *---------------------------------------------------------------
 */
void proto_reg_handoff_zep(void)
{
    dissector_handle_t h;

    /* Get dissector handles. */
    if ( !(h = find_dissector("wpan")) ) { /* Try use built-in 802.15.4 dissector */
        h = find_dissector("ieee802154");  /* otherwise use older 802.15.4 plugin dissector */
    }
    ieee802154_handle = h;
    if ( !(h = find_dissector("wpan_cc24xx")) ) { /* Try use built-in 802.15.4 (Chipcon) dissector */
        h = find_dissector("ieee802154_ccfcs");   /* otherwise use older 802.15.4 (Chipcon) plugin dissector */
    }
    ieee802154_ccfcs_handle = h;

    data_handle = find_dissector("data");
    dissector_add_uint("udp.port", ZEP_DEFAULT_PORT, zep_handle);
} /* proto_reg_handoff_zep */

/*
 * 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:
 */
