/* packet-iso14443.c
 * Routines for ISO14443 dissection
 * Copyright 2015-2016, Martin Kaiser <martin@kaiser.cx>
 *
 * 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.
 */

/* ISO14443 is a set of standards describing the communication between a
 * card reader and a contactless smartcard.
 *
 * This dissector handles the initialization messages defined in
 * ISO14443-3 and the activation and protocol messages from ISO14443-4
 *
 * The standards are available as "final committee drafts" from
 * http://wg8.de/wg8n1496_17n3613_Ballot_FCD14443-3.pdf
 * http://wg8.de/wg8n1344_17n3269_Ballot_FCD14443-4.pdf
 *
 * The pcap input format for this dissector is documented at
 * http://www.kaiser.cx/pcap-iso14443.html.
 */


#include "config.h"
#include <math.h>
#include <epan/packet.h>
#include <epan/expert.h>
#include <epan/circuit.h>
#include <epan/tfs.h>
#include <wiretap/wtap.h>
#include <epan/crc16-tvb.h>


/* Proximity Integrated Circuit Card, i.e. the smartcard */
#define ADDR_PICC "PICC"
/* Proximity Coupling Device, i.e. the card reader */
#define ADDR_PCD  "PCD"

/* event byte in the PCAP ISO14443 pseudo-header */
#define ISO14443_EVT_DATA_PICC_TO_PCD              0xFF
#define ISO14443_EVT_DATA_PCD_TO_PICC              0xFE
#define ISO14443_EVT_FIELD_OFF                     0xFD
#define ISO14443_EVT_FIELD_ON                      0xFC
#define ISO14443_EVT_DATA_PICC_TO_PCD_CRC_DROPPED  0xFB
#define ISO14443_EVT_DATA_PCD_TO_PICC_CRC_DROPPED  0xFA

static const value_string iso14443_event[] = {
    { ISO14443_EVT_DATA_PICC_TO_PCD, "Data transfer PICC -> PCD" },
    { ISO14443_EVT_DATA_PCD_TO_PICC, "Data transfer PCD -> PICC" },
    { ISO14443_EVT_FIELD_ON,         "Field on" },
    { ISO14443_EVT_FIELD_OFF,        "Field off" },
    { ISO14443_EVT_DATA_PICC_TO_PCD_CRC_DROPPED,
        "Data transfer PICC -> PCD (CRC bytes were dropped)" },
    { ISO14443_EVT_DATA_PCD_TO_PICC_CRC_DROPPED,
        "Data transfer PCD -> PICC (CRC bytes were dropped)" },
    { 0, NULL }
};

#define IS_DATA_TRANSFER(e) \
    ((e)==ISO14443_EVT_DATA_PICC_TO_PCD || \
     (e)==ISO14443_EVT_DATA_PCD_TO_PICC || \
     (e)==ISO14443_EVT_DATA_PICC_TO_PCD_CRC_DROPPED || \
     (e)==ISO14443_EVT_DATA_PCD_TO_PICC_CRC_DROPPED)

typedef enum _iso14443_cmd_t {
    CMD_TYPE_WUPA,    /* REQA, WUPA or ATQA */
    CMD_TYPE_WUPB,    /* REQB, WUPB or ATQB */
    CMD_TYPE_HLTA,
    CMD_TYPE_UID,     /* anticollision or selection commands
                         and their answers */
    CMD_TYPE_ATS,     /* RATS or ATS */
    CMD_TYPE_ATTRIB,  /* Attrib or the answer to Attrib */
    CMD_TYPE_BLOCK,   /* I-, R- or S-blocks */
    CMD_TYPE_UNKNOWN
} iso14443_cmd_t;

static wmem_tree_t *transactions = NULL;

typedef struct _iso14443_transaction_t {
    guint32 rqst_frame;
    guint32 resp_frame;
    iso14443_cmd_t cmd;
} iso14443_transaction_t;

typedef enum _iso14443_type_t {
    ISO14443_A,
    ISO14443_B,
} iso14443_type_t;

static const value_string iso14443_short_frame[] = {
    { 0x26 , "REQA" },
    { 0x52 , "WUPA" },
    { 0, NULL }
};

/* convert a length code into the length it encodes
   code_to_len[x] is the length encoded by x
   this conversion is used for type A's FSCI and FSDI and for type B's
   maximum frame size */
static const guint16 code_to_len[] = {
    16, 24, 32, 40, 48, 64, 96, 128, 256
};
#define LEN_CODE_MAX array_length(code_to_len)

/* the bits in the ATS' TO byte indicating which other bytes are transmitted */
#define HAVE_TC1 0x40
#define HAVE_TB1 0x20
#define HAVE_TA1 0x10

#define I_BLOCK_TYPE 0x00
#define R_BLOCK_TYPE 0x02
#define S_BLOCK_TYPE 0x03
static const value_string iso14443_block_type[] = {
    { I_BLOCK_TYPE , "I-block" },
    { R_BLOCK_TYPE , "R-block" },
    { S_BLOCK_TYPE , "S-block" },
    { 0, NULL }
};

#define S_CMD_DESELECT 0x00
#define S_CMD_WTX      0x03
#define S_CMD_NONE     0xFF

static const value_string iso14443_s_block_cmd[] = {
    { S_CMD_DESELECT , "Deselect" },
    { S_CMD_WTX , "WTX" },
    { 0, NULL }
};

static const true_false_string tfs_wupb_reqb = { "WUPB", "REQB" };
static const true_false_string tfs_compliant_not_compliant = { "Compliant", "Not compliant" };
static const true_false_string tfs_incomplete_complete = { "Incomplete", "Complete" };
static const true_false_string tfs_iso_propr = { "As defined in ISO14443-3", "Proprietary" };
static const true_false_string tfs_not_required_required = { "Not required", "Required" };
static const true_false_string tfs_nak_ack = { "NAK", "ACK" };

#define CT_BYTE 0x88

#define CRC_LEN 2

/* we'll only ever have a single circuit,
   only one card can be active at a time */
#define ISO14443_CIRCUIT_ID 0

void proto_register_iso14443(void);
void proto_reg_handoff_iso14443(void);

static int proto_iso14443 = -1;

static dissector_handle_t iso14443_handle;

static dissector_table_t iso14443_cmd_type_table;

static int ett_iso14443 = -1;
static int ett_iso14443_hdr = -1;
static int ett_iso14443_msg = -1;
static int ett_iso14443_app_data = -1;
static int ett_iso14443_prot_inf = -1;
static int ett_iso14443_prot_type = -1;
static int ett_iso14443_ats_t0 = -1;
static int ett_iso14443_ats_tb1 = -1;
static int ett_iso14443_ats_tc1 = -1;
static int ett_iso14443_attr_p1 = -1;
static int ett_iso14443_attr_p2 = -1;
static int ett_iso14443_attr_p3 = -1;
static int ett_iso14443_pcb = -1;
static int ett_iso14443_inf = -1;

static int hf_iso14443_hdr_ver = -1;
static int hf_iso14443_event = -1;
static int hf_iso14443_len_field = -1;
static int hf_iso14443_resp_to = -1;
static int hf_iso14443_resp_in = -1;
static int hf_iso14443_short_frame = -1;
static int hf_iso14443_atqa_rfu1 = -1;
static int hf_iso14443_atqa_rfu2 = -1;
static int hf_iso14443_propr_coding = -1;
static int hf_iso14443_uid_bits = -1;
static int hf_iso14443_uid_size = -1;
static int hf_iso14443_max_frame_size = -1;
static int hf_iso14443_bit_frame_anticoll = -1;
static int hf_iso14443_apf = -1;
static int hf_iso14443_afi = -1;
static int hf_iso14443_ext_atqb = -1;
/* if this is present but unset, we have a REQB */
static int hf_iso14443_wupb = -1;
static int hf_iso14443_n = -1;
static int hf_iso14443_atqb_start = -1;
static int hf_iso14443_app_data = -1;
static int hf_iso14443_num_afi_apps = -1;
static int hf_iso14443_total_num_apps = -1;
static int hf_iso14443_prot_inf = -1;
static int hf_iso14443_bit_rate_cap = -1;
static int hf_iso14443_max_frame_size_code = -1;
static int hf_iso14443_prot_type = -1;
static int hf_iso14443_min_tr2 = -1;
static int hf_iso14443_4_compl_atqb = -1;
static int hf_iso14443_fwi = -1;
static int hf_iso14443_sfgi = -1;
static int hf_iso14443_adc = -1;
static int hf_iso14443_nad_supported = -1;
static int hf_iso14443_cid_supported = -1;
static int hf_iso14443_hlta = -1;
static int hf_iso14443_sel = -1;
static int hf_iso14443_nvb = -1;
static int hf_iso14443_4_compl_sak = -1;
static int hf_iso14443_uid_complete = -1;
static int hf_iso14443_ct = -1;
static int hf_iso14443_uid_cln = -1;
static int hf_iso14443_bcc = -1;
static int hf_iso14443_rats_start = -1;
static int hf_iso14443_fsdi = -1;
static int hf_iso14443_fsd = -1;
static int hf_iso14443_cid = -1;
static int hf_iso14443_tl = -1;
static int hf_iso14443_t0 = -1;
static int hf_iso14443_tc1_transmitted = -1;
static int hf_iso14443_tb1_transmitted = -1;
static int hf_iso14443_ta1_transmitted = -1;
static int hf_iso14443_fsci = -1;
static int hf_iso14443_fsc = -1;
static int hf_iso14443_tc1 = -1;
static int hf_iso14443_tb1 = -1;
static int hf_iso14443_ta1 = -1;
static int hf_iso14443_hist_bytes = -1;
static int hf_iso14443_attrib_start = -1;
static int hf_iso14443_pupi = -1;
static int hf_iso14443_param1 = -1;
static int hf_iso14443_min_tr0 = -1;
static int hf_iso14443_min_tr1 = -1;
static int hf_iso14443_eof = -1;
static int hf_iso14443_sof = -1;
static int hf_iso14443_param2 = -1;
static int hf_iso14443_param3 = -1;
static int hf_iso14443_param4 = -1;
static int hf_iso14443_mbli = -1;
static int hf_iso14443_pcb = -1;
static int hf_iso14443_block_type = -1;
static int hf_iso14443_i_blk_chaining = -1;
static int hf_iso14443_cid_following = -1;
static int hf_iso14443_nad_following = -1;
static int hf_iso14443_nak = -1;
static int hf_iso14443_blk_num = -1;
static int hf_iso14443_s_blk_cmd = -1;
static int hf_iso14443_pwr_lvl_ind = -1;
static int hf_iso14443_wtxm = -1;
static int hf_iso14443_inf = -1;
static int hf_iso14443_crc = -1;

static expert_field ei_iso14443_unknown_cmd = EI_INIT;
static expert_field ei_iso14443_wrong_crc = EI_INIT;
static expert_field ei_iso14443_uid_inval_size = EI_INIT;


/* dissect and verify the CRC
   the tvb includes the message followed by the CRC
   bytes 0 to crc_offset-1 contain the message, the CRC starts at
   crc_offset and is CRC_LEN bytes long */
static int dissect_iso14443_crc(tvbuff_t *tvb, gint crc_offset,
        packet_info *pinfo, proto_tree *tree, iso14443_type_t type)
{
    proto_item *crc_pi;
    guint16 crc_recv, crc_calc;

    crc_recv = tvb_get_letohs(tvb, crc_offset);
    if (type == ISO14443_A)
        crc_calc = crc16_iso14443a_tvb_offset(tvb, 0, crc_offset);
    else if (type == ISO14443_B)
        crc_calc = crc16_ccitt_tvb_offset(tvb, 0, crc_offset);
    else
        return -1;

    crc_pi = proto_tree_add_item(tree, hf_iso14443_crc,
            tvb, crc_offset, CRC_LEN, ENC_LITTLE_ENDIAN);
    proto_item_append_text(crc_pi, crc_recv==crc_calc ?
            " (correct)" : " (wrong)");
    /* add an expert info to allow filtering on packets with wrong crc */
    if (crc_recv != crc_calc)
        expert_add_info(pinfo, crc_pi, &ei_iso14443_wrong_crc);

    return CRC_LEN;
}


static int
dissect_iso14443_cmd_type_wupa(tvbuff_t *tvb, packet_info *pinfo,
        proto_tree *tree, void *data _U_)
{
    proto_item *ti = proto_tree_get_parent(tree);
    gint offset = 0;
    guint8 uid_bits, uid_size = 0;

    if (pinfo->p2p_dir == P2P_DIR_SENT) {
        const gchar *sf_str;
        sf_str = try_val_to_str(
            tvb_get_guint8(tvb, 0), iso14443_short_frame);
        proto_tree_add_item(tree, hf_iso14443_short_frame,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        offset++;
        if (sf_str) {
            proto_item_append_text(ti, ": %s", sf_str);
            col_set_str(pinfo->cinfo, COL_INFO, sf_str);
        }
    }
    else if (pinfo->p2p_dir == P2P_DIR_RECV) {
        guint16 atqa;
        proto_item *pi_uid;

        atqa = tvb_get_letohs(tvb, offset);
        col_set_str(pinfo->cinfo, COL_INFO, "ATQA");
        proto_item_append_text(ti, ": ATQA 0x%04x", atqa);

        proto_tree_add_item(tree, hf_iso14443_atqa_rfu1,
                tvb, offset, 2, ENC_LITTLE_ENDIAN);
        proto_tree_add_item(tree, hf_iso14443_propr_coding,
                tvb, offset, 2, ENC_LITTLE_ENDIAN);

        uid_bits = (atqa & 0xC0) >> 6;
        if (uid_bits == 0x00)
            uid_size = 4;
        else if (uid_bits == 0x01)
            uid_size = 7;
        else if (uid_bits == 0x02)
            uid_size = 10;

        pi_uid = proto_tree_add_item(tree, hf_iso14443_uid_bits,
                tvb, offset, 2, ENC_LITTLE_ENDIAN);
        if (uid_size != 0) {
            proto_item *pi_uid_size;
            pi_uid_size = proto_tree_add_uint(tree, hf_iso14443_uid_size,
                    tvb, offset+1, 1, uid_size);
            PROTO_ITEM_SET_GENERATED(pi_uid_size);
        }
        else {
            expert_add_info(pinfo, pi_uid, &ei_iso14443_uid_inval_size);
        }

        proto_tree_add_item(tree, hf_iso14443_atqa_rfu2,
                tvb, offset, 2, ENC_LITTLE_ENDIAN);
        proto_tree_add_item(tree, hf_iso14443_bit_frame_anticoll,
                tvb, offset, 2, ENC_LITTLE_ENDIAN);

        offset += 2;
    }

    return offset;
}


static int dissect_iso14443_atqb(tvbuff_t *tvb, gint offset,
        packet_info *pinfo, proto_tree *tree, gboolean crc_dropped)
{
    proto_item *ti = proto_tree_get_parent(tree);
    proto_item *app_data_it, *prot_inf_it, *prot_type_it;
    proto_tree *app_data_tree, *prot_inf_tree, *prot_type_tree;
    gint app_data_offset, rem_len;
    gboolean nad_supported, cid_supported;
    guint8 max_frame_size_code, fwi;
    proto_item *pi;
    gboolean iso14443_adc;
    guint8 prot_inf_len = 0;

    col_set_str(pinfo->cinfo, COL_INFO, "ATQB");
    proto_item_append_text(ti, ": ATQB");
    proto_tree_add_item(tree, hf_iso14443_atqb_start,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    offset++;

    proto_tree_add_item(tree, hf_iso14443_pupi,
            tvb, offset, 4, ENC_BIG_ENDIAN);
    offset += 4;

    app_data_offset = offset;
    app_data_it = proto_tree_add_item(tree, hf_iso14443_app_data,
            tvb, offset, 4, ENC_BIG_ENDIAN);
    offset += 4;

    /* we should not link the protocol info length to the "extended
       ATQB supported" field in the WUPB - even if the PCD supports
       extended ATQB, the PICC may still send a basic one */
    rem_len = tvb_reported_length_remaining(tvb, offset);
    if (!crc_dropped) {
        if (rem_len == 5 || rem_len == 6)
            prot_inf_len = rem_len - 2;
    }
    else if (rem_len == 3 || rem_len == 4)
        prot_inf_len = rem_len;
    /* XXX - exception if (prot_inf_len==0) */

    prot_inf_it = proto_tree_add_item(tree, hf_iso14443_prot_inf,
            tvb, offset, prot_inf_len, ENC_BIG_ENDIAN);
    prot_inf_tree = proto_item_add_subtree(
            prot_inf_it, ett_iso14443_prot_inf);
    proto_tree_add_item(prot_inf_tree, hf_iso14443_bit_rate_cap,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    offset++;
    max_frame_size_code = (tvb_get_guint8(tvb, offset) & 0xF0) >> 4;
    proto_tree_add_uint_bits_format_value(prot_inf_tree,
            hf_iso14443_max_frame_size_code,
            tvb, offset*8, 4, max_frame_size_code, "%d",
            max_frame_size_code);
    if (max_frame_size_code < LEN_CODE_MAX) {
        pi = proto_tree_add_uint(prot_inf_tree, hf_iso14443_max_frame_size,
                tvb, offset, 1, code_to_len[max_frame_size_code]);
        PROTO_ITEM_SET_GENERATED(pi);
    }
    prot_type_it = proto_tree_add_item(prot_inf_tree, hf_iso14443_prot_type,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    prot_type_tree = proto_item_add_subtree(
            prot_type_it, ett_iso14443_prot_type);
    proto_tree_add_item(prot_type_tree, hf_iso14443_min_tr2,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    proto_tree_add_item(prot_type_tree, hf_iso14443_4_compl_atqb,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    offset++;
    fwi = (tvb_get_guint8(tvb, offset) & 0xF0) >> 4;
    proto_tree_add_uint_bits_format_value(prot_inf_tree, hf_iso14443_fwi,
            tvb, offset*8, 4, fwi, "%d", fwi);
    iso14443_adc = tvb_get_guint8(tvb, offset) & 0x04;
    proto_tree_add_item(prot_inf_tree, hf_iso14443_adc,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    if (iso14443_adc) {
        app_data_tree = proto_item_add_subtree(
                app_data_it, ett_iso14443_app_data);
        proto_tree_add_item(app_data_tree, hf_iso14443_afi,
                tvb, app_data_offset, 1, ENC_BIG_ENDIAN);
        app_data_offset++;
        /* XXX - CRC_B app */
        app_data_offset += 2;
        proto_tree_add_item(app_data_tree, hf_iso14443_num_afi_apps,
                tvb, app_data_offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(app_data_tree, hf_iso14443_total_num_apps,
                tvb, app_data_offset, 1, ENC_BIG_ENDIAN);
    }

    nad_supported = tvb_get_guint8(tvb, offset) & 0x02;
    proto_tree_add_boolean_bits_format_value(prot_inf_tree,
            hf_iso14443_nad_supported, tvb, 8*offset+6, 1, nad_supported,
            "%s", nad_supported ?
            tfs_supported_not_supported.true_string :
            tfs_supported_not_supported.false_string);
    cid_supported = tvb_get_guint8(tvb, offset) & 0x01;
    proto_tree_add_boolean_bits_format_value(prot_inf_tree,
            hf_iso14443_cid_supported, tvb, 8*offset+7, 1, cid_supported,
            "%s", cid_supported ?
            tfs_supported_not_supported.true_string :
            tfs_supported_not_supported.false_string);
    offset++;

    /* XXX - extended ATQB */
    if (prot_inf_len>3)
        offset++;

    if (!crc_dropped)
        offset += dissect_iso14443_crc(tvb, offset, pinfo, tree, ISO14443_B);

    return offset;
}


static int
dissect_iso14443_cmd_type_wupb(tvbuff_t *tvb, packet_info *pinfo,
        proto_tree *tree, void *data)
{
    proto_item *ti = proto_tree_get_parent(tree);
    gboolean crc_dropped = (gboolean)GPOINTER_TO_UINT(data);
    gint offset = 0;
    guint8 param;
    const char *msg_type;

    if (pinfo->p2p_dir == P2P_DIR_SENT) {
        proto_tree_add_item(tree, hf_iso14443_apf,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        offset++;
        proto_tree_add_item(tree, hf_iso14443_afi,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        offset++;

        param = tvb_get_guint8(tvb, offset);
        proto_tree_add_item(tree, hf_iso14443_ext_atqb,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(tree, hf_iso14443_wupb,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        msg_type = (param & 0x08) ?
            tfs_wupb_reqb.true_string : tfs_wupb_reqb.false_string;
        col_set_str(pinfo->cinfo, COL_INFO, msg_type);
        proto_item_append_text(ti, ": %s", msg_type);
        proto_tree_add_uint_bits_format_value(tree, hf_iso14443_n,
                tvb, offset*8+5, 3, (guint8)pow(2, param&0x07),
                "%d", (guint8)pow(2, param&0x07));
        offset++;

        if (!crc_dropped)
            offset += dissect_iso14443_crc(tvb, offset, pinfo, tree, ISO14443_B);
    }
    else if (pinfo->p2p_dir == P2P_DIR_RECV) {
        offset = dissect_iso14443_atqb(tvb, offset, pinfo, tree, crc_dropped);
    }

    return offset;
}


static int
dissect_iso14443_cmd_type_hlta(tvbuff_t *tvb, packet_info *pinfo,
        proto_tree *tree, void *data)
{
    gboolean crc_dropped = (gboolean)GPOINTER_TO_UINT(data);
    proto_item *ti = proto_tree_get_parent(tree);
    gint offset = 0;

    col_set_str(pinfo->cinfo, COL_INFO, "HLTA");
    proto_item_append_text(ti, ": HLTA");
    proto_tree_add_item(tree, hf_iso14443_hlta,
            tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;

    if (!crc_dropped)
        offset += dissect_iso14443_crc(tvb, offset, pinfo, tree, ISO14443_A);

    return offset;
}


static int dissect_iso14443_uid_part(
        tvbuff_t *tvb, gint offset, packet_info *pinfo _U_, proto_tree *tree)
{
    guint8 uid_len = 4;

    if (tvb_get_guint8(tvb, offset) == CT_BYTE) {
        proto_tree_add_item(tree, hf_iso14443_ct, tvb, offset, 1, ENC_NA);
        offset++;
        uid_len = 3;
    }

    proto_tree_add_item(tree, hf_iso14443_uid_cln, tvb, offset, uid_len, ENC_NA);
    offset += uid_len;
    proto_tree_add_item(tree, hf_iso14443_bcc, tvb, offset, 1, ENC_BIG_ENDIAN);
    offset++;

    return offset;
}


static int
dissect_iso14443_cmd_type_uid(tvbuff_t *tvb, packet_info *pinfo,
        proto_tree *tree, void *data)
{
    gboolean crc_dropped = (gboolean)GPOINTER_TO_UINT(data);
    proto_item *ti = proto_tree_get_parent(tree);
    gint offset = 0;

    if (pinfo->p2p_dir == P2P_DIR_SENT) {
        proto_tree_add_item(tree, hf_iso14443_sel,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        offset++;
        proto_tree_add_item(tree, hf_iso14443_nvb,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        offset++;

        if (tvb_reported_length_remaining(tvb, offset) == 0) {
            col_set_str(pinfo->cinfo, COL_INFO, "Anticollision");
            proto_item_append_text(ti, ": Anticollision");
        }
        else {
            col_set_str(pinfo->cinfo, COL_INFO, "Select");
            proto_item_append_text(ti, ": Select");
            offset = dissect_iso14443_uid_part(tvb, offset, pinfo, tree);
            if (!crc_dropped)
                offset += dissect_iso14443_crc(tvb, offset, pinfo, tree, ISO14443_A);
        }
    }
    else if (pinfo->p2p_dir == P2P_DIR_RECV) {
        if (tvb_reported_length_remaining(tvb, offset) <= 3) {
            col_set_str(pinfo->cinfo, COL_INFO, "SAK");
            proto_item_append_text(ti, ": SAK");
            proto_tree_add_item(tree, hf_iso14443_4_compl_sak,
                tvb, offset, 1, ENC_BIG_ENDIAN);
            proto_tree_add_item(tree, hf_iso14443_uid_complete,
                tvb, offset, 1, ENC_BIG_ENDIAN);
            offset++;
            if (!crc_dropped)
                offset += dissect_iso14443_crc(tvb, offset, pinfo, tree, ISO14443_A);
        }
        else if (tvb_reported_length_remaining(tvb, offset) == 5) {
            col_set_str(pinfo->cinfo, COL_INFO, "UID");
            offset = dissect_iso14443_uid_part(tvb, offset, pinfo, tree);
        }
    }

    return offset;
}


static int dissect_iso14443_ats(tvbuff_t *tvb, gint offset,
        packet_info *pinfo, proto_tree *tree, gboolean crc_dropped)
{
    proto_item *ti = proto_tree_get_parent(tree);
    circuit_t *circuit;
    guint8 tl, t0 = 0, fsci, fwi, sfgi;
    proto_item *t0_it, *tb1_it, *tc1_it, *pi;
    proto_tree *t0_tree, *tb1_tree, *tc1_tree;
    gint offset_tl, hist_len;
    gboolean nad_supported, cid_supported;

    col_set_str(pinfo->cinfo, COL_INFO, "ATS");
    proto_item_append_text(ti, ": ATS");

    circuit = circuit_new(CT_ISO14443, ISO14443_CIRCUIT_ID, pinfo->num);
    circuit_add_proto_data(circuit,
            proto_iso14443, GUINT_TO_POINTER((guint)ISO14443_A));

    offset_tl = offset;
    tl = tvb_get_guint8(tvb, offset);
    proto_tree_add_item(tree, hf_iso14443_tl,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    offset++;
    /* the length in TL includes the TL byte itself */
    if (tl >= 2) {
        t0 = tvb_get_guint8(tvb, offset);
        t0_it = proto_tree_add_item(tree, hf_iso14443_t0,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        t0_tree = proto_item_add_subtree(t0_it, ett_iso14443_ats_t0);
        proto_tree_add_item(t0_tree, hf_iso14443_tc1_transmitted,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(t0_tree, hf_iso14443_tb1_transmitted,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(t0_tree, hf_iso14443_ta1_transmitted,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        fsci = t0 & 0x0F;
        proto_tree_add_item(t0_tree, hf_iso14443_fsci,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        if (fsci < LEN_CODE_MAX) {
            pi = proto_tree_add_uint(t0_tree, hf_iso14443_fsc,
                    tvb, offset, 1, code_to_len[fsci]);
            PROTO_ITEM_SET_GENERATED(pi);
        }
        offset++;
    }
    if (t0 & HAVE_TA1) {
        proto_tree_add_item(tree, hf_iso14443_ta1,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        offset++;
    }
    if (t0 & HAVE_TB1) {
        tb1_it = proto_tree_add_item(tree, hf_iso14443_tb1,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        tb1_tree = proto_item_add_subtree(tb1_it, ett_iso14443_ats_tb1);
        fwi = (tvb_get_guint8(tvb, offset) & 0xF0) >> 4;
        proto_tree_add_uint_bits_format_value(tb1_tree, hf_iso14443_fwi,
                tvb, offset*8, 4, fwi, "%d", fwi);
        sfgi = tvb_get_guint8(tvb, offset) & 0x0F;
        proto_tree_add_uint_bits_format_value(tb1_tree, hf_iso14443_sfgi,
                tvb, offset*8+4, 4, sfgi, "%d", sfgi);
        offset++;
    }
    if (t0 & HAVE_TC1) {
        tc1_it = proto_tree_add_item(tree, hf_iso14443_tc1,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        tc1_tree = proto_item_add_subtree(tc1_it, ett_iso14443_ats_tc1);

        cid_supported = tvb_get_guint8(tvb, offset) & 0x02;
        proto_tree_add_boolean_bits_format_value(tc1_tree,
                hf_iso14443_cid_supported, tvb, 8*offset+6, 1, cid_supported,
                "%s", cid_supported ?
                tfs_supported_not_supported.true_string :
                tfs_supported_not_supported.false_string);
        nad_supported = tvb_get_guint8(tvb, offset) & 0x01;
        proto_tree_add_boolean_bits_format_value(tc1_tree,
                hf_iso14443_nad_supported, tvb, 8*offset+7, 1, nad_supported,
                "%s", nad_supported ?
                tfs_supported_not_supported.true_string :
                tfs_supported_not_supported.false_string);
        offset++;
    }
    hist_len = tl - (offset - offset_tl);
    if (hist_len > 0) {
        proto_tree_add_item(tree, hf_iso14443_hist_bytes,
                tvb, offset, hist_len, ENC_NA);
        offset += hist_len;
    }
    if (!crc_dropped)
        offset += dissect_iso14443_crc(tvb, offset, pinfo, tree, ISO14443_A);

    return offset;
}


static int
dissect_iso14443_cmd_type_ats(tvbuff_t *tvb, packet_info *pinfo,
        proto_tree *tree, void *data)
{
    gboolean crc_dropped = (gboolean)GPOINTER_TO_UINT(data);
    proto_item *ti = proto_tree_get_parent(tree);
    gint offset = 0;
    guint8 fsdi, cid;
    proto_item *pi;

    if (pinfo->p2p_dir == P2P_DIR_SENT) {
        col_set_str(pinfo->cinfo, COL_INFO, "RATS");
        proto_item_append_text(ti, ": RATS");

        proto_tree_add_item(tree, hf_iso14443_rats_start,
                tvb, offset, 1, ENC_BIG_ENDIAN);
        offset++;
        fsdi = tvb_get_guint8(tvb, offset) >> 4;
        proto_tree_add_uint_bits_format_value(tree, hf_iso14443_fsdi,
                tvb, offset*8, 4, fsdi, "%d", fsdi);
        if (fsdi < LEN_CODE_MAX) {
            pi = proto_tree_add_uint(tree, hf_iso14443_fsd,
                    tvb, offset, 1, code_to_len[fsdi]);
            PROTO_ITEM_SET_GENERATED(pi);
        }
        cid = tvb_get_guint8(tvb, offset) & 0x0F;
        proto_tree_add_uint_bits_format_value(tree, hf_iso14443_cid,
                tvb, offset*8+4, 4, cid, "%d", cid);
        offset++;
        if (!crc_dropped)
            offset += dissect_iso14443_crc(tvb, offset, pinfo, tree, ISO14443_A);
    }
    else if (pinfo->p2p_dir == P2P_DIR_RECV) {
        offset = dissect_iso14443_ats(tvb, offset, pinfo, tree, crc_dropped);
    }

    return offset;
}


static int dissect_iso14443_attrib(tvbuff_t *tvb, gint offset,
        packet_info *pinfo, proto_tree *tree, gboolean crc_dropped)
{
    proto_item *ti = proto_tree_get_parent(tree);
    proto_item *p1_it, *p2_it, *p3_it, *pi;
    proto_tree *p1_tree, *p2_tree, *p3_tree;
    guint8 max_frame_size_code;
    gint hl_inf_len;

    col_set_str(pinfo->cinfo, COL_INFO, "Attrib");
    proto_item_append_text(ti, ": Attrib");

    proto_tree_add_item(tree, hf_iso14443_attrib_start,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    offset++;
    proto_tree_add_item(tree, hf_iso14443_pupi,
            tvb, offset, 4, ENC_BIG_ENDIAN);
    offset += 4;

    p1_it = proto_tree_add_item(tree, hf_iso14443_param1,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    p1_tree = proto_item_add_subtree( p1_it, ett_iso14443_attr_p1);
    proto_tree_add_item(p1_tree, hf_iso14443_min_tr0,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    proto_tree_add_item(p1_tree, hf_iso14443_min_tr1,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    proto_tree_add_item(p1_tree, hf_iso14443_eof,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    proto_tree_add_item(p1_tree, hf_iso14443_sof,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    offset++;

    p2_it = proto_tree_add_item(tree, hf_iso14443_param2,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    p2_tree = proto_item_add_subtree( p2_it, ett_iso14443_attr_p2);
    max_frame_size_code = tvb_get_guint8(tvb, offset) & 0x0F;
    proto_tree_add_uint_bits_format_value(p2_tree,
            hf_iso14443_max_frame_size_code,
            tvb, offset*8+4, 4, max_frame_size_code, "%d",
            max_frame_size_code);
    if (max_frame_size_code < LEN_CODE_MAX) {
        pi = proto_tree_add_uint(p2_tree, hf_iso14443_max_frame_size,
                tvb, offset, 1, code_to_len[max_frame_size_code]);
        PROTO_ITEM_SET_GENERATED(pi);
    }
    offset++;

    p3_it = proto_tree_add_item(tree, hf_iso14443_param3,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    p3_tree = proto_item_add_subtree(p3_it, ett_iso14443_attr_p3);
    proto_tree_add_item(p3_tree, hf_iso14443_min_tr2,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    proto_tree_add_item(p3_tree, hf_iso14443_4_compl_atqb,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    offset++;
    /* XXX - subtree, details for each parameter */
    proto_tree_add_item(tree, hf_iso14443_param4,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    offset++;
    hl_inf_len = crc_dropped ?
        tvb_reported_length_remaining(tvb, offset) :
        tvb_reported_length_remaining(tvb, offset) - CRC_LEN;
    if (hl_inf_len > 0) {
        offset += hl_inf_len;
    }
    if (!crc_dropped)
        offset += dissect_iso14443_crc(tvb, offset, pinfo, tree, ISO14443_B);

    return offset;
}


static int
dissect_iso14443_cmd_type_attrib(tvbuff_t *tvb, packet_info *pinfo,
        proto_tree *tree, void *data)
{
    gboolean crc_dropped = (gboolean)GPOINTER_TO_UINT(data);
    proto_item *ti = proto_tree_get_parent(tree);
    gint offset = 0;
    guint8 mbli, cid;
    gint hl_resp_len;
    circuit_t *circuit;

    if (pinfo->p2p_dir == P2P_DIR_SENT) {
        offset = dissect_iso14443_attrib(
                tvb, offset, pinfo, tree, crc_dropped);
    }
    else if (pinfo->p2p_dir == P2P_DIR_RECV) {
        col_set_str(pinfo->cinfo, COL_INFO, "Response to Attrib");
        proto_item_append_text(ti, ": Response to Attrib");

        circuit = circuit_new(CT_ISO14443, ISO14443_CIRCUIT_ID, pinfo->num);
        circuit_add_proto_data(circuit,
                proto_iso14443, GUINT_TO_POINTER((guint)ISO14443_B));

        mbli = tvb_get_guint8(tvb, offset) >> 4;
        proto_tree_add_uint_bits_format_value(tree, hf_iso14443_mbli,
                tvb, offset*8, 4, mbli, "%d", mbli);
        cid = tvb_get_guint8(tvb, offset) & 0x0F;
        proto_tree_add_uint_bits_format_value(tree, hf_iso14443_cid,
                tvb, offset*8+4, 4, cid, "%d", cid);
        offset++;

        hl_resp_len = crc_dropped ?
            tvb_reported_length_remaining(tvb, offset) :
            tvb_reported_length_remaining(tvb, offset) - CRC_LEN;
        if (hl_resp_len > 0) {
            offset += hl_resp_len;
        }

        if (!crc_dropped)
            offset += dissect_iso14443_crc(tvb, offset, pinfo, tree, ISO14443_B);
    }

    return offset;
}


static int
dissect_iso14443_cmd_type_block(tvbuff_t *tvb, packet_info *pinfo,
        proto_tree *tree, void *data)
{
    gboolean crc_dropped = (gboolean)GPOINTER_TO_UINT(data);
    proto_item *ti = proto_tree_get_parent(tree);
    gint offset = 0;
    guint8 pcb, block_type;
    const gchar *bt_str;
    proto_item *pcb_ti, *inf_ti;
    proto_tree *pcb_tree, *inf_tree;
    gboolean has_cid, has_nad = FALSE;
    guint8 s_cmd = S_CMD_NONE;
    guint8 inf_len;

    pcb = tvb_get_guint8(tvb, offset);
    block_type = (pcb & 0xC0) >> 6;
    bt_str = try_val_to_str(block_type, iso14443_block_type);
    if (bt_str) {
        proto_item_append_text(ti, ": %s", bt_str);
        col_set_str(pinfo->cinfo, COL_INFO, bt_str);
    }
    has_cid = ((pcb & 0x08) != 0);

    pcb_ti = proto_tree_add_item(tree, hf_iso14443_pcb,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    pcb_tree = proto_item_add_subtree(pcb_ti, ett_iso14443_pcb);
    proto_tree_add_item(pcb_tree, hf_iso14443_block_type,
            tvb, offset, 1, ENC_BIG_ENDIAN);

    switch (block_type) {
        case I_BLOCK_TYPE:
            col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL,
                    (pcb & 0x10) ? "Chaining" : "No chaining");
            proto_tree_add_item(pcb_tree, hf_iso14443_i_blk_chaining,
                    tvb, offset, 1, ENC_BIG_ENDIAN);
            proto_tree_add_item(pcb_tree, hf_iso14443_cid_following,
                    tvb, offset, 1, ENC_BIG_ENDIAN);
            has_nad = ((pcb & 0x40) != 0);
            proto_tree_add_item(pcb_tree, hf_iso14443_nad_following,
                    tvb, offset, 1, ENC_BIG_ENDIAN);
            col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL,
                    "Block number %d", pcb & 0x01);
            proto_tree_add_item(pcb_tree, hf_iso14443_blk_num,
                    tvb, offset, 1, ENC_BIG_ENDIAN);
            break;

        case R_BLOCK_TYPE:
            col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL,
                    "%s", (pcb & 0x10) ?
                    tfs_nak_ack.true_string : tfs_nak_ack.false_string);
            proto_tree_add_item(pcb_tree, hf_iso14443_nak,
                    tvb, offset, 1, ENC_BIG_ENDIAN);
            proto_tree_add_item(pcb_tree, hf_iso14443_cid_following,
                    tvb, offset, 1, ENC_BIG_ENDIAN);
            col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL,
                    "Block number %d", pcb & 0x01);
            proto_tree_add_item(pcb_tree, hf_iso14443_blk_num,
                    tvb, offset, 1, ENC_BIG_ENDIAN);
            break;

        case S_BLOCK_TYPE:
            s_cmd = (pcb & 0x30) >> 4;
            proto_tree_add_item(pcb_tree, hf_iso14443_s_blk_cmd,
                    tvb, offset, 1, ENC_BIG_ENDIAN);
            col_append_sep_fstr(pinfo->cinfo, COL_INFO, NULL, "%s",
                    val_to_str(s_cmd, iso14443_s_block_cmd,
                        "Unknown (0x%02x)"));
            proto_tree_add_item(pcb_tree, hf_iso14443_cid_following,
                    tvb, offset, 1, ENC_BIG_ENDIAN);
            break;

        default:
            /* Report an error? b8 = 0, b7 = 1 */
            break;
    }
    offset++;

    if (has_cid)
        offset++;
    if (has_nad)
        offset++;

    switch (block_type) {
        case I_BLOCK_TYPE:
            inf_len = crc_dropped ?
                tvb_reported_length_remaining(tvb, offset) :
                tvb_reported_length_remaining(tvb, offset) - 2;
            break;

        /* R-blocks have no payload */

        case S_BLOCK_TYPE:
            inf_len = 1;
            break;

        default:
            inf_len = 0;
            break;

    }

    if (inf_len > 0) {
        inf_ti = proto_tree_add_item(tree, hf_iso14443_inf,
                tvb, offset, inf_len, ENC_NA);
        if (block_type == S_BLOCK_TYPE) {
            if (s_cmd == S_CMD_WTX) {
                inf_tree = proto_item_add_subtree(inf_ti, ett_iso14443_inf);
                if (pinfo->p2p_dir == P2P_DIR_RECV) {
                    proto_tree_add_item(inf_tree, hf_iso14443_pwr_lvl_ind,
                            tvb, offset, 1, ENC_BIG_ENDIAN);
                }
                proto_tree_add_item(inf_tree, hf_iso14443_wtxm,
                        tvb, offset, 1, ENC_BIG_ENDIAN);
            }
        }
        offset += inf_len;
    }


    if (!crc_dropped) {
        iso14443_type_t t;
        circuit_t *circuit;

        circuit = find_circuit(CT_ISO14443, ISO14443_CIRCUIT_ID, pinfo->num);
        if (circuit) {
            t = (iso14443_type_t)GPOINTER_TO_UINT(
                    (gpointer)circuit_get_proto_data(circuit, proto_iso14443));
            offset += dissect_iso14443_crc(tvb, offset, pinfo, tree, t);
        }
    }

    return offset;
}


static gint
iso14443_set_addrs(guint8 event, packet_info *pinfo)
{
    if (!IS_DATA_TRANSFER(event))
        return -1;

    /* pinfo->p2p_dir is from the perspective of the card reader,
       like in iso7816
       i.e sent is from reader to card, received is from card to reader */
    if (event == ISO14443_EVT_DATA_PCD_TO_PICC ||
            event == ISO14443_EVT_DATA_PCD_TO_PICC_CRC_DROPPED) {

        set_address(&pinfo->src, AT_STRINGZ,
                (int)strlen(ADDR_PCD)+1, ADDR_PCD);
        set_address(&pinfo->dst, AT_STRINGZ,
                (int)strlen(ADDR_PICC)+1 , ADDR_PICC);

        pinfo->p2p_dir = P2P_DIR_SENT;
    }
    else {
        set_address(&pinfo->src, AT_STRINGZ,
                (int)strlen(ADDR_PICC)+1 , ADDR_PICC);
        set_address(&pinfo->dst, AT_STRINGZ,
                (int)strlen(ADDR_PCD)+1, ADDR_PCD);

        pinfo->p2p_dir = P2P_DIR_RECV;
    }

    return 1;
}


static inline gboolean
iso14443_block_pcb(guint8 byte)
{
    if ((byte & 0xE2) == 0x02) {
        /* I-block */
        return TRUE;
    }
    else if ((byte & 0xE6) == 0xA2) {
        /* R-block */
        return TRUE;
    }
    else if ((byte & 0xC7) == 0xC2) {
        /* S-block */
        return TRUE;
    }

    return FALSE;
}


static iso14443_transaction_t *
iso14443_get_transaction(packet_info *pinfo, proto_tree *tree)
{
    proto_item *it;
    iso14443_transaction_t *iso14443_trans = NULL;

    if (pinfo->p2p_dir == P2P_DIR_SENT) {
        if (PINFO_FD_VISITED(pinfo)) {
            iso14443_trans = (iso14443_transaction_t *)wmem_tree_lookup32(
                    transactions, pinfo->num);
            if (iso14443_trans && iso14443_trans->rqst_frame==pinfo->num &&
                    iso14443_trans->resp_frame!=0) {
               it = proto_tree_add_uint(tree, hf_iso14443_resp_in,
                       NULL, 0, 0, iso14443_trans->resp_frame);
               PROTO_ITEM_SET_GENERATED(it);
            }
        }
        else {
            iso14443_trans = wmem_new(wmem_file_scope(), iso14443_transaction_t);
            iso14443_trans->rqst_frame = pinfo->num;
            iso14443_trans->resp_frame = 0;
            /* iso14443_trans->ctrl = ctrl; */
            wmem_tree_insert32(transactions,
                    iso14443_trans->rqst_frame, (void *)iso14443_trans);
        }
    }
    else if (pinfo->p2p_dir == P2P_DIR_RECV) {
        iso14443_trans = (iso14443_transaction_t *)wmem_tree_lookup32_le(
                transactions, pinfo->num);
        if (iso14443_trans && iso14443_trans->resp_frame==0) {
            /* there's a pending request, this packet is the response */
            iso14443_trans->resp_frame = pinfo->num;
        }

        if (iso14443_trans && iso14443_trans->resp_frame == pinfo->num) {
            it = proto_tree_add_uint(tree, hf_iso14443_resp_to,
                    NULL, 0, 0, iso14443_trans->rqst_frame);
            PROTO_ITEM_SET_GENERATED(it);
        }
    }

    return iso14443_trans;
}


static iso14443_cmd_t iso14443_get_cmd_type(
        tvbuff_t *tvb, packet_info *pinfo, iso14443_transaction_t *trans)
{
    guint8 first_byte;

    first_byte = tvb_get_guint8(tvb, 0);

    if (pinfo->p2p_dir == P2P_DIR_SENT) {
        if (tvb_reported_length(tvb) == 1) {
            return CMD_TYPE_WUPA;
        }
        else if (first_byte == 0x05) {
            return CMD_TYPE_WUPB;
        }
        else if (first_byte == 0x50) {
            return CMD_TYPE_HLTA;
        }
        else if (first_byte == 0x1D) {
            return CMD_TYPE_ATTRIB;
        }
        else if (first_byte == 0xE0) {
            return CMD_TYPE_ATS;
        }
        else if ((first_byte & 0xF8) == 0x90) {
            return CMD_TYPE_UID;
        }
        else if (iso14443_block_pcb(first_byte)) {
            return CMD_TYPE_BLOCK;
        }
    }
    else if (pinfo->p2p_dir == P2P_DIR_RECV) {
        if (trans->cmd != CMD_TYPE_UNKNOWN) {
            return trans->cmd;
        }
        else if (iso14443_block_pcb(first_byte)) {
            return CMD_TYPE_BLOCK;
        }

        /* we don't try to detect any response messages based on their
           length - depending on the log tool, two trailing CRC bytes
           may be added or not */
    }

    return CMD_TYPE_UNKNOWN;
}


static gint
dissect_iso14443_msg(tvbuff_t *tvb, packet_info *pinfo,
        proto_tree *tree, guint8 event)
{
    gboolean crc_dropped = FALSE;
    iso14443_transaction_t *iso14443_trans;
    iso14443_cmd_t cmd;
    proto_tree *msg_tree;
    gint ret;

    if (event == ISO14443_EVT_DATA_PICC_TO_PCD_CRC_DROPPED ||
            event == ISO14443_EVT_DATA_PCD_TO_PICC_CRC_DROPPED) {
        crc_dropped = TRUE;
    }

    iso14443_trans = iso14443_get_transaction(pinfo, tree);
    if (!iso14443_trans)
        return -1;

    cmd = iso14443_get_cmd_type(tvb, pinfo, iso14443_trans);
    if (cmd != CMD_TYPE_UNKNOWN)
        iso14443_trans->cmd = cmd;

    msg_tree = proto_tree_add_subtree(
            tree, tvb, 0, -1, ett_iso14443_msg, NULL, "Message");

    ret = dissector_try_uint_new(iso14443_cmd_type_table, cmd,
            tvb, pinfo, msg_tree, FALSE, GUINT_TO_POINTER((guint)crc_dropped));
    if (ret == 0) {
        proto_tree_add_expert(tree, pinfo, &ei_iso14443_unknown_cmd,
                tvb, 0, tvb_captured_length(tvb));
        ret = tvb_captured_length(tvb);
    }

    return ret;
}


static int dissect_iso14443(tvbuff_t *tvb,
        packet_info *pinfo, proto_tree *tree, void *data _U_)
{
    gint        packet_len;
    gint        offset = 0, offset_ver, offset_evt, offset_len_field;
    gint        ret;
    guint8      version, event;
    const gchar *event_str;
    guint16     len_field;
    proto_item *tree_ti;
    proto_tree *iso14443_tree, *hdr_tree;
    tvbuff_t    *payload_tvb;
    circuit_t   *circuit;

    if (tvb_captured_length(tvb) < 4)
        return 0;

    offset_ver = offset;
    version = tvb_get_guint8(tvb, offset++);
    if (version != 0)
        return 0;

    offset_evt = offset;
    event = tvb_get_guint8(tvb, offset++);
    event_str = try_val_to_str(event, iso14443_event);
    if (!event_str)
        return 0;

    packet_len = tvb_reported_length(tvb);
    offset_len_field = offset;
    len_field = tvb_get_ntohs(tvb, offset);
    if (len_field != (packet_len-4))
        return 0;
    offset += 2;

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

    tree_ti = proto_tree_add_protocol_format(tree, proto_iso14443,
            tvb, 0, tvb_reported_length(tvb), "ISO 14443");
    iso14443_tree = proto_item_add_subtree(tree_ti, ett_iso14443);

    hdr_tree = proto_tree_add_subtree(iso14443_tree,
            tvb, 0, offset, ett_iso14443_hdr, NULL, "Pseudo header");

    proto_tree_add_item(hdr_tree, hf_iso14443_hdr_ver,
            tvb, offset_ver, 1, ENC_BIG_ENDIAN);
    proto_tree_add_item(hdr_tree, hf_iso14443_event,
            tvb, offset_evt, 1, ENC_BIG_ENDIAN);
    proto_tree_add_item(hdr_tree, hf_iso14443_len_field,
            tvb, offset_len_field, 2, ENC_BIG_ENDIAN);

    if (IS_DATA_TRANSFER(event)) {
        iso14443_set_addrs(event, pinfo);

        payload_tvb = tvb_new_subset_remaining(tvb, offset);
        ret = dissect_iso14443_msg(payload_tvb, pinfo, iso14443_tree, event);
        if (ret > 0)
            offset += ret;
    }
    else {
        col_set_str(pinfo->cinfo, COL_INFO, event_str);

        /* all events that are not data transfers close the connection
           to the card (e.g. the field is switched on or off) */
        circuit = find_circuit(CT_ISO14443, ISO14443_CIRCUIT_ID, pinfo->num);
        if (circuit)
                close_circuit(circuit, pinfo->num);
    }

    return offset;
}


void
proto_register_iso14443(void)
{
    static hf_register_info hf[] = {
        { &hf_iso14443_hdr_ver,
            { "Version", "iso14443.hdr_version",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_event,
            { "Event", "iso14443.event",
                FT_UINT8, BASE_HEX, VALS(iso14443_event), 0, NULL, HFILL }
        },
        { &hf_iso14443_len_field,
            { "Length field", "iso14443.length_field",
                FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_resp_in,
            { "Response In", "iso14443.resp_in",
                FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_iso14443_resp_to,
            { "Response To", "iso14443.resp_to",
                FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_iso14443_short_frame,
            { "Short frame", "iso14443.short_frame",
                FT_UINT8, BASE_HEX, VALS(iso14443_short_frame), 0, NULL, HFILL }
        },
        { &hf_iso14443_atqa_rfu1,
            { "RFU", "iso14443.atqa_rfu",
                FT_UINT16, BASE_HEX, NULL, 0xF000, NULL, HFILL }
        },
        { &hf_iso14443_atqa_rfu2,
            { "RFU", "iso14443.atqa_rfu",
                FT_UINT16, BASE_HEX, NULL, 0x0020, NULL, HFILL }
        },
        { &hf_iso14443_propr_coding,
            { "Proprietary coding", "iso14443.propr_coding",
                FT_UINT16, BASE_HEX, NULL, 0x0F00, NULL, HFILL }
        },
        { &hf_iso14443_uid_bits,
            { "UID bits", "iso14443.uid_bits",
                FT_UINT16, BASE_HEX, NULL, 0x00C0, NULL, HFILL }
        },
        { &hf_iso14443_uid_size,
            { "UID size", "iso14443.uid_size",
                FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_max_frame_size,
            { "Maximum frame size", "iso14443.max_frame_size",
                FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_bit_frame_anticoll,
            { "Bit frame anticollision", "iso14443.bit_frame_anticoll",
                FT_UINT16, BASE_HEX, NULL, 0x001F, NULL, HFILL }
        },
        { &hf_iso14443_apf,
            { "Anticollision prefix", "iso14443.apf",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_afi,
            { "Application Family Identifier", "iso14443.afi",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_ext_atqb,
            { "Extended ATQB", "iso14443.ext_atqb", FT_BOOLEAN, 8,
                TFS(&tfs_supported_not_supported), 0x10, NULL, HFILL }
        },
        { &hf_iso14443_wupb,
            { "WUPB/REQB", "iso14443.wupb",
                FT_BOOLEAN, 8, TFS(&tfs_wupb_reqb), 0x08, NULL, HFILL }
        },
        { &hf_iso14443_n,
            { "N", "iso14443.n",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_atqb_start,
            { "Start byte", "iso14443.atqb_start",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_app_data,
            { "Application data", "iso14443.application_data",
                FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_num_afi_apps,
            { "Number of applications for this AFI", "iso14443.num_afi_apps",
                FT_UINT8, BASE_DEC, NULL, 0xF0, NULL, HFILL }
        },
        { &hf_iso14443_total_num_apps,
            { "Total number of applications", "iso14443.total_num_apps",
                FT_UINT8, BASE_DEC, NULL, 0x0F, NULL, HFILL }
        },
        { &hf_iso14443_prot_inf,
            { "Protocol info", "iso14443.protocol_info",
                FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_bit_rate_cap,
            { "Bit rate capability", "iso14443.bit_rate_cap",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_max_frame_size_code,
            { "Max frame size code", "iso14443.max_frame_size_code",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_prot_type,
            { "Protocol type", "iso14443.protocol_type",
                FT_UINT8, BASE_HEX, NULL, 0x0F, NULL, HFILL }
        },
        /* we're using min tr2 in two different places (atqb and attrib)
           the relative position within the byte is identical so we can
           set the mask here */
        { &hf_iso14443_min_tr2,
            { "Minimum TR2", "iso14443.min_tr2",
                FT_UINT8, BASE_HEX, NULL, 0x06, NULL, HFILL }
        },
        /* the same goes for the 14443-4 compliant flag */
        { &hf_iso14443_4_compl_atqb,
            { "Compliant with ISO 14443-4", "iso14443.4_compliant", FT_BOOLEAN, 8,
                TFS(&tfs_compliant_not_compliant), 0x01, NULL, HFILL }
        },
        { &hf_iso14443_fwi,
            { "FWI", "iso14443.fwi", FT_UINT8, BASE_DEC,
                NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_sfgi,
            { "SFGI", "iso14443.sfgi", FT_UINT8, BASE_DEC,
                NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_adc,
            { "Application Data Coding", "iso14443.adc", FT_BOOLEAN, 8,
                TFS(&tfs_iso_propr), 0x04, NULL, HFILL }
        },
        { &hf_iso14443_nad_supported,
            { "NAD", "iso14443.nad_supported", FT_BOOLEAN, 8,
                TFS(&tfs_supported_not_supported), 0, NULL, HFILL }
        },
        { &hf_iso14443_cid_supported,
            { "CID", "iso14443.cid_supported", FT_BOOLEAN, 8,
                TFS(&tfs_supported_not_supported), 0, NULL, HFILL }
        },
        { &hf_iso14443_hlta,
            { "HLTA", "iso14443.hlta",
                FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_sel,
            { "SEL", "iso14443.sel",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_nvb,
            { "NVB", "iso14443.nvb",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_4_compl_sak,
            { "Compliant with ISO 14443-4", "iso14443.4_compliant", FT_BOOLEAN, 8,
                TFS(&tfs_compliant_not_compliant), 0x20, NULL, HFILL }
        },
        { &hf_iso14443_uid_complete,
            { "UID complete", "iso14443.uid_complete", FT_BOOLEAN, 8,
                TFS(&tfs_incomplete_complete), 0x04, NULL, HFILL }
        },
        { &hf_iso14443_ct,
            { "CT", "iso14443.ct",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_uid_cln,
            { "UID_CLn", "iso14443.uid_cln",
                FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_bcc,
            { "BCC", "iso14443.bcc",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_rats_start,
            { "Start byte", "iso14443.rats_start",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_fsdi,
            { "FSDI", "iso14443.fsdi",
                FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_fsd,
            { "FSD", "iso14443.fsd",
                FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_cid,
            { "CID", "iso14443.cid",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_tl,
            { "Length byte TL", "iso14443.tl",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_t0,
            { "Format byte T0", "iso14443.t0",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_tc1_transmitted,
            { "TC(1) transmitted", "iso14443.tc1_transmitted",
                FT_BOOLEAN, 8, NULL, HAVE_TC1, NULL, HFILL }
        },
        { &hf_iso14443_tb1_transmitted,
            { "TB(1) transmitted", "iso14443.tb1_transmitted",
                FT_BOOLEAN, 8, NULL, HAVE_TB1, NULL, HFILL }
        },
        { &hf_iso14443_ta1_transmitted,
            { "TA(1) transmitted", "iso14443.ta1_transmitted",
                FT_BOOLEAN, 8, NULL, HAVE_TA1, NULL, HFILL }
        },
        { &hf_iso14443_fsci,
            { "FSCI", "iso14443.fsci",
                FT_UINT8, BASE_DEC, NULL, 0x0F, NULL, HFILL }
        },
        { &hf_iso14443_fsc,
            { "FSC", "iso14443.fsc",
                FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_tc1,
            { "Interface byte TC1", "iso14443.tc1",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_tb1,
            { "Interface byte TB1", "iso14443.tb1",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_ta1,
            { "Interface byte TA1", "iso14443.ta1",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_hist_bytes,
            { "Historical bytes", "iso14443.hist_bytes",
                FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_attrib_start,
            { "Start byte", "iso14443.attrib_start",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_pupi,
            { "PUPI", "iso14443.pupi",
                FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_param1,
            { "Param 1", "iso14443.param1",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_min_tr0,
            { "Minimum TR0", "iso14443.min_tr0",
                FT_UINT8, BASE_HEX, NULL, 0xC0, NULL, HFILL }
        },
        { &hf_iso14443_min_tr1,
            { "Minimum TR1", "iso14443.min_tr1",
                FT_UINT8, BASE_HEX, NULL, 0x30, NULL, HFILL }
        },
        { &hf_iso14443_eof,
            { "EOF", "iso14443.eof", FT_BOOLEAN, 8,
                TFS(&tfs_not_required_required), 0x08, NULL, HFILL }
        },
        { &hf_iso14443_sof,
            { "SOF", "iso14443.eof", FT_BOOLEAN, 8,
                TFS(&tfs_not_required_required), 0x04, NULL, HFILL }
        },
        { &hf_iso14443_param2,
            { "Param 2", "iso14443.param2",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_param3,
            { "Param 3", "iso14443.param3",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_param4,
            { "Param 4", "iso14443.param4",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_mbli,
            { "MBLI", "iso14443.mbli",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_pcb,
            { "PCB", "iso14443.pcb",
                FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_block_type,
            { "Block type", "iso14443.block_type", FT_UINT8,
                BASE_HEX, VALS(iso14443_block_type), 0xC0, NULL, HFILL }
        },
        { &hf_iso14443_i_blk_chaining,
            { "Chaining", "iso14443.i_block_chaining", FT_BOOLEAN, 8,
                TFS(&tfs_set_notset), 0x10, NULL, HFILL }
        },
        { &hf_iso14443_cid_following,
            { "CID following", "iso14443.cid_following", FT_BOOLEAN, 8,
                TFS(&tfs_true_false), 0x08, NULL, HFILL }
        },
        { &hf_iso14443_nad_following,
            { "NAD following", "iso14443.nad_following", FT_BOOLEAN, 8,
                TFS(&tfs_true_false), 0x04, NULL, HFILL }
        },
        { &hf_iso14443_nak,
            { "NAK/ACK", "iso14443.nak", FT_BOOLEAN, 8,
                TFS(&tfs_nak_ack), 0x10, NULL, HFILL }
        },
        { &hf_iso14443_blk_num,
            { "Block number", "iso14443.block_number",
                FT_UINT8, BASE_DEC, NULL, 0x01, NULL, HFILL }
        },
        { &hf_iso14443_s_blk_cmd,
            { "Command", "iso14443.s_block_cmd", FT_UINT8,
                BASE_HEX, VALS(iso14443_s_block_cmd), 0x30, NULL, HFILL }
        },
        { &hf_iso14443_pwr_lvl_ind,
            { "Power level indication", "iso14443.pwr_lvl_ind",
                FT_UINT8, BASE_HEX, NULL, 0xC0, NULL, HFILL }
        },
        { &hf_iso14443_wtxm,
            { "WTXM", "iso14443.wtxm",
                FT_UINT8, BASE_DEC, NULL, 0x3F, NULL, HFILL }
        },
        { &hf_iso14443_inf,
            { "INF", "iso14443.inf",
                FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }
        },
        { &hf_iso14443_crc,
            { "CRC", "iso14443.crc",
                FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL }
        }
   };

    static gint *ett[] = {
        &ett_iso14443,
        &ett_iso14443_hdr,
        &ett_iso14443_msg,
        &ett_iso14443_app_data,
        &ett_iso14443_prot_inf,
        &ett_iso14443_prot_type,
        &ett_iso14443_ats_t0,
        &ett_iso14443_ats_tb1,
        &ett_iso14443_ats_tc1,
        &ett_iso14443_attr_p1,
        &ett_iso14443_attr_p2,
        &ett_iso14443_attr_p3,
        &ett_iso14443_pcb,
        &ett_iso14443_inf
    };

    static ei_register_info ei[] = {
        { &ei_iso14443_unknown_cmd,
            { "iso14443.cmd.unknown", PI_PROTOCOL, PI_WARN,
                "Unknown ISO1443 command", EXPFILL }
        },
        { &ei_iso14443_wrong_crc,
            { "iso14443.crc.wrong", PI_PROTOCOL, PI_WARN, "Wrong CRC", EXPFILL }
        },
        { &ei_iso14443_uid_inval_size,
            { "iso14443.uid.invalid_size", PI_PROTOCOL, PI_WARN,
                "Invalid UID size", EXPFILL }
        }
    };

    expert_module_t* expert_iso14443;

    proto_iso14443 = proto_register_protocol(
            "ISO/IEC 14443", "ISO 14443", "iso14443");
    proto_register_field_array(proto_iso14443, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));
    expert_iso14443 = expert_register_protocol(proto_iso14443);
    expert_register_field_array(expert_iso14443, ei, array_length(ei));

    iso14443_cmd_type_table = register_dissector_table(
            "iso14443.cmd_type", "ISO14443 Command Type",
            proto_iso14443, FT_UINT8, BASE_DEC);

    iso14443_handle =
        register_dissector("iso14443", dissect_iso14443, proto_iso14443);

    transactions = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope());
}


void
proto_reg_handoff_iso14443(void)
{
  dissector_handle_t cmd_type_handle;

  dissector_add_uint("wtap_encap", WTAP_ENCAP_ISO14443, iso14443_handle);

  cmd_type_handle = create_dissector_handle(
          dissect_iso14443_cmd_type_wupa, proto_iso14443);
  dissector_add_uint("iso14443.cmd_type", CMD_TYPE_WUPA, cmd_type_handle);

  cmd_type_handle = create_dissector_handle(
          dissect_iso14443_cmd_type_wupb, proto_iso14443);
  dissector_add_uint("iso14443.cmd_type", CMD_TYPE_WUPB, cmd_type_handle);

  cmd_type_handle = create_dissector_handle(
          dissect_iso14443_cmd_type_hlta, proto_iso14443);
  dissector_add_uint("iso14443.cmd_type", CMD_TYPE_HLTA, cmd_type_handle);

  cmd_type_handle = create_dissector_handle(
          dissect_iso14443_cmd_type_uid, proto_iso14443);
  dissector_add_uint("iso14443.cmd_type", CMD_TYPE_UID, cmd_type_handle);

  cmd_type_handle = create_dissector_handle(
          dissect_iso14443_cmd_type_ats, proto_iso14443);
  dissector_add_uint("iso14443.cmd_type", CMD_TYPE_ATS, cmd_type_handle);

  cmd_type_handle = create_dissector_handle(
          dissect_iso14443_cmd_type_attrib, proto_iso14443);
  dissector_add_uint("iso14443.cmd_type", CMD_TYPE_ATTRIB, cmd_type_handle);

  cmd_type_handle = create_dissector_handle(
          dissect_iso14443_cmd_type_block, proto_iso14443);
  dissector_add_uint("iso14443.cmd_type", CMD_TYPE_BLOCK, cmd_type_handle);
}


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