/* packet-ircomm.c
 * Routines for IrCOMM dissection
 * By Jan Kiszka <jan.kiszka@web.de>
 * Copyright 2003 Jan Kiszka
 *
 * $Id: packet-ircomm.c 13596 2005-03-05 06:28:10Z lroland $
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@unicom.net>
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "moduleinfo.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <glib.h>
#include <gmodule.h>
#include <epan/packet.h>
#include <epan/proto.h>

#ifdef NEED_SNPRINTF_H
# include "snprintf.h"
#endif

/*
 * See
 *
 *	http://www.irda.org/standards/specifications.asp
 *
 * for various IrDA specifications.
 */

#include "irda-appl.h"


/* Parameters common to all service types */
#define IRCOMM_SERVICE_TYPE     0x00
#define IRCOMM_PORT_TYPE        0x01 /* Only used in LM-IAS */
#define IRCOMM_PORT_NAME        0x02 /* Only used in LM-IAS */

/* Parameters for both 3 wire and 9 wire */
#define IRCOMM_DATA_RATE        0x10
#define IRCOMM_DATA_FORMAT      0x11
#define IRCOMM_FLOW_CONTROL     0x12
#define IRCOMM_XON_XOFF         0x13
#define IRCOMM_ENQ_ACK          0x14
#define IRCOMM_LINE_STATUS      0x15
#define IRCOMM_BREAK            0x16

/* Parameters for 9 wire */
#define IRCOMM_DTE              0x20
#define IRCOMM_DCE              0x21
#define IRCOMM_POLL             0x22

/* Service type (details) */
#define IRCOMM_3_WIRE_RAW       0x01
#define IRCOMM_3_WIRE           0x02
#define IRCOMM_9_WIRE           0x04
#define IRCOMM_CENTRONICS       0x08

/* Port type (details) */
#define IRCOMM_SERIAL           0x01
#define IRCOMM_PARALLEL         0x02

/* Data format (details) */
#define IRCOMM_WSIZE_5          0x00
#define IRCOMM_WSIZE_6          0x01
#define IRCOMM_WSIZE_7          0x02
#define IRCOMM_WSIZE_8          0x03

#define IRCOMM_1_STOP_BIT       0x00
#define IRCOMM_2_STOP_BIT       0x04 /* 1.5 if char len 5 */

#define IRCOMM_PARITY_DISABLE   0x00
#define IRCOMM_PARITY_ENABLE    0x08

#define IRCOMM_PARITY_ODD       0x00
#define IRCOMM_PARITY_EVEN      0x10
#define IRCOMM_PARITY_MARK      0x20
#define IRCOMM_PARITY_SPACE     0x30

/* Flow control */
#define IRCOMM_XON_XOFF_IN      0x01
#define IRCOMM_XON_XOFF_OUT     0x02
#define IRCOMM_RTS_CTS_IN       0x04
#define IRCOMM_RTS_CTS_OUT      0x08
#define IRCOMM_DSR_DTR_IN       0x10
#define IRCOMM_DSR_DTR_OUT      0x20
#define IRCOMM_ENQ_ACK_IN       0x40
#define IRCOMM_ENQ_ACK_OUT      0x80

/* Line status */
#define IRCOMM_OVERRUN_ERROR    0x02
#define IRCOMM_PARITY_ERROR     0x04
#define IRCOMM_FRAMING_ERROR    0x08

/* DTE (Data terminal equipment) line settings */
#define IRCOMM_DELTA_DTR        0x01
#define IRCOMM_DELTA_RTS        0x02
#define IRCOMM_DTR              0x04
#define IRCOMM_RTS              0x08

/* DCE (Data communications equipment) line settings */
#define IRCOMM_DELTA_CTS        0x01  /* Clear to send has changed */
#define IRCOMM_DELTA_DSR        0x02  /* Data set ready has changed */
#define IRCOMM_DELTA_RI         0x04  /* Ring indicator has changed */
#define IRCOMM_DELTA_CD         0x08  /* Carrier detect has changed */
#define IRCOMM_CTS              0x10  /* Clear to send is high */
#define IRCOMM_DSR              0x20  /* Data set ready is high */
#define IRCOMM_RI               0x40  /* Ring indicator is high */
#define IRCOMM_CD               0x80  /* Carrier detect is high */
#define IRCOMM_DCE_DELTA_ANY    0x0f

/* Initialize the subtree pointers */
static gint ett_ircomm = -1;
static gint ett_ircomm_ctrl = -1;

#define MAX_PARAMETERS          32
static gint ett_param[MAX_IAP_ENTRIES * MAX_PARAMETERS];


static int proto_ircomm = -1;
static int hf_ircomm_param = -1;
static int hf_param_pi = -1;
static int hf_param_pl = -1;
static int hf_param_pv = -1;
static int hf_control = -1;
static int hf_control_len = -1;

static gboolean dissect_ircomm_parameters(tvbuff_t* tvb, unsigned offset, packet_info* pinfo,
                                          proto_tree* tree, unsigned list_index, guint8 attr_type);
static gboolean dissect_ircomm_ttp_lsap(tvbuff_t* tvb, unsigned offset, packet_info* pinfo,
                                        proto_tree* tree, unsigned list_index, guint8 attr_type);
static gboolean dissect_ircomm_lmp_lsap(tvbuff_t* tvb, unsigned offset, packet_info* pinfo,
                                        proto_tree* tree, unsigned list_index, guint8 attr_type);

ias_attr_dissector_t ircomm_attr_dissector[] = {
/* IrDA:IrCOMM attribute dissectors */
    { "Parameters",             dissect_ircomm_parameters },
    { "IrDA:TinyTP:LsapSel",    dissect_ircomm_ttp_lsap },
    { NULL,                     NULL }
};

ias_attr_dissector_t irlpt_attr_dissector[] = {
/* IrLPT attribute dissectors */
    { "IrDA:IrLMP:LsapSel", dissect_ircomm_lmp_lsap },
    { "IrDA:IrLMP:LSAPSel", dissect_ircomm_lmp_lsap },  /* according to IrCOMM V1.0, p25 */
    { NULL, NULL }
};


/*
 * Dissect the cooked IrCOMM protocol
 */
static void dissect_cooked_ircomm(tvbuff_t* tvb, packet_info* pinfo, proto_tree* root)
{
    unsigned offset = 0;
    unsigned clen;


    if (tvb_length(tvb) == 0)
        return;

    /* Make entries in Protocol column on summary display */
    if (check_col(pinfo->cinfo, COL_PROTOCOL))
        col_set_str(pinfo->cinfo, COL_PROTOCOL, "IrCOMM");

    clen = tvb_get_guint8(tvb, offset);

    if (check_col(pinfo->cinfo, COL_INFO))
    {
        char        buf[128];
        unsigned    len = tvb_length(tvb) - 1 - clen;


        if (len > 0)
            sprintf(buf, "Clen=%d, UserData: %d byte%s", clen, len, (len > 1)? "s": "");
        else
            sprintf(buf, "Clen=%d", clen);
        col_add_str(pinfo->cinfo, COL_INFO, buf);
    }

    if (root)
    {
        /* create display subtree for the protocol */
        proto_item* ti   = proto_tree_add_item(root, proto_ircomm, tvb, 0, -1, FALSE);
        proto_tree* tree = proto_item_add_subtree(ti, ett_ircomm);

        proto_tree* ctrl_tree;


        ti        = proto_tree_add_item(tree, hf_control, tvb, 0, clen + 1, FALSE);
        ctrl_tree = proto_item_add_subtree(ti, ett_ircomm_ctrl);
        proto_tree_add_item(ctrl_tree, hf_control_len, tvb, offset, 1, FALSE);
        offset++;
        {
            tvbuff_t *cvalue = tvb_new_subset(tvb, offset, clen, clen);
            call_dissector(data_handle, cvalue, pinfo, ctrl_tree);
            offset += clen;
        }

        tvb = tvb_new_subset(tvb, offset, -1, -1);
        call_dissector(data_handle, tvb, pinfo, tree);
    }
}


/*
 * Dissect the raw IrCOMM/IrLPT protocol
 */
static void dissect_raw_ircomm(tvbuff_t* tvb, packet_info* pinfo, proto_tree* root)
{
    unsigned len = tvb_length(tvb);


    if (len == 0)
        return;

    /* Make entries in Protocol column on summary display */
    if (check_col(pinfo->cinfo, COL_PROTOCOL))
        col_set_str(pinfo->cinfo, COL_PROTOCOL, "IrCOMM");

    if (check_col(pinfo->cinfo, COL_INFO))
    {
        char    buf[128];


        sprintf(buf, "User Data: %d byte%s", len, (len > 1)? "s": "");
        col_add_str(pinfo->cinfo, COL_INFO, buf);
    }

    if (root)
    {
        /* create display subtree for the protocol */
        proto_item* ti   = proto_tree_add_item(root, proto_ircomm, tvb, 0, -1, FALSE);
        proto_tree* tree = proto_item_add_subtree(ti, ett_ircomm);

        call_dissector(data_handle, tvb, pinfo, tree);
    }
}


/*
 * Dissect IrCOMM IAS "Parameters" attribute
 */
static gboolean dissect_ircomm_parameters(tvbuff_t* tvb, unsigned offset, packet_info* pinfo _U_,
                                          proto_tree* tree, unsigned list_index, guint8 attr_type)
{
    unsigned    len;
    unsigned    n = 0;
    proto_item* ti;
    proto_tree* p_tree;
    char        buf[256];
    guint8      pv;


    if (!check_iap_octet_result(tvb, tree, offset, "Parameters", attr_type))
        return TRUE;

    if (tree)
    {
        len = tvb_get_ntohs(tvb, offset) + offset + 2;
        offset += 2;

        while (offset < len)
        {
            guint8  p_len = tvb_get_guint8(tvb, offset + 1);


            ti = proto_tree_add_item(tree, hf_ircomm_param, tvb, offset, p_len + 2, FALSE);
            p_tree = proto_item_add_subtree(ti, ett_param[list_index * MAX_PARAMETERS + n]);

            buf[0] = 0;

            switch (tvb_get_guint8(tvb, offset))
            {
                case IRCOMM_SERVICE_TYPE:
                    proto_item_append_text(ti, ": Service Type (");

                    pv = tvb_get_guint8(tvb, offset+2);
                    if (pv & IRCOMM_3_WIRE_RAW)
                        strcat(buf, ", 3-Wire raw");
                    if (pv & IRCOMM_3_WIRE)
                        strcat(buf, ", 3-Wire");
                    if (pv & IRCOMM_9_WIRE)
                        strcat(buf, ", 9-Wire");
                    if (pv & IRCOMM_CENTRONICS)
                        strcat(buf, ", Centronics");

                    strcat(buf, ")");

                    proto_item_append_text(ti, buf+2);

                    break;

                case IRCOMM_PORT_TYPE:
                    proto_item_append_text(ti, ": Port Type (");

                    pv = tvb_get_guint8(tvb, offset+2);
                    if (pv & IRCOMM_SERIAL)
                        strcat(buf, ", serial");
                    if (pv & IRCOMM_PARALLEL)
                        strcat(buf, ", parallel");

                    strcat(buf, ")");

                    proto_item_append_text(ti, buf+2);

                    break;

                case IRCOMM_PORT_NAME:
                    /* XXX - the IrCOMM V1.0 spec says this "Normally
                       human readable text, but not required". */
                    proto_item_append_text(ti, ": Port Name (\"%s\")",
                        tvb_format_text(tvb, offset+2, p_len));

                    break;

                default:
                    proto_item_append_text(ti, ": unknown");
            }

            offset = dissect_param_tuple(tvb, p_tree, offset);
            n++;
        }

    }

    return TRUE;
}


/*
 * Dissect IrCOMM IAS "IrDA:TinyTP:LsapSel" attribute
 */
static gboolean dissect_ircomm_ttp_lsap(tvbuff_t* tvb, unsigned offset, packet_info* pinfo,
                                        proto_tree* tree, unsigned list_index _U_, guint8 attr_type)
{
    guint8 dlsap;


    if ((dlsap = check_iap_lsap_result(tvb, tree, offset, "IrDA:TinyTP:LsapSel", attr_type)) == 0)
        return FALSE;

    add_lmp_conversation(pinfo, dlsap, TRUE, dissect_cooked_ircomm);

    return FALSE;
}


/*
 * Dissect IrCOMM/IrLPT IAS "IrDA:IrLMP:LsapSel" attribute
 */
static gboolean dissect_ircomm_lmp_lsap(tvbuff_t* tvb, unsigned offset, packet_info* pinfo,
                                        proto_tree* tree, unsigned list_index _U_, guint8 attr_type)
{
    guint8 dlsap;


    if ((dlsap = check_iap_lsap_result(tvb, tree, offset, "IrDA:IrLMP:LsapSel", attr_type)) == 0)
        return FALSE;

    add_lmp_conversation(pinfo, dlsap, FALSE, dissect_raw_ircomm);

    return FALSE;
}


/*
 * Register the IrCOMM protocol
 */
void proto_register_ircomm(void)
{
    unsigned i;

    /* Setup list of header fields */
    static hf_register_info hf_ircomm[] = {
        { &hf_ircomm_param,
            { "IrCOMM Parameter", "ircomm.parameter",
                FT_NONE, BASE_NONE, NULL, 0,
                "", HFILL }},
        { &hf_param_pi,
            { "Parameter Identifier", "ircomm.pi",
                FT_UINT8, BASE_HEX, NULL, 0,
                "", HFILL }},
        { &hf_param_pl,
            { "Parameter Length", "ircomm.pl",
                FT_UINT8, BASE_HEX, NULL, 0,
                "", HFILL }},
        { &hf_param_pv,
            { "Parameter Value", "ircomm.pv",
                FT_BYTES, BASE_HEX, NULL, 0,
                "", HFILL }},
        { &hf_control,
            { "Control Channel", "ircomm.control",
                FT_NONE, BASE_NONE, NULL, 0,
                "", HFILL }},
        { &hf_control_len,
            { "Clen", "ircomm.control.len",
                FT_UINT8, BASE_DEC, NULL, 0,
                "", HFILL }},
    };

    /* Setup protocol subtree arrays */
    static gint* ett[] = {
        &ett_ircomm,
        &ett_ircomm_ctrl
    };

    static gint* ett_p[MAX_IAP_ENTRIES * MAX_PARAMETERS];


    /* Register protocol names and descriptions */
    proto_ircomm = proto_register_protocol("IrCOMM Protocol", "IrCOMM", "ircomm");

    /* Required function calls to register the header fields */
    proto_register_field_array(proto_ircomm, hf_ircomm, array_length(hf_ircomm));

    /* Register subtrees */
    proto_register_subtree_array(ett, array_length(ett));
    for (i = 0; i < MAX_IAP_ENTRIES * MAX_PARAMETERS; i++)
    {
        ett_param[i] = -1;
        ett_p[i]     = &ett_param[i];
    }
    proto_register_subtree_array(ett_p, MAX_IAP_ENTRIES * MAX_PARAMETERS);
}
