/* packet-wimaxasncp.c
 *
 * Routines for WiMAX ASN Control Plane packet dissection dissection
 *
 * Copyright 2007, Mobile Metrics - http://mobilemetrics.net/
 *
 * Author: Stephen Croll <croll@mobilemetrics.net>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */


#include "config.h"

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

#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/sminmpec.h>
#include <epan/addr_resolv.h>
#include <epan/ipproto.h>
#include <epan/expert.h>
#include <epan/eap.h>
#include <wsutil/filesystem.h>
#include <wsutil/report_err.h>

#include "wimaxasncp_dict.h"

/* Forward declarations we need below */
void proto_register_wimaxasncp(void);
void proto_reg_handoff_wimaxasncp(void);

/* Initialize the protocol and registered fields */
static int proto_wimaxasncp                     = -1;
static int hf_wimaxasncp_version                = -1;
static int hf_wimaxasncp_flags                  = -1;
static int hf_wimaxasncp_function_type          = -1;
static int hf_wimaxasncp_op_id                  = -1;
/* static int hf_wimaxasncp_message_type           = -1; */
/* static int hf_wimaxasncp_qos_msg                = -1; */
/* static int hf_wimaxasncp_ho_control_msg         = -1; */
/* static int hf_wimaxasncp_data_path_control_msg  = -1; */
/* static int hf_wimaxasncp_context_delivery_msg   = -1; */
/* static int hf_wimaxasncp_r3_mobility_msg        = -1; */
/* static int hf_wimaxasncp_paging_msg             = -1; */
/* static int hf_wimaxasncp_rrm_msg                = -1; */
/* static int hf_wimaxasncp_authentication_msg     = -1; */
/* static int hf_wimaxasncp_ms_state_msg           = -1; */
/* static int hf_wimaxasncp_reauthentication_msg   = -1; */
/* static int hf_wimaxasncp_session_msg            = -1; */
static int hf_wimaxasncp_length                 = -1;
static int hf_wimaxasncp_msid                   = -1;
static int hf_wimaxasncp_reserved1              = -1;
static int hf_wimaxasncp_transaction_id         = -1;
static int hf_wimaxasncp_reserved2              = -1;
/* static int hf_wimaxasncp_tlv                    = -1; */
static int hf_wimaxasncp_tlv_type               = -1;
static int hf_wimaxasncp_tlv_length             = -1;
static int hf_wimaxasncp_tlv_value_bytes        = -1;
static int hf_wimaxasncp_tlv_value_bitflags8    = -1;
static int hf_wimaxasncp_tlv_value_bitflags16   = -1;
static int hf_wimaxasncp_tlv_value_bitflags32   = -1;
/* static int hf_wimaxasncp_tlv_value_protocol     = -1; */
/* static int hf_wimaxasncp_tlv_value_vendor_id    = -1; */

/* Preferences */
static gboolean show_transaction_id_d_bit      = FALSE;
static gboolean debug_enabled                  = FALSE;

/* Default WiMAX ASN control protocol port */
#define WIMAXASNCP_DEF_UDP_PORT     2231
static guint global_wimaxasncp_udp_port = WIMAXASNCP_DEF_UDP_PORT;


/* Initialize the subtree pointers */
static gint ett_wimaxasncp                                       = -1;
static gint ett_wimaxasncp_flags                                 = -1;
static gint ett_wimaxasncp_tlv                                   = -1;
static gint ett_wimaxasncp_tlv_value_bitflags8                   = -1;
static gint ett_wimaxasncp_tlv_value_bitflags16                  = -1;
static gint ett_wimaxasncp_tlv_value_bitflags32                  = -1;
static gint ett_wimaxasncp_tlv_protocol_list                     = -1;
static gint ett_wimaxasncp_tlv_port_range_list                   = -1;
static gint ett_wimaxasncp_tlv_ip_address_mask_list              = -1;
static gint ett_wimaxasncp_tlv_ip_address_mask                   = -1;
static gint ett_wimaxasncp_tlv_eap                               = -1;
static gint ett_wimaxasncp_tlv_vendor_specific_information_field = -1;
static gint ett_wimaxasncp_port_range                            = -1;

static expert_field ei_wimaxasncp_tlv_type = EI_INIT;
static expert_field ei_wimaxasncp_function_type = EI_INIT;
static expert_field ei_wimaxasncp_op_id = EI_INIT;
static expert_field ei_wimaxasncp_length_bad = EI_INIT;

/* Header size, up to, but not including, the TLV fields. */
#define WIMAXASNCP_HEADER_SIZE       20

/* Offset to end of the length field in the headder. */
#define WIMAXASNCP_HEADER_LENGTH_END 6

#define WIMAXASNCP_BIT32(n) (1U << (31 - (n)))
#define WIMAXASNCP_BIT16(n) (1U << (15 - (n)))
#define WIMAXASNCP_BIT8(n)  (1U << ( 7 - (n)))

#define WIMAXASNCP_FLAGS_T  WIMAXASNCP_BIT8(6)
#define WIMAXASNCP_FLAGS_R  WIMAXASNCP_BIT8(7)

typedef struct {
    GArray* hf;
    GArray* ett;
} wimaxasncp_build_dict_t;

static wimaxasncp_dict_t *wimaxasncp_dict = NULL;

wimaxasncp_build_dict_t wimaxasncp_build_dict;

static wimaxasncp_dict_tlv_t wimaxasncp_tlv_not_found =
{
    0, "Unknown", NULL, WIMAXASNCP_TLV_UNKNOWN, 0,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    NULL, NULL, NULL
};

static dissector_handle_t wimaxasncp_handle;
static dissector_handle_t eap_handle;

/* ------------------------------------------------------------------------- */

static const value_string wimaxasncp_flag_vals[] =
{
    { WIMAXASNCP_BIT8(0), "Reserved" },
    { WIMAXASNCP_BIT8(1), "Reserved" },
    { WIMAXASNCP_BIT8(2), "Reserved" },
    { WIMAXASNCP_BIT8(3), "Reserved" },
    { WIMAXASNCP_BIT8(4), "Reserved" },
    { WIMAXASNCP_BIT8(5), "Reserved" },
    { WIMAXASNCP_FLAGS_T, "T - Source and Destination Identifier TLVs"},
    { WIMAXASNCP_FLAGS_R, "R - Reset Next Expected Transaction ID"},
    { 0,                  NULL}
};

/* ------------------------------------------------------------------------- */

static const value_string wimaxasncp_op_id_vals[] =
{
    { 0,   "Invalid"},
    { 1,   "Request/Initiation"},
    { 2,   "Response"},
    { 3,   "Ack"},
    { 4,   "Indication"},
    { 5,   "Reserved"},
    { 6,   "Reserved"},
    { 7,   "Reserved"},
    { 0,   NULL}
};

/* ------------------------------------------------------------------------- */

#define WIMAXASNCP_FT_QOS                 1
#define WIMAXASNCP_FT_HO_CONTROL          2
#define WIMAXASNCP_FT_DATA_PATH_CONTROL   3
#define WIMAXASNCP_FT_CONTEXT_TRANSFER    4
#define WIMAXASNCP_FT_R3_MOBILITY         5
#define WIMAXASNCP_FT_PAGING              6
#define WIMAXASNCP_FT_RRM                 7
#define WIMAXASNCP_FT_AUTHENTICATION      8
#define WIMAXASNCP_FT_MS_STATE            9
#define WIMAXASNCP_FT_REAUTHENTICATION    10
/* since NWG R1 V1.2.0 */
#define WIMAXASNCP_FT_IM_OPERATIONS       10
/* since NWG R1 V1.2.1 */
#define WIMAXASNCP_FT_ACCOUNTING          11

/* ------------------------------------------------------------------------- */

/* struct to hold a value_string tuple, per version */
typedef struct _ver_value_string
{
    guint32 since;
    value_string vs;
} ver_value_string;

static const ver_value_string wimaxasncp_function_type_vals[] =
{
    {0,                          { WIMAXASNCP_FT_QOS,                   "QoS"}},
    {0,                          { WIMAXASNCP_FT_HO_CONTROL,            "HO Control"}},
    {0,                          { WIMAXASNCP_FT_DATA_PATH_CONTROL,     "Data Path Control"}},
    {0,                          { WIMAXASNCP_FT_CONTEXT_TRANSFER,      "Context Transfer"}},
    {0,                          { WIMAXASNCP_FT_R3_MOBILITY,           "R3 Mobility"}},
    {0,                          { WIMAXASNCP_FT_PAGING,                "Paging"}},
    {0,                          { WIMAXASNCP_FT_RRM,                   "RRM"}},
    {0,                          { WIMAXASNCP_FT_AUTHENTICATION,        "Authentication Relay"}},
    {0,                          { WIMAXASNCP_FT_MS_STATE,              "MS State"}},
    {0,                          { WIMAXASNCP_FT_REAUTHENTICATION,      "Re-Authentication"}},
    {WIMAXASNCP_NWGVER_R10_V120, {WIMAXASNCP_FT_IM_OPERATIONS,          "IM Operations"}},
    {WIMAXASNCP_NWGVER_R10_V121, { WIMAXASNCP_FT_ACCOUNTING,            "Accounting"}},
    {0, { 0, NULL}}
};

/* ------------------------------------------------------------------------- */

static const ver_value_string wimaxasncp_qos_msg_vals[] =
{
    {0,{ 1,  "RR_Req"}},
    {0,{ 2,  "RR_Rsp"}},
    {0,{ 3,  "RR_Ack"}},
    {0,{ 0,   NULL}}
};

/* ------------------------------------------------------------------------- */

static const ver_value_string wimaxasncp_ho_control_msg_vals[] =
{
    {0,                          { 1,  "HO_Ack"}},
    {0,                          { 2,  "HO_Complete"}},
    {0,                          { 3,  "HO_Cnf"}},
    {0,                          { 4,  "HO_Req"}},
    {0,                          { 5,  "HO_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 1,  "HO_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 2,  "HO_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 3,  "HO_Ack"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 4,  "HO_Cnf"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 5,  "HO_Complete"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 6,  "HO_Directive"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 7,  "HO_Directive_Rsp"}},
    {0, { 0,   NULL}}
};

/* ------------------------------------------------------------------------- */

static const ver_value_string wimaxasncp_data_path_control_msg_vals[] =
{
    {0, { 1,   "Path_Dereg_Ack"}},
    {0, { 2,   "Path_Dereg_Req"}},
    {0, { 3,   "Path_Dereg_Rsp"}},
    {0, { 4,   "Path_Modification_Ack"}},
    {0, { 5,   "Path_Modification_Req"}},
    {0, { 6,   "Path_Modification_Rsp"}},
    {0, { 7,   "Path_Prereg_Ack"}},
    {0, { 8,   "Path_Prereg_Req"}},
    {0, { 9,   "Path_Prereg_Rsp"}},
    {0, { 10,  "Path_Reg_Ack"}},
    {0, { 11,  "Path_Reg_Req"}},
    {0, { 12,  "Path_Reg_Rsp"}},
    {0, { 13,  "MS_Attachment_Req"}},
    {0, { 14,  "MS_Attachment_Rsp"}},
    {0, { 15,  "MS_Attachment_Ack"}},
    {0, { 16,  "Key_Change_Directive"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 1,   "Path_Dereg_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 2,   "Path_Dereg_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 3,   "Path_Dereg_Ack"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 4,   "Path_Modification_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 5,   "Path_Modification_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 6,   "Path_Modification_Ack"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 7,   "Path_Prereg_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 8,   "Path_Prereg_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 9,   "Path_Prereg_Ack"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 10,  "Path_Reg_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 11,  "Path_Reg_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 12,  "Path_Reg_Ack"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 13,  "Obsolete"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 14,  "Obsolete"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 15,  "Obsolete"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 16,  "Obsolete"}},
    {0, { 0,   NULL}}
};

/* ------------------------------------------------------------------------- */

static const ver_value_string wimaxasncp_context_transfer_msg_vals[] =
{
    {0,                          { 1,  "Context_Rpt"}},
    {0,                          { 2,  "Context_Req"}},
    {0,                          { 3,  "Context_Ack"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 1,  "Context_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 2,  "Context_Rpt"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 4,  "CMAC_Key_Count_Update"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 5,  "CMAC_Key_Count_Update_ACK"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 6,  "CMAC_Key_Count_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 7,  "CMAC_Key_Count_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 8,  "Prepaid Request"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 9,  "Prepaid Notify"}},
    {WIMAXASNCP_NWGVER_R10_V121, { 6,  "VOID"}},
    {WIMAXASNCP_NWGVER_R10_V121, { 7,  "VOID"}},
    {WIMAXASNCP_NWGVER_R10_V121, { 0,   NULL}}
};

/* ------------------------------------------------------------------------- */

static const ver_value_string wimaxasncp_r3_mobility_msg_vals[] =
{
    {0,                          { 1,  "Anchor_DPF_HO_Req"}},
    {0,                          { 2,  "Anchor_DPF_HO_Trigger"}},
    {0,                          { 3,  "Anchor_DPF_HO_Rsp"}},
    {0,                          { 4,  "Anchor_DPF_Relocate_Req"}},
    {0,                          { 5,  "FA_Register_Req"}},
    {0,                          { 6,  "FA_Register_Rsp"}},
    {0,                          { 7,  "Anchor_DPF_Relocate_Rsp"}},
    {0,                          { 8,  "FA_Revoke_Req"}},
    {0,                          { 9,  "FA_Revoke_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 5,  "Anchor_DPF_Relocate_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 6,  "FA_Register_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 7,  "FA_Register_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 10, "Anchor_DPF_Release_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 11, "Relocation_Ready_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 12, "Relocation_Ready_Rsp"}},
    {0, { 0,   NULL}}
};

/* ------------------------------------------------------------------------- */

static const ver_value_string wimaxasncp_paging_msg_vals[] =
{
    {0,                          { 1,  "Initiate_Paging_Req"}},
    {0,                          { 2,  "Initiate_Paging_Rsp"}},
    {0,                          { 3,  "LU_Cnf"}},
    {0,                          { 4,  "LU_Req"}},
    {0,                          { 5,  "LU_Rsp"}},
    {0,                          { 6,  "Paging_Announce"}},
    {0,                          { 7,  "CMAC_Key_Count_Req"}},
    {0,                          { 8,  "CMAC_Key_Count_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 1,  "Paging_Announce"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 2,  "Delete_MS_Entry_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 3,  "PC_Relocation_Ind"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 4,  "PC_Relocation_Ack"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 5,  "Obsolete"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 6,  "Obsolete"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 7,  "Obsolete"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 8,  "Obsolete"}},
    {0, { 0,   NULL}}
};

/* ------------------------------------------------------------------------- */

static const ver_value_string wimaxasncp_rrm_msg_vals[] =
{
    {0,                          { 1,  "R6 PHY_Parameters_Req"}},
    {0,                          { 2,  "R6 PHY_Parameters_Rpt"}},
    {0,                          { 3,  "R4/R6 Spare_Capacity_Req"}},
    {0,                          { 4,  "R4/R6 Spare_Capacity_Rpt"}},
    {0,                          { 5,  "R6 Neighbor_BS_Resource_Status_Update"}},
    {0,                          { 6,  "R4/R6 Radio_Config_Update_Req"}},
    {0,                          { 7,  "R4/R6 Radio_Config_Update_Rpt"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 8,  "R4/R6 Radio_Config_Update_Ack"}},
    {0, { 0,   NULL}}
};

/* ------------------------------------------------------------------------- */

static const ver_value_string wimaxasncp_authentication_msg_vals[] =
{
    {0,                          { 1,  "AR_Authenticated_Eap_Start"}},
    {0,                          { 2,  "AR_Authenticated_EAP_Transfer"}},
    {0,                          { 3,  "AR_Eap_Start"}},
    {0,                          { 4,  "AR_EAP_Transfer"}},
    {0,                          { 5,  "AR_EAP_Complete"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 1,  "AR_EAP_Start"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 2,  "AR_EAP_Transfer"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 3,  "Bulk_Interim_Update"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 4,  "Bulk_Interim_Update_Ack"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 5,  "Obsolete"}},
    {0, { 0,   NULL}}
};

/* ------------------------------------------------------------------------- */

static const ver_value_string wimaxasncp_ms_state_msg_vals[] =
{
    {0,                          { 1,  "IM_Entry_State_Change_Req"}},
    {0,                          { 2,  "IM_Entry_State_Change_Rsp"}},
    {0,                          { 3,  "IM_Exit_State_Change_Req"}},
    {0,                          { 4,  "IM_Exit_State_Change_Rsp"}},
    {0,                          { 5,  "NW_ReEntry_State_Change_Directive"}},
    {0,                          { 6,  "MS_PreAttachment_Req"}},
    {0,                          { 7,  "MS_PreAttachment_Rsp"}},
    {0,                          { 8,  "MS_PreAttachment_Ack"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 1,  "MS_PreAttachment_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 2,  "MS_PreAttachment_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 3,  "MS_PreAttachment_Ack"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 4,  "MS_Attachment_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 5,  "MS_Attachment_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 6,  "MS_Attachment_Ack"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 7,  "Key_Change_Directive"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 8,  "Key_Change_Cnf"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 9,  "Key_Change_Ack"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 10, "Relocation_Conplete_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 11, "Relocation_Conplete_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 12, "Relocation_Conplete_Ack"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 13, "Relocation_Notify"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 14, "Relocation_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 15, "Relocation_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 16, "NetExit_MS_State_Change_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 17, "NetExit_MS_State_Change_Rsp"}},
    {0, { 0,   NULL}}
};

/* ------------------------------------------------------------------------- */

/* note - function type 10-im_operation, was once used for re-authrntication */
static const ver_value_string wimaxasncp_im_operations_msg_vals[] =
{
    {0,                          { 1,  "AR_EAP_Start"}},
    {0,                          { 2,  "Key_Change_Directive"}},
    {0,                          { 3,  "Key_Change_Cnf"}},
    {0,                          { 4,  "Relocation_Cnf"}},
    {0,                          { 5,  "Relocation_Confirm_Ack"}},
    {0,                          { 6,  "Relocation_Notify"}},
    {0,                          { 7,  "Relocation_Notify_Ack"}},
    {0,                          { 8,  "Relocation_Req"}},
    {0,                          { 9,  "Relocation_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 1,  "IM_Entry_State_Change_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 2,  "IM_Entry_State_Change_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 3,  "IM_Entry_State_Change_Ack"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 4,  "IM_Exit_State_Change_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 5,  "IM_Exit_State_Change_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 6,  "Initiate_Paging_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 7,  "Initiate_Paging_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 8,  "LU_Req"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 9,  "LU_Rsp"}},
    {WIMAXASNCP_NWGVER_R10_V120, { 10, "LU_Cnf"}},
    {0, { 0,   NULL}}
};

/* ------------------------------------------------------------------------- */

static const ver_value_string wimaxasncp_accounting_msg_vals_r1v121[] =
{
    {WIMAXASNCP_NWGVER_R10_V121, { 1,  "Hot_lining_Req"}},
    {WIMAXASNCP_NWGVER_R10_V121, { 2,  "Hot_lining_Rsp"}},
    {0, { 0,   NULL}}
};

/* ------------------------------------------------------------------------- */

/* supported NWG versions */
static const enum_val_t wimaxasncp_nwg_versions[] = {
    { "Release 1.0, Version 1.0.0" , "R1.0 v1.0.0" , WIMAXASNCP_NWGVER_R10_V100  },
    { "Release 1.0, Version 1.2.0" , "R1.0 v1.2.0" , WIMAXASNCP_NWGVER_R10_V120  },
    { "Release 1.0, Version 1.2.1" , "R1.0 v1.2.1" , WIMAXASNCP_NWGVER_R10_V121  },
    { NULL, NULL, 0 }
};

/* ------------------------------------------------------------------------- */

/* NWG version */
#define WIMAXASNCP_DEF_NWGVER       WIMAXASNCP_NWGVER_R10_V121
static guint global_wimaxasncp_nwg_ver = WIMAXASNCP_DEF_NWGVER;

/* ========================================================================= */

typedef struct {
    guint8 function_type;
    const ver_value_string *vals;
} wimaxasncp_func_msg_t;

/* ------------------------------------------------------------------------ */

static const wimaxasncp_func_msg_t wimaxasncp_func_to_msg_vals_map[] =
{
    { WIMAXASNCP_FT_QOS,               wimaxasncp_qos_msg_vals },
    { WIMAXASNCP_FT_HO_CONTROL,        wimaxasncp_ho_control_msg_vals },
    { WIMAXASNCP_FT_DATA_PATH_CONTROL, wimaxasncp_data_path_control_msg_vals },
    { WIMAXASNCP_FT_CONTEXT_TRANSFER,  wimaxasncp_context_transfer_msg_vals },
    { WIMAXASNCP_FT_R3_MOBILITY,       wimaxasncp_r3_mobility_msg_vals },
    { WIMAXASNCP_FT_PAGING,            wimaxasncp_paging_msg_vals },
    { WIMAXASNCP_FT_RRM,               wimaxasncp_rrm_msg_vals },
    { WIMAXASNCP_FT_AUTHENTICATION,    wimaxasncp_authentication_msg_vals },
    { WIMAXASNCP_FT_MS_STATE,          wimaxasncp_ms_state_msg_vals },
    { WIMAXASNCP_FT_IM_OPERATIONS,     wimaxasncp_im_operations_msg_vals },
    { WIMAXASNCP_FT_ACCOUNTING,        wimaxasncp_accounting_msg_vals_r1v121 }
};

/* ========================================================================= */

static const wimaxasncp_dict_tlv_t *wimaxasncp_get_tlv_info(
    guint16 type)
{
    wimaxasncp_dict_tlv_t *res = NULL;

    if (wimaxasncp_dict)
    {
        wimaxasncp_dict_tlv_t *tlv;

        for (tlv = wimaxasncp_dict->tlvs; tlv; tlv = tlv->next)
        {
            if (tlv->type == type)
            {
                /* if the TLV is defined for current NWG version */
                if (tlv->since<= global_wimaxasncp_nwg_ver)
                {
                    /* if the current TLV is newer then last found TLV, save it */
                    if (!res || (tlv->since > res->since))
                    {
                        res = tlv;
                    }
                }
            }
        }
    }

    if (debug_enabled && !res)
    {
        g_print("fix-me: unknown TLV type: %u\n", type);
    }

    return res? res:&wimaxasncp_tlv_not_found;
}

/* ========================================================================= */

static const gchar *wimaxasncp_get_enum_name(
    const wimaxasncp_dict_tlv_t *tlv_info,
    guint32 code)
{
    if (tlv_info->enum_vs)
    {
        return val_to_str(code, tlv_info->enum_vs, "Unknown");
    }
    else
    {
        return "Unknown";
    }
}

/* ========================================================================= */

static const value_string wimaxasncp_decode_type_vals[] =
{
    { WIMAXASNCP_TLV_UNKNOWN,              "WIMAXASNCP_TLV_UNKNOWN"},
    { WIMAXASNCP_TLV_TBD,                  "WIMAXASNCP_TLV_TBD"},
    { WIMAXASNCP_TLV_COMPOUND,             "WIMAXASNCP_TLV_COMPOUND"},
    { WIMAXASNCP_TLV_BYTES,                "WIMAXASNCP_TLV_BYTES"},
    { WIMAXASNCP_TLV_ENUM8,                "WIMAXASNCP_TLV_ENUM8"},
    { WIMAXASNCP_TLV_ENUM16,               "WIMAXASNCP_TLV_ENUM16"},
    { WIMAXASNCP_TLV_ENUM32,               "WIMAXASNCP_TLV_ENUM32"},
    { WIMAXASNCP_TLV_ETHER,                "WIMAXASNCP_TLV_ETHER"},
    { WIMAXASNCP_TLV_ASCII_STRING,         "WIMAXASNCP_TLV_ASCII_STRING"},
    { WIMAXASNCP_TLV_FLAG0,                "WIMAXASNCP_TLV_FLAG0"},
    { WIMAXASNCP_TLV_BITFLAGS8,            "WIMAXASNCP_TLV_BITFLAGS8"},
    { WIMAXASNCP_TLV_BITFLAGS16,           "WIMAXASNCP_TLV_BITFLAGS16"},
    { WIMAXASNCP_TLV_BITFLAGS32,           "WIMAXASNCP_TLV_BITFLAGS32"},
    { WIMAXASNCP_TLV_ID,                   "WIMAXASNCP_TLV_ID"},
    { WIMAXASNCP_TLV_HEX8,                 "WIMAXASNCP_TLV_HEX8"},
    { WIMAXASNCP_TLV_HEX16,                "WIMAXASNCP_TLV_HEX16"},
    { WIMAXASNCP_TLV_HEX32,                "WIMAXASNCP_TLV_HEX32"},
    { WIMAXASNCP_TLV_DEC8,                 "WIMAXASNCP_TLV_DEC8"},
    { WIMAXASNCP_TLV_DEC16,                "WIMAXASNCP_TLV_DEC16"},
    { WIMAXASNCP_TLV_DEC32,                "WIMAXASNCP_TLV_DEC32"},
    { WIMAXASNCP_TLV_IP_ADDRESS,           "WIMAXASNCP_TLV_IP_ADDRESS"},
    { WIMAXASNCP_TLV_IPV4_ADDRESS,         "WIMAXASNCP_TLV_IPV4_ADDRESS"},
    { WIMAXASNCP_TLV_PROTOCOL_LIST,        "WIMAXASNCP_TLV_PROTOCOL_LIST"},
    { WIMAXASNCP_TLV_PORT_RANGE_LIST,      "WIMAXASNCP_TLV_PORT_RANGE_LIST"},
    { WIMAXASNCP_TLV_IP_ADDRESS_MASK_LIST, "WIMAXASNCP_TLV_IP_ADDRESS_MASK_LIST"},
    { WIMAXASNCP_TLV_VENDOR_SPECIFIC,      "WIMAXASNCP_TLV_VENDOR_SPECIFIC"},
    { 0, NULL}
};

/* ========================================================================= */

static void wimaxasncp_proto_tree_add_tlv_ipv4_value(
    tvbuff_t   *tvb,
    proto_tree *tree,
    proto_item *tlv_item,
    guint       offset,
    const wimaxasncp_dict_tlv_t *tlv_info)
{
    int          hf_value;
    guint32      ip;
    const gchar *addr_res;

    if (tlv_info->hf_ipv4 != -1)
    {
        hf_value = tlv_info->hf_ipv4;
    }
    else
    {
        hf_value = tlv_info->hf_value;
    }

    ip = tvb_get_ipv4(tvb, offset);
    addr_res = tvb_address_with_resolution_to_str(wmem_packet_scope(), tvb, AT_IPv4, offset);

    proto_tree_add_ipv4_format(
        tree, hf_value,
        tvb, offset, 4, ip,
        "Value: %s", addr_res);

    proto_item_append_text(
        tlv_item, " - %s", addr_res);
}

/* ========================================================================= */

static void wimaxasncp_proto_tree_add_tlv_ipv6_value(
    tvbuff_t   *tvb,
    proto_tree *tree,
    proto_item *tlv_item,
    guint       offset,
    const wimaxasncp_dict_tlv_t *tlv_info)
{
    int                hf_value;
    struct e_in6_addr  ip;
    const gchar *addr_res;

    if (tlv_info->hf_ipv4 != -1)
    {
        hf_value = tlv_info->hf_ipv6;
    }
    else
    {
        hf_value = tlv_info->hf_value;
    }

    tvb_get_ipv6(tvb, offset, &ip);
    addr_res = tvb_address_with_resolution_to_str(wmem_packet_scope(), tvb, AT_IPv6, offset);

    proto_tree_add_ipv6_format(
        tree, hf_value,
        tvb, offset, 16, &ip,
        "Value: %s", addr_res);

    proto_item_append_text(
        tlv_item, " - %s", addr_res);
}

/* ========================================================================= */

static void wimaxasncp_proto_tree_add_ether_value(
    tvbuff_t   *tvb,
    proto_tree *tree,
    proto_item *tlv_item,
    guint       offset,
    guint       length,
    const wimaxasncp_dict_tlv_t *tlv_info)
{
    int           hf_value;
    const guint8 *p;
    const gchar  *ether_name;

    if (tlv_info->hf_bsid != -1)
    {
        hf_value = tlv_info->hf_bsid;
    }
    else
    {
        hf_value = tlv_info->hf_value;
    }

    p = tvb_get_ptr(tvb, offset, length);
    ether_name = tvb_address_with_resolution_to_str(wmem_packet_scope(), tvb, AT_ETHER, offset);

    proto_tree_add_ether_format(
        tree, hf_value,
        tvb, offset, length, p,
        "Value: %s",
        ether_name);

    proto_item_append_text(
        tlv_item, " - %s",
        ether_name);
}

/* ========================================================================= */

static void wimaxasncp_dissect_tlv_value(
    tvbuff_t           *tvb,
    packet_info *pinfo  _U_,
    proto_tree         *tree,
    proto_item         *tlv_item,
    const wimaxasncp_dict_tlv_t *tlv_info)
{
    guint        offset          = 0;
    guint        length;
    const guint  max_show_bytes  = 24; /* arbitrary */
    static const gchar *hex_note = "[hex]";

    length = tvb_reported_length(tvb);

    switch (tlv_info->decoder)
    {
    case WIMAXASNCP_TLV_ENUM8:
    {
        if (length != 1)
        {
            /* encoding error */
            break;
        }

        if (tlv_info->enums == NULL)
        {
            if (debug_enabled)
            {
                g_print("fix-me: enum values missing for TLV %s (%u)\n",
                        tlv_info->name, tlv_info->type);
            }
        }

        if (tree)
        {
            guint8       value;
            const gchar *s;

            value = tvb_get_guint8(tvb, offset);

            s = wimaxasncp_get_enum_name(tlv_info, value);

            proto_tree_add_uint_format(
                tree, tlv_info->hf_value,
                tvb, offset, length, value,
                "Value: %s (%u)", s, value);

            proto_item_append_text(tlv_item, " - %s", s);
        }

        return;
    }
    case WIMAXASNCP_TLV_ENUM16:
    {
        if (length != 2)
        {
            /* encoding error */
            break;
        }

        if (tlv_info->enums == NULL)
        {
            if (debug_enabled)
            {
                g_print("fix-me: enum values missing for TLV %s (%u)\n",
                        tlv_info->name, tlv_info->type);
            }
        }

        if (tree)
        {
            guint16      value;
            const gchar *s;

            value = tvb_get_ntohs(tvb, offset);

            s = wimaxasncp_get_enum_name(tlv_info, value);

            proto_tree_add_uint_format(
                tree, tlv_info->hf_value,
                tvb, offset, length, value,
                "Value: %s (%u)", s, value);

            proto_item_append_text(tlv_item, " - %s", s);
        }

        return;
    }
    case WIMAXASNCP_TLV_ENUM32:
    {
        if (length != 4)
        {
            /* encoding error */
            break;
        }

        if (tlv_info->enums == NULL)
        {
            if (debug_enabled)
            {
                g_print("fix-me: enum values missing for TLV %s (%u)\n",
                        tlv_info->name, tlv_info->type);
            }
        }

        if (tree)
        {
            guint32      value;
            const gchar *s;

            value = tvb_get_ntohl(tvb, offset);

            s = wimaxasncp_get_enum_name(tlv_info, value);

            proto_tree_add_uint_format(
                tree, tlv_info->hf_value,
                tvb, offset, length, value,
                "Value: %s (%u)", s, value);

            proto_item_append_text(tlv_item, " - %s", s);
        }

        return;
    }
    case WIMAXASNCP_TLV_ETHER:
    {
        if (length != 6)
        {
            /* encoding error */
            break;
        }

        if (tree)
        {
            wimaxasncp_proto_tree_add_ether_value(
                tvb, tree, tlv_item, offset, length, tlv_info);
        }

        return;
    }
    case WIMAXASNCP_TLV_ASCII_STRING:
    {
        if (tree)
        {
            const gchar  *s = tvb_get_string_enc(wmem_packet_scope(), tvb, offset, length, ENC_ASCII);

            proto_tree_add_string_format(
                tree, tlv_info->hf_value,
                tvb, offset, length, s,
                "Value: %s", s);

            proto_item_append_text(
                tlv_item, " - %s", s);
        }

        return;
    }
    case WIMAXASNCP_TLV_FLAG0:
    {
        if (length != 0)
        {
            /* encoding error */
            break;
        }

        return;
    }
    case WIMAXASNCP_TLV_BITFLAGS8:
    {
        if (length != 1)
        {
            /* encoding error */
            break;
        }

        if (tlv_info->enums == NULL)
        {
            /* enum values missing */
        }

        if (tree)
        {
            proto_tree *flags_tree;
            proto_item *item;
            guint8      value;
            guint       i;

            value = tvb_get_guint8(tvb, offset);

            item = proto_tree_add_item(
                tree, tlv_info->hf_value,
                tvb, offset, 1, ENC_NA);

            proto_item_append_text(tlv_item, " - 0x%02x", value);

            if (value != 0)
            {
                flags_tree = proto_item_add_subtree(
                    item, ett_wimaxasncp_tlv_value_bitflags8);

                for (i = 0; i < 8; ++i)
                {
                    guint8 mask;
                    mask = 1U << (7 - i);

                    if (value & mask)
                    {
                        const gchar *s;

                        s = wimaxasncp_get_enum_name(tlv_info, value & mask);

                        proto_tree_add_uint_format(
                            flags_tree, hf_wimaxasncp_tlv_value_bitflags8,
                            tvb, offset, length, value,
                            "Bit #%u is set: %s", i, s);
                    }
                }
            }
        }

        return;
    }
    case WIMAXASNCP_TLV_BITFLAGS16:
    {
        if (length != 2)
        {
            /* encoding error */
            break;
        }

        if (tlv_info->enums == NULL)
        {
            /* enum values missing */
        }

        if (tree)
        {
            proto_tree *flags_tree;
            proto_item *item;
            guint16     value;
            guint       i;

            value = tvb_get_ntohs(tvb, offset);

            item = proto_tree_add_item(
                tree, tlv_info->hf_value,
                tvb, offset, 2, ENC_BIG_ENDIAN);

            proto_item_append_text(tlv_item, " - 0x%04x", value);

            if (value != 0)
            {
                flags_tree = proto_item_add_subtree(
                    item, ett_wimaxasncp_tlv_value_bitflags16);

                for (i = 0; i < 16; ++i)
                {
                    guint16 mask;
                    mask = 1U << (15 - i);

                    if (value & mask)
                    {
                        const gchar *s;

                        s = wimaxasncp_get_enum_name(tlv_info, value & mask);

                        proto_tree_add_uint_format(
                            flags_tree, hf_wimaxasncp_tlv_value_bitflags16,
                            tvb, offset, length, value,
                            "Bit #%u is set: %s", i, s);
                    }
                }
            }
        }

        return;
    }
    case WIMAXASNCP_TLV_BITFLAGS32:
    {
        if (length != 4)
        {
            /* encoding error */
            break;
        }

        if (tlv_info->enums == NULL)
        {
            /* enum values missing */
        }

        if (tree)
        {
            proto_tree *flags_tree;
            proto_item *item;
            guint32     value;
            guint       i;

            value = tvb_get_ntohl(tvb, offset);

            item = proto_tree_add_item(
                tree, tlv_info->hf_value,
                tvb, offset, 4, ENC_BIG_ENDIAN);

            proto_item_append_text(tlv_item, " - 0x%08x", value);

            if (value != 0)
            {
                flags_tree = proto_item_add_subtree(
                    item, ett_wimaxasncp_tlv_value_bitflags32);

                for (i = 0; i < 32; ++i)
                {
                    guint32 mask;
                    mask = 1U << (31 - i);

                    if (value & mask)
                    {
                        const gchar *s;
                        s = wimaxasncp_get_enum_name(tlv_info, value & mask);

                        proto_tree_add_uint_format(
                            flags_tree, hf_wimaxasncp_tlv_value_bitflags32,
                            tvb, offset, length, value,
                            "Bit #%u is set: %s", i, s);
                    }
                }
            }
        }

        return;
    }
    case WIMAXASNCP_TLV_ID:
    {
        if (length == 4)
        {
            if (tree)
            {
                wimaxasncp_proto_tree_add_tlv_ipv4_value(
                    tvb, tree, tlv_item, offset, tlv_info);
            }

            return;
        }
        else if (length == 6)
        {
            if (tree)
            {
                wimaxasncp_proto_tree_add_ether_value(
                    tvb, tree, tlv_item, offset, length, tlv_info);
            }

            return;
        }
        else if (length == 16)
        {
            if (tree)
            {
                wimaxasncp_proto_tree_add_tlv_ipv6_value(
                    tvb, tree, tlv_item, offset, tlv_info);
            }

            return;
        }
        else
        {
            /* encoding error */
            break;
        }
    }
    case WIMAXASNCP_TLV_BYTES:
    {
        if (tree)
        {
            const gchar  *format1;
            const gchar  *format2;
            const guint8 *p = tvb_get_ptr(tvb, offset, length);
            const gchar  *s = bytestring_to_str(wmem_packet_scope(), p, MIN(length, max_show_bytes), 0);

            if (length <= max_show_bytes)
            {
                format1 = "Value: %s";
                format2 = " - %s";
            }
            else
            {
                format1 = "Value: %s...";
                format2 = " - %s...";
            }

            proto_tree_add_bytes_format(
                tree, tlv_info->hf_value,
                tvb, offset, length, p,
                format1, s);

            proto_item_append_text(
                tlv_item, format2, s);
        }

        return;
    }
    case WIMAXASNCP_TLV_HEX8:
    {
        if (length != 1)
        {
            /* encoding error */
            break;
        }

        if (tree)
        {
            guint8 value;

            value = tvb_get_guint8(tvb, offset);

            proto_tree_add_uint_format(
                tree, tlv_info->hf_value,
                tvb, offset, length, value,
                "Value: 0x%02x", value);

            proto_item_append_text(tlv_item, " - 0x%02x", value);
        }

        return;
    }
    case WIMAXASNCP_TLV_HEX16:
    {
        if (length != 2)
        {
            /* encoding error */
            break;
        }

        if (tree)
        {
            guint16 value;

            value = tvb_get_ntohs(tvb, offset);

            proto_tree_add_uint_format(
                tree, tlv_info->hf_value,
                tvb, offset, length, value,
                "Value: 0x%04x", value);

            proto_item_append_text(tlv_item, " - 0x%04x", value);
        }

        return;
    }
    case WIMAXASNCP_TLV_HEX32:
    {
        if (length != 4)
        {
            /* encoding error */
            break;
        }

        if (tree)
        {
            guint32 value;

            value = tvb_get_ntohl(tvb, offset);

            proto_tree_add_uint_format(
                tree, tlv_info->hf_value,
                tvb, offset, length, value,
                "Value: 0x%08x", value);

            proto_item_append_text(tlv_item, " - 0x%08x", value);
        }

        return;
    }
    case WIMAXASNCP_TLV_DEC8:
    {
        if (length != 1)
        {
            /* encoding error */
            break;
        }

        if (tree)
        {
            guint8 value;

            value = tvb_get_guint8(tvb, offset);

            proto_tree_add_uint_format(
                tree, tlv_info->hf_value,
                tvb, offset, length, value,
                "Value: %u", value);

            proto_item_append_text(tlv_item, " - %u", value);
        }

        return;
    }
    case WIMAXASNCP_TLV_DEC16:
    {
        if (length != 2)
        {
            /* encoding error */
            break;
        }

        if (tree)
        {
            guint16 value;

            value = tvb_get_ntohs(tvb, offset);

            proto_tree_add_uint_format(
                tree, tlv_info->hf_value,
                tvb, offset, length, value,
                "Value: %u", value);

            proto_item_append_text(tlv_item, " - %u", value);
        }

        return;
    }
    case WIMAXASNCP_TLV_DEC32:
    {
        if (length != 4)
        {
            /* encoding error */
            break;
        }

        if (tree)
        {
            guint32 value;

            value = tvb_get_ntohl(tvb, offset);

            proto_tree_add_uint_format(
                tree, tlv_info->hf_value,
                tvb, offset, length, value,
                "Value: %u", value);

            proto_item_append_text(tlv_item, " - %u", value);
        }

        return;
    }
    case WIMAXASNCP_TLV_TBD:
    {
        if (debug_enabled)
        {
            g_print(
                "fix-me: TBD: TLV %s (%u)\n", tlv_info->name, tlv_info->type);
        }

        if (tree)
        {
            const gchar  *format;
            const guint8 *p = tvb_get_ptr(tvb, offset, length);
            const gchar  *s = bytestring_to_str(wmem_packet_scope(), p, MIN(length, max_show_bytes), 0);

            if (length <= max_show_bytes)
            {
                format = "Value: %s %s";
            }
            else
            {
                format = "Value: %s %s...";
            }

            proto_tree_add_bytes_format(
                tree, tlv_info->hf_value,
                tvb, offset, length, p,
                format, hex_note, s);

            proto_item_append_text(tlv_item, " - TBD");
        }

        return;
    }
    case WIMAXASNCP_TLV_IP_ADDRESS:
    {
        if (length == 4)
        {
            if (tree)
            {
                wimaxasncp_proto_tree_add_tlv_ipv4_value(
                    tvb, tree, tlv_item, offset, tlv_info);
            }

            return;
        }
        else if (length == 16)
        {
            if (tree)
            {
                wimaxasncp_proto_tree_add_tlv_ipv6_value(
                    tvb, tree, tlv_item, offset, tlv_info);
            }

            return;
        }
        else
        {
            /* encoding error */
            break;
        }
    }
    case WIMAXASNCP_TLV_IPV4_ADDRESS:
    {
        if (length != 4)
        {
            /* encoding error */
            break;
        }

        if (tree)
        {
            wimaxasncp_proto_tree_add_tlv_ipv4_value(
                tvb, tree, tlv_item, offset, tlv_info);
        }

        return;
    }
    case WIMAXASNCP_TLV_PROTOCOL_LIST:
    {
        if (length % 2 != 0)
        {
            /* encoding error */
            break;
        }

        if (tree && length > 0)
        {
            proto_tree  *protocol_list_tree;
            proto_item  *item;
            const guint  max_protocols_in_tlv_item = 8; /* arbitrary */

            protocol_list_tree = proto_tree_add_subtree(
                tree, tvb, offset, length,
                ett_wimaxasncp_tlv_protocol_list, NULL, "Value");

            /* hidden item for filtering */
            item = proto_tree_add_item(
                protocol_list_tree, tlv_info->hf_value,
                tvb, offset, length, ENC_NA);

            PROTO_ITEM_SET_HIDDEN(item);

            while (offset < tvb_reported_length(tvb))
            {
                guint16      protocol;
                const gchar *protocol_name;

                protocol = tvb_get_ntohs(tvb, offset);
                protocol_name = ipprotostr(protocol);

                proto_tree_add_uint_format(
                    protocol_list_tree, tlv_info->hf_protocol,
                    tvb, offset, 2, protocol,
                    "Protocol: %s (%u)", protocol_name, protocol);

                if (offset == 0)
                {
                    proto_item_append_text(tlv_item, " - %s", protocol_name);
                }
                else if (offset < 2 * max_protocols_in_tlv_item)
                {
                    proto_item_append_text(tlv_item, ", %s", protocol_name);
                }
                else if (offset == 2 * max_protocols_in_tlv_item)
                {
                    proto_item_append_text(tlv_item, ", ...");
                }

                offset += 2;
            }
        }

        return;
    }
    case WIMAXASNCP_TLV_PORT_RANGE_LIST:
    {
        if (length % 4 != 0)
        {
            /* encoding error */
            break;
        }

        if (tree && length > 0)
        {
            proto_tree  *port_range_list_tree;
            proto_item  *item;
            const guint  max_port_ranges_in_tlv_item = 3; /* arbitrary */

            port_range_list_tree = proto_tree_add_subtree(
                tree, tvb, offset, length,
                ett_wimaxasncp_tlv_port_range_list, NULL, "Value");

            /* hidden item for filtering */
            item = proto_tree_add_item(
                port_range_list_tree, tlv_info->hf_value,
                tvb, offset, length, ENC_NA);

            PROTO_ITEM_SET_HIDDEN(item);

            while (offset < tvb_reported_length(tvb))
            {
                guint16 portLow;
                guint16 portHigh;
                proto_tree* range_tree;

                portLow  = tvb_get_ntohs(tvb, offset);
                portHigh = tvb_get_ntohs(tvb, offset + 2);

                range_tree = proto_tree_add_subtree_format(
                    port_range_list_tree, tvb, offset, 4,
                    ett_wimaxasncp_port_range, NULL, "Port Range: %u-%u", portLow, portHigh);

                /* hidden items are for filtering */

                item = proto_tree_add_item(
                    range_tree, tlv_info->hf_port_low,
                    tvb, offset, 2, ENC_BIG_ENDIAN);

                PROTO_ITEM_SET_HIDDEN(item);

                item = proto_tree_add_item(
                    range_tree, tlv_info->hf_port_high,
                    tvb, offset + 2, 2, ENC_BIG_ENDIAN);

                PROTO_ITEM_SET_HIDDEN(item);

                if (offset == 0)
                {
                    proto_item_append_text(
                        tlv_item, " - %u-%u", portLow, portHigh);
                }
                else if (offset < 4 * max_port_ranges_in_tlv_item)
                {
                    proto_item_append_text(
                        tlv_item, ", %u-%u", portLow, portHigh);
                }
                else if (offset == 4 * max_port_ranges_in_tlv_item)
                {
                    proto_item_append_text(tlv_item, ", ...");
                }

                offset += 4;
            }
        }

        return;
    }
    case WIMAXASNCP_TLV_IP_ADDRESS_MASK_LIST:
    {
        /* --------------------------------------------------------------------
         * The definion of these TLVs are ambiguous. The length in octets is
         * described as Nx8 (IPv4) or Nx32 (IPv6), but this function cannot
         * always differentiate between IPv4 and IPv6. For example, if length
         * = 32, then is it IPv4 where N=4 (4x8) or IPv6 where N=1 (1x32)?
         *
         * For now, we presume lengths that *can* indicate an IPv6 address and
         * mask list *do* denote an IPv6 address and mask list.
         * --------------------------------------------------------------------
         */

        if (length % 8 != 0)
        {
            /* encoding error */
            break;
        }

        if (tree && length > 0)
        {
            proto_tree *ip_address_mask_list_tree;
            proto_item *item;

            ip_address_mask_list_tree = proto_tree_add_subtree(
                tree, tvb, offset, length,
                ett_wimaxasncp_tlv_ip_address_mask_list, NULL, "Value");

            /* hidden item for filtering */
            item = proto_tree_add_item(
                ip_address_mask_list_tree, tlv_info->hf_value,
                tvb, offset, length, ENC_NA);

            PROTO_ITEM_SET_HIDDEN(item);

            if (length % 32 == 0)
            {
                /* ------------------------------------------------------------
                 * presume IPv6
                 * ------------------------------------------------------------
                 */

                while (offset < tvb_reported_length(tvb))
                {
                    proto_tree        *ip_address_mask_tree;

                    ip_address_mask_tree = proto_tree_add_subtree(
                        ip_address_mask_list_tree, tvb, offset, 32,
                        ett_wimaxasncp_tlv_ip_address_mask, NULL, "IPv6 Address and Mask");

                    /* --------------------------------------------------------
                     * address
                     * --------------------------------------------------------
                     */

                    proto_tree_add_item(
                        ip_address_mask_tree,
                        tlv_info->hf_ipv6,
                        tvb, offset, 16, ENC_NA);

                    /* too long to display ?
                    proto_item_append_text(
                        item, " - %s (%s)",
                        get_hostname6(&ip), ip6_to_str(&ip));
                    */

                    offset += 16;

                    /* --------------------------------------------------------
                     * mask
                     * --------------------------------------------------------
                     */
                    proto_tree_add_item(
                        ip_address_mask_tree,
                        tlv_info->hf_ipv6_mask,
                        tvb, offset, 16, ENC_NA);

                    /* too long to display ?
                    proto_item_append_text(
                        item, " / %s", s);
                    */

                    offset += 16;
                }
            }
            else
            {
                /* ------------------------------------------------------------
                 * IPv4
                 * ------------------------------------------------------------
                 */

                while (offset < tvb_reported_length(tvb))
                {
                    proto_tree  *ip_address_mask_tree;
                    guint32      ip;
                    const gchar *s;

                    ip_address_mask_tree = proto_tree_add_subtree(
                        ip_address_mask_list_tree, tvb, offset, 8,
                        ett_wimaxasncp_tlv_ip_address_mask, NULL, "IPv4 Address and Mask");

                    /* --------------------------------------------------------
                     * address
                     * --------------------------------------------------------
                     */

                    ip = tvb_get_ipv4(tvb, offset);

                    proto_tree_add_item(
                        ip_address_mask_tree,
                        tlv_info->hf_ipv4,
                        tvb, offset, 4, ENC_BIG_ENDIAN);

                    proto_item_append_text(
                        item, " - %s (%s)",
                        get_hostname(ip), tvb_ip_to_str(tvb, offset));

                    offset += 4;

                    /* --------------------------------------------------------
                     * mask
                     * --------------------------------------------------------
                     */

                    s = tvb_ip_to_str(tvb, offset);

                    proto_tree_add_item(
                        ip_address_mask_tree,
                        tlv_info->hf_ipv4_mask,
                        tvb, offset, 4, ENC_BIG_ENDIAN);

                    proto_item_append_text(
                        item, " / %s", s);

                    offset += 4;
                }
            }
        }

        return;
    }
    case WIMAXASNCP_TLV_EAP:
    {
        /*
         *   EAP payload, call eap dissector to dissect eap payload
         */
        guint8 eap_code;
        guint8 eap_type = 0;

        /* Get code */
        eap_code = tvb_get_guint8(tvb, offset);
        if (eap_code == EAP_REQUEST || eap_code == EAP_RESPONSE)
        {
            /* Get type */
            eap_type = tvb_get_guint8(tvb, offset + 4);
        }

        /* Add code and type to info column */
        col_append_str(pinfo->cinfo, COL_INFO, " [");
        col_append_str(pinfo->cinfo, COL_INFO,
                        val_to_str(eap_code, eap_code_vals, "Unknown code (0x%02X)"));

        if (eap_code == EAP_REQUEST || eap_code == EAP_RESPONSE)
        {
            col_append_str(pinfo->cinfo, COL_INFO, ", ");
            col_append_str(pinfo->cinfo, COL_INFO,
                            val_to_str_ext(eap_type, &eap_type_vals_ext, "Unknown type (0x%02X)"));
        }

        col_append_str(pinfo->cinfo, COL_INFO, "]");


        {
            proto_tree *eap_tree;
            proto_item *item;
            gboolean save_writable;
            tvbuff_t *eap_tvb;

            /* Create EAP subtree */
            item = proto_tree_add_item(tree, tlv_info->hf_value, tvb,
                                       offset, length, ENC_NA);
            proto_item_set_text(item, "Value");
            eap_tree = proto_item_add_subtree(item, ett_wimaxasncp_tlv_eap);

            /* Also show high-level details in this root item */
            proto_item_append_text(item, " (%s",
                                   val_to_str(eap_code, eap_code_vals,
                                              "Unknown code (0x%02X)"));
            if (eap_code == EAP_REQUEST || eap_code == EAP_RESPONSE)
            {
                proto_item_append_text(item, ", %s",
                                       val_to_str_ext(eap_type, &eap_type_vals_ext,
                                       "Unknown type (0x%02X)"));
            }
            proto_item_append_text(item, ")");


            /* Extract remaining bytes into new tvb */
            eap_tvb = tvb_new_subset_remaining(tvb, offset);

            /* Disable writing to info column while calling eap dissector */
            save_writable = col_get_writable(pinfo->cinfo, -1);
            col_set_writable(pinfo->cinfo, -1, FALSE);

            /* Call the EAP dissector. */
            call_dissector(eap_handle, eap_tvb, pinfo, eap_tree);

            /* Restore previous writable state of info column */
            col_set_writable(pinfo->cinfo, -1, save_writable);
        }

        return;
    }

    case WIMAXASNCP_TLV_VENDOR_SPECIFIC:
    {
        /* --------------------------------------------------------------------
         *  The format of the vendor specific information field (VSIF) is not
         *  clearly defined.  It appears to be compound as the spec states
         *  that the vendor ID field shall be the first TLV embedded inside
         *  the VSIF.  However, the vendor ID is shown as a 24-bit value. Does
         *  this mean the field is 24-bits?  If so, how is alignment/padding
         *  handled?
         *
         * For now, we decode the vendor ID as a non-padded 24-bit value and
         * dump the rest as hex.
         * --------------------------------------------------------------------
         */

        if (length < 3)
        {
            /* encoding error */
            break;
        }

        if (tree)
        {
            proto_tree *vsif_tree;
            proto_item *item;
            guint32 vendorId;
            const gchar *vendorName;

            vsif_tree = proto_tree_add_subtree(
                tree, tvb, offset, length,
                ett_wimaxasncp_tlv_vendor_specific_information_field, NULL, "Value");

            /* hidden item for filtering */
            item = proto_tree_add_item(
                vsif_tree, tlv_info->hf_value,
                tvb, offset, length, ENC_NA);

            PROTO_ITEM_SET_HIDDEN(item);

            /* ----------------------------------------------------------------
             * vendor ID (24-bit)
             * ----------------------------------------------------------------
             */

            vendorId = tvb_get_ntoh24(tvb, offset);

            vendorName = val_to_str_ext_const(vendorId, &sminmpec_values_ext, "Unknown");
            proto_tree_add_uint_format(
                vsif_tree, tlv_info->hf_vendor_id,
                tvb, offset, 3, vendorId,
                "Vendor ID: %s (%u)", vendorName, vendorId);

            proto_item_append_text(tlv_item, " - %s", vendorName);

            offset += 3;

            /* ----------------------------------------------------------------
             * hex dump the rest
             * ----------------------------------------------------------------
             */

            if (offset < tvb_reported_length(tvb))
            {
                proto_tree_add_item(
                    vsif_tree, tlv_info->hf_vendor_rest_of_info,
                    tvb, offset, length - offset, ENC_NA);
            }
        }

        return;
    }
    case WIMAXASNCP_TLV_UNKNOWN:
    {
        if (tree)
        {
            const gchar *format1;
            const gchar *format2;
            const guint8 *p = tvb_get_ptr(tvb, offset, length);
            const gchar *s =
                bytestring_to_str(wmem_packet_scope(), p, MIN(length, max_show_bytes), 0);

            if (length <= max_show_bytes)
            {
                format1 = "Value: %s %s";
                format2 = " - %s %s";
            }
            else
            {
                format1 = "Value: %s %s...";
                format2 = " - %s %s...";
            }

            proto_tree_add_bytes_format(
                tree, tlv_info->hf_value,
                tvb, offset, length, p,
                format1, hex_note, s);

            proto_item_append_text(
                tlv_item, format2, hex_note, s);

        }

        return;
    }
    default:
        if (debug_enabled)
        {
            g_print(
                "fix-me: unknown decoder: %d\n", tlv_info->decoder);
        }
        break;
    }

    /* default is hex dump */

    if (tree)
    {
        const gchar *format;
        const guint8 *p = tvb_get_ptr(tvb, offset, length);
        const gchar *s = bytestring_to_str(wmem_packet_scope(), p, MIN(length, max_show_bytes), 0);

        if (length <= max_show_bytes)
        {
            format = "Value: %s %s";
        }
        else
        {
            format = "Value: %s %s...";
        }

        proto_tree_add_bytes_format(
            tree, hf_wimaxasncp_tlv_value_bytes,
            tvb, offset, length, p,
            format, hex_note, s);
    }
}

/* ========================================================================= */

static guint dissect_wimaxasncp_tlvs(
    tvbuff_t    *tvb,
    packet_info *pinfo,
    proto_tree  *tree)
{
    guint offset;

    offset = 0;
    while (offset < tvb_reported_length(tvb))
    {
        const wimaxasncp_dict_tlv_t *tlv_info;

        proto_tree *tlv_tree;
        proto_item *tlv_item;
        guint16     type;
        guint16     length;
        guint       pad;

        /* --------------------------------------------------------------------
         * type and length
         * --------------------------------------------------------------------
         */

        type = tvb_get_ntohs(tvb, offset);
        tlv_info = wimaxasncp_get_tlv_info(type);

        length = tvb_get_ntohs(tvb, offset + 2);
#if 0   /* Commented out padding; As there is no mention of padding in
           the Latest specification */
        pad = 4 - (length % 4);
        if (pad == 4)
        {
            pad = 0;
        }
#endif
        pad = 0;
        {
            proto_item *type_item;

            gint tree_length = MIN(
                (gint)(4 + length + pad), tvb_captured_length_remaining(tvb, offset));

            tlv_item = proto_tree_add_item(
                tree, tlv_info->hf_root,
                tvb, offset, tree_length, ENC_NA);

            /* Set label for tlv item */
            proto_item_set_text(tlv_item, "TLV: %s", tlv_info->name);

            /* Show code number if unknown */
            if (tlv_info->decoder == WIMAXASNCP_TLV_UNKNOWN)
            {
                proto_item_append_text(tlv_item, " (%u)", type);
            }

            /* Indicate if a compound tlv */
            if (tlv_info->decoder == WIMAXASNCP_TLV_COMPOUND)
            {
                proto_item_append_text(tlv_item, " [Compound]");
            }

            /* Create TLV subtree */
            tlv_tree = proto_item_add_subtree(
                tlv_item, ett_wimaxasncp_tlv);

            /* Type (expert item if unknown) */
            type_item = proto_tree_add_uint_format(
                tlv_tree, hf_wimaxasncp_tlv_type,
                tvb, offset, 2, type,
                "Type: %s (%u)", tlv_info->name, type);

            if (tlv_info->decoder == WIMAXASNCP_TLV_UNKNOWN)
            {
                expert_add_info_format(pinfo, type_item, &ei_wimaxasncp_tlv_type,
                                       "Unknown TLV type (%u)",
                                       type);
            }

            /* Length */
            proto_tree_add_uint(
                tlv_tree, hf_wimaxasncp_tlv_length,
                tvb, offset + 2, 2, length);

        }

        offset += 4;

        /* --------------------------------------------------------------------
         * value
         * --------------------------------------------------------------------
         */

        if (tlv_info->decoder == WIMAXASNCP_TLV_COMPOUND)
        {
            if (length == 0)
            {
                /* error? compound, but no TLVs inside */
            }
            else if (tvb_reported_length_remaining(tvb, offset) > 0)
            {
                tvbuff_t *tlv_tvb;

                /* N.B.  Not padding out tvb length */
                tlv_tvb = tvb_new_subset(
                    tvb, offset,
                    MIN(length, tvb_captured_length_remaining(tvb, offset)),
                    length);

                /* N.B.  This is a recursive call... */
                dissect_wimaxasncp_tlvs(tlv_tvb, pinfo, tlv_tree);
            }
            else
            {
                /* this should throw */
                tvb_ensure_bytes_exist(tvb, offset, length + pad);
            }
        }
        else
        {
            tvbuff_t *tlv_tvb;

            tvb_ensure_bytes_exist(tvb, offset, length + pad);

            tlv_tvb = tvb_new_subset(
                tvb, offset,
                MIN(length, tvb_captured_length_remaining(tvb, offset)),
                length);

            wimaxasncp_dissect_tlv_value(
                tlv_tvb, pinfo, tlv_tree, tlv_item, tlv_info);
        }

        offset += length + pad;
    }

    return offset;
}

/* ========================================================================= */

static guint dissect_wimaxasncp_backend(
    tvbuff_t    *tvb,
    packet_info *pinfo,
    proto_tree  *tree)
{
    guint     offset = 0;
    guint16   ui16;
    guint32   ui32;
    const guint8 *pmsid;
    guint16   tid    = 0;
    gboolean  dbit_show;


    /* ------------------------------------------------------------------------
     * MSID
     * ------------------------------------------------------------------------
     */

    if (tree)
    {
        proto_tree_add_item(
            tree, hf_wimaxasncp_msid,
            tvb, offset, 6, ENC_NA);
    }
    pmsid = tvb_ether_to_str(tvb, offset);

    offset += 6;

    /* ------------------------------------------------------------------------
     * reserved
     * ------------------------------------------------------------------------
     */

    ui32 = tvb_get_ntohl(tvb, offset);

    if (tree)
    {
        proto_tree_add_uint(
            tree, hf_wimaxasncp_reserved1,
            tvb, offset, 4, ui32);
    }

    offset += 4;

    /* ------------------------------------------------------------------------
     * transaction ID
     * ------------------------------------------------------------------------
     */

    dbit_show = FALSE;
    ui16 = tvb_get_ntohs(tvb, offset);

    if (show_transaction_id_d_bit)
    {
        const guint16 mask = 0x7fff;

        if (ui16 & 0x8000)
        {
            proto_tree_add_uint_format(
                tree, hf_wimaxasncp_transaction_id,
                tvb, offset, 2, ui16,
                "Transaction ID: D + 0x%04x (0x%04x)", mask & ui16, ui16);

            tid = ui16 & mask;
            dbit_show = TRUE;
        }
        else
        {
            proto_tree_add_uint_format(
                tree, hf_wimaxasncp_transaction_id,
                tvb, offset, 2, ui16,
                "Transaction ID: 0x%04x", ui16);

            tid = ui16;
        }
    }
    else
    {
        proto_tree_add_uint(
            tree, hf_wimaxasncp_transaction_id,
            tvb, offset, 2, ui16);

        tid = ui16;
    }

    offset += 2;

    /* ------------------------------------------------------------------------
     * reserved
     * ------------------------------------------------------------------------
     */

    ui16 = tvb_get_ntohs(tvb, offset);

    if (tree)
    {
        proto_tree_add_uint(
            tree, hf_wimaxasncp_reserved2,
            tvb, offset, 2, ui16);
    }

    offset += 2;

    /* ------------------------------------------------------------------------
     * TLVs
     * ------------------------------------------------------------------------
     */

    if (tvb_reported_length_remaining(tvb, offset) > 0)
    {
        tvbuff_t *tlv_tvb;

        tlv_tvb = tvb_new_subset_remaining(tvb, offset);

        offset += dissect_wimaxasncp_tlvs(tlv_tvb, pinfo, tree);
    }

    col_append_fstr(pinfo->cinfo, COL_INFO, " - MSID:%s", pmsid);
    if (dbit_show)
    {
        col_append_fstr(pinfo->cinfo, COL_INFO, ", TID:D+0x%04x", tid);
    }
    else
    {
        col_append_fstr(pinfo->cinfo, COL_INFO, ", TID:0x%04x", tid);
    }

    return offset;
}

/* ========================================================================= */


static const gchar*
match_ver_value_string(
    const guint32 val,
    const ver_value_string* const strings,
    const guint32 max_ver)
{
    const ver_value_string* vvs;
    const ver_value_string* res = NULL;

    /* loop on the levels, from max to 0 */
    for(vvs=strings; vvs->vs.strptr; vvs++)
    {
        if ((vvs->vs.value == val) && (vvs->since <= max_ver))
        {
            if (!res || (vvs->since > res->since))
            {
                res = vvs;
            }
        }
    }

    return res? res->vs.strptr : NULL;
}

static int
dissect_wimaxasncp(
    tvbuff_t    *tvb,
    packet_info *pinfo,
    proto_tree  *tree,
    void *data   _U_)
{
    static const gchar unknown[] = "Unknown";

    /* Set up structures needed to add the protocol subtree and manage it */
    proto_item *packet_item     = NULL;
    proto_item *item            = NULL;
    proto_tree *wimaxasncp_tree = NULL;
    tvbuff_t   *subtree;

    guint  offset;
    guint8 ui8;

    guint8       function_type;
    const gchar *function_type_name;
    proto_item  *function_type_item;
    guint16      length;

    const wimaxasncp_func_msg_t *p = NULL;
    const gchar *message_name;
    gsize        i;

    /* ------------------------------------------------------------------------
     * First, we do some heuristics to check if the packet cannot be our
     * protocol.
     * ------------------------------------------------------------------------
     */

    /* Should we check a minimum size?  If so, uncomment out the following
     * code. */
#if 0
    if (tvb_reported_length(tvb) < WIMAXASNCP_HEADER_SIZE)
    {
        return 0;
    }
#endif

    /* We currently only support version 1. */
    if (tvb_bytes_exist(tvb, 0, 1) && tvb_get_guint8(tvb, 0) != 1)
    {
        return 0;
    }

    /* ------------------------------------------------------------------------
     * Initialize the protocol and info column.
     * ------------------------------------------------------------------------
     */

    /* Make entries in Protocol column and Info column on summary display */
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "WiMAX");

    /* We'll fill in the "Info" column after fetch data, so we clear the
       column first in case calls to fetch data from the packet throw an
       exception. */
    col_clear(pinfo->cinfo, COL_INFO);

    /* ========================================================================
     * Disesction starts here
     * ========================================================================
     */

    /* ------------------------------------------------------------------------
     * total packet, we'll adjust after we read the length field
     * ------------------------------------------------------------------------
     */

    offset = 0;

    /* Register protocol fields, etc if haven't done yet. */
    if (hf_wimaxasncp_version == -1)
    {
        proto_registrar_get_byname("wimaxasncp.version");
    }

    if (tree)
    {
        packet_item = proto_tree_add_item(
            tree, proto_wimaxasncp,
            tvb, 0, MIN(WIMAXASNCP_HEADER_LENGTH_END, tvb_captured_length(tvb)), ENC_NA);

        wimaxasncp_tree = proto_item_add_subtree(
            packet_item, ett_wimaxasncp);
    }

    /* ------------------------------------------------------------------------
     * version
     * ------------------------------------------------------------------------
     */

    if (tree)
    {
        proto_tree_add_item(
            wimaxasncp_tree, hf_wimaxasncp_version,
            tvb, offset, 1, ENC_BIG_ENDIAN);
    }

    offset += 1;

    /* ------------------------------------------------------------------------
     * flags
     * ------------------------------------------------------------------------
     */

    ui8 = tvb_get_guint8(tvb, offset);

    if (tree)
    {
        proto_tree *flags_tree;

        if (ui8 == 0)
        {
            proto_tree_add_uint_format(
                wimaxasncp_tree, hf_wimaxasncp_flags,
                tvb, offset, 1, ui8,
                "Flags: 0x%02x", ui8);
        }
        else
        {
            guint j;
            item = proto_tree_add_uint_format(
                wimaxasncp_tree, hf_wimaxasncp_flags,
                tvb, offset, 1, ui8,
                "Flags: ");

            if (ui8 & (WIMAXASNCP_FLAGS_T | WIMAXASNCP_FLAGS_R))
            {
                if (ui8 & WIMAXASNCP_FLAGS_T)
                {
                    proto_item_append_text(item, "T");
                }

                if (ui8 & WIMAXASNCP_FLAGS_R)
                {
                    proto_item_append_text(item, "R");
                }

                proto_item_append_text(item, " - ");
            }

            proto_item_append_text(item, "0x%02x", ui8);

            flags_tree = proto_item_add_subtree(
                item, ett_wimaxasncp_flags);

            for (j = 0; j < 8; ++j)
            {
                guint8 mask;
                mask = 1U << (7 - j);

                /* Only add flags that are set */
                if (ui8 & mask)
                {
                    proto_tree_add_uint_format(
                        flags_tree, hf_wimaxasncp_flags,
                        tvb, offset, 1, ui8,
                        "Bit #%u is set: %s",
                        j,
                        val_to_str(
                            ui8 & mask, wimaxasncp_flag_vals, "Unknown"));
                }
            }
        }
    }

    offset += 1;

    /* ------------------------------------------------------------------------
     * function type
     * ------------------------------------------------------------------------
     */

    function_type = tvb_get_guint8(tvb, offset);

    function_type_name = match_ver_value_string(function_type,
        wimaxasncp_function_type_vals,
        global_wimaxasncp_nwg_ver);

    if (function_type_name)
    {
        /* add the item to the tree */
        proto_tree_add_uint_format(
            wimaxasncp_tree, hf_wimaxasncp_function_type,
            tvb, offset, 1, function_type,
            "%s (%u)", function_type_name, function_type);
    }
    else
    {
        /* if not matched, add the item and append expert item  */
        function_type_item = proto_tree_add_uint_format(
            wimaxasncp_tree, hf_wimaxasncp_function_type,
            tvb, offset, 1, function_type,
            "Unknown (%u)", function_type);

        expert_add_info_format(pinfo, function_type_item,
                               &ei_wimaxasncp_function_type,
                               "Unknown function type (%u)",
                               function_type);
    }

    offset += 1;

    /* ------------------------------------------------------------------------
     * OP ID and message type
     * ------------------------------------------------------------------------
     */

    ui8 = tvb_get_guint8(tvb, offset);


    /* --------------------------------------------------------------------
     * OP ID
     * --------------------------------------------------------------------
     */

    item = proto_tree_add_uint_format(
        wimaxasncp_tree, hf_wimaxasncp_op_id,
         tvb, offset, 1, ui8,
        "OP ID: %s", val_to_str(ui8 >> 5, wimaxasncp_op_id_vals, unknown));

    proto_item_append_text(item, " (%u)", ((ui8 >> 5) & 7));


    /* use the function type to find the message vals */
    for (i = 0; i < array_length(wimaxasncp_func_to_msg_vals_map); ++i)
    {
        p = &wimaxasncp_func_to_msg_vals_map[i];

        if (function_type == p->function_type)
        {
            break;
        }
    }

    /* --------------------------------------------------------------------
     * message type
     * --------------------------------------------------------------------
     */

    message_name = p ? match_ver_value_string(0x1f & ui8, p->vals, global_wimaxasncp_nwg_ver) : unknown;
    if (message_name == NULL)
    {
        message_name = unknown;
    }

    item = proto_tree_add_uint_format(
        wimaxasncp_tree, hf_wimaxasncp_op_id,
        tvb, offset, 1, ui8,
        "Message Type: %s", message_name);

    proto_item_append_text(item, " (%u)", ui8 & 0x1F);

    /* Add expert item if not matched */
    if (strcmp(message_name, unknown) == 0)
    {
        expert_add_info_format(pinfo, item, &ei_wimaxasncp_op_id,
                               "Unknown message op (%u)",
                               0x1f & ui8);
    }

    col_add_str(pinfo->cinfo, COL_INFO, message_name);

    offset += 1;

    /* ------------------------------------------------------------------------
     * length
     * ------------------------------------------------------------------------
     */

    length = tvb_get_ntohs(tvb, offset);

    if (tree)
    {
        proto_item_set_len(
            packet_item, MAX(WIMAXASNCP_HEADER_LENGTH_END, length));

        item = proto_tree_add_uint(
            wimaxasncp_tree, hf_wimaxasncp_length,
            tvb, offset, 2, length);
    }

    offset += 2;

    if (length < WIMAXASNCP_HEADER_SIZE)
    {
        expert_add_info(pinfo, item, &ei_wimaxasncp_length_bad);

        if (tree)
        {
            proto_item_append_text(
                item, " [error: specified length less than header size (20)]");
        }

        if (length <= WIMAXASNCP_HEADER_LENGTH_END)
        {
            return offset;
        }
    }

    /* ------------------------------------------------------------------------
     * remaining header fields and TLVs
     * ------------------------------------------------------------------------
     */

    subtree = tvb_new_subset(
        tvb, offset,
        MIN(length, tvb_captured_length_remaining(tvb, offset)),
        length - WIMAXASNCP_HEADER_LENGTH_END);

    offset += dissect_wimaxasncp_backend(
        subtree, pinfo, wimaxasncp_tree);

    /* ------------------------------------------------------------------------
     * done, return the amount of data this dissector was able to dissect
     * ------------------------------------------------------------------------
     */

    return offset;
}

/* ========================================================================= */
/* Modify the given string to make a suitable display filter                 */
static char *alnumerize(
    char *name)
{
    char *r = name;  /* read pointer */
    char *w = name;  /* write pointer */
    char  c;

    for ( ; (c = *r); ++r)
    {
        if (g_ascii_isalnum(c) || c == '_' || c == '.')
        {
            /* These characters are fine - copy them */
            *(w++) = c;
        }
        else if (c == ' ' || c == '-' || c == '/')
        {
            /* Skip these others if haven't written any characters out yet */
            if (w == name)
            {
                continue;
            }

            /* Skip if we would produce multiple adjacent '_'s */
            if (*(w - 1) == '_')
            {
                continue;
            }

            /* OK, replace with underscore */
            *(w++) = '_';
        }

        /* Other undesirable characters are just skipped */
    }

    /* Terminate and return modified string */
    *w = '\0';
    return name;
}

/* ========================================================================= */

static void add_reg_info(
    int         *hf_ptr,
    const char  *name,
    const char  *abbrev,
    enum ftenum  type,
    int          display,
    const char  *blurb)
{
    hf_register_info hf = {
        hf_ptr, { name, abbrev, type, display, NULL, 0x0, blurb, HFILL } };

    g_array_append_val(wimaxasncp_build_dict.hf, hf);
}

/* ========================================================================= */

static void add_tlv_reg_info(
    wimaxasncp_dict_tlv_t *tlv)
{
    char *name;
    char *abbrev;
    const char *root_blurb;
    char *blurb;

    /* ------------------------------------------------------------------------
     * add root reg info
     * ------------------------------------------------------------------------
     */

    name = g_strdup(tlv->name);
    abbrev = alnumerize(g_strdup_printf("wimaxasncp.tlv.%s", tlv->name));

    switch (tlv->decoder)
    {
    case WIMAXASNCP_TLV_UNKNOWN:
        root_blurb = "type=Unknown";
        break;
    case WIMAXASNCP_TLV_TBD:
        root_blurb = g_strdup_printf("type=%u, TBD", tlv->type);
        break;
    case WIMAXASNCP_TLV_COMPOUND:
        root_blurb = g_strdup_printf("type=%u, Compound", tlv->type);
        break;
    case WIMAXASNCP_TLV_FLAG0:
        root_blurb = g_strdup_printf("type=%u, Value = Null", tlv->type);
        break;
    default:
        root_blurb = g_strdup_printf("type=%u", tlv->type);
        break;
    }

    add_reg_info(
        &tlv->hf_root, name, abbrev, FT_BYTES, BASE_NONE, root_blurb);

    /* ------------------------------------------------------------------------
     * add value(s) reg info
     * ------------------------------------------------------------------------
     */

    name = g_strdup("Value");
    abbrev = alnumerize(g_strdup_printf("wimaxasncp.tlv.%s.value", tlv->name));
    blurb = g_strdup_printf("value for type=%u", tlv->type);

    switch (tlv->decoder)
    {
    case WIMAXASNCP_TLV_UNKNOWN:
        g_free(blurb);

        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_BYTES, BASE_NONE,
            "value for unknown type");
        break;

    case WIMAXASNCP_TLV_TBD:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_BYTES, BASE_NONE, blurb);
        break;

    case WIMAXASNCP_TLV_COMPOUND:
    case WIMAXASNCP_TLV_FLAG0:
        g_free(name);
        g_free(abbrev);
        g_free(blurb);
        break;

    case WIMAXASNCP_TLV_BYTES:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_BYTES, BASE_NONE, blurb);
        break;

    case WIMAXASNCP_TLV_ENUM8:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_UINT8, BASE_DEC, blurb);
        break;

    case WIMAXASNCP_TLV_ENUM16:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_UINT16, BASE_DEC, blurb);
        break;

    case WIMAXASNCP_TLV_ENUM32:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_UINT32, BASE_DEC, blurb);
        break;

    case WIMAXASNCP_TLV_ETHER:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_ETHER, BASE_NONE, blurb);
        break;

    case WIMAXASNCP_TLV_ASCII_STRING:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_STRING, BASE_NONE, blurb);
        break;

    case WIMAXASNCP_TLV_BITFLAGS8:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_UINT8, BASE_HEX, blurb);
        break;

    case WIMAXASNCP_TLV_BITFLAGS16:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_UINT16, BASE_HEX, blurb);
        break;

    case WIMAXASNCP_TLV_BITFLAGS32:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_UINT32, BASE_HEX, blurb);
        break;

    case WIMAXASNCP_TLV_ID:
        g_free(abbrev);

        abbrev = alnumerize(
            g_strdup_printf("wimaxasncp.tlv.%s.ipv4_value", tlv->name));

        add_reg_info(
            &tlv->hf_ipv4, "IPv4 Address", abbrev, FT_IPv4, BASE_NONE, blurb);

        abbrev = alnumerize(
            g_strdup_printf("wimaxasncp.tlv.%s.ipv6_value", tlv->name));

        add_reg_info(
            &tlv->hf_ipv6, "IPv6 Address", abbrev, FT_IPv6, BASE_NONE, blurb);

        abbrev = alnumerize(
            g_strdup_printf("wimaxasncp.tlv.%s.bsid_value", tlv->name));

        add_reg_info(
            &tlv->hf_bsid, "BS ID", abbrev, FT_ETHER, BASE_NONE, blurb);

        break;

    case WIMAXASNCP_TLV_HEX8:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_UINT8, BASE_HEX, blurb);
        break;

    case WIMAXASNCP_TLV_HEX16:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_UINT16, BASE_HEX, blurb);
        break;

    case WIMAXASNCP_TLV_HEX32:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_UINT32, BASE_HEX, blurb);
        break;

    case WIMAXASNCP_TLV_DEC8:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_UINT8, BASE_DEC, blurb);
        break;

    case WIMAXASNCP_TLV_DEC16:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_UINT16, BASE_DEC, blurb);
        break;

    case WIMAXASNCP_TLV_DEC32:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_UINT32, BASE_DEC, blurb);
        break;

    case WIMAXASNCP_TLV_IP_ADDRESS:
        g_free(abbrev);

        abbrev = alnumerize(
            g_strdup_printf("wimaxasncp.tlv.%s.ipv4_value", tlv->name));

        add_reg_info(
            &tlv->hf_ipv4, "IPv4 Address", abbrev, FT_IPv4, BASE_NONE, blurb);

        abbrev = alnumerize(
            g_strdup_printf("wimaxasncp.tlv.%s.ipv6_value", tlv->name));

        add_reg_info(
            &tlv->hf_ipv6, "IPv6 Address", abbrev, FT_IPv6, BASE_NONE, blurb);

        break;

    case WIMAXASNCP_TLV_IPV4_ADDRESS:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_IPv4, BASE_NONE, blurb);
        break;

    case WIMAXASNCP_TLV_PROTOCOL_LIST:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_BYTES, BASE_NONE, blurb);

        blurb = g_strdup_printf("value component for type=%u", tlv->type);

        abbrev = alnumerize(
            g_strdup_printf("wimaxasncp.tlv.%s.value.protocol", tlv->name));

        add_reg_info(
            &tlv->hf_protocol, "Protocol", abbrev, FT_UINT16, BASE_DEC, blurb);

        break;

    case WIMAXASNCP_TLV_PORT_RANGE_LIST:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_BYTES, BASE_NONE, blurb);

        blurb = g_strdup_printf("value component for type=%u", tlv->type);

        abbrev = alnumerize(
            g_strdup_printf("wimaxasncp.tlv.%s.value.port_low", tlv->name));

        add_reg_info(
            &tlv->hf_port_low, "Port Low", abbrev, FT_UINT16, BASE_DEC, blurb);

        abbrev = alnumerize(
            g_strdup_printf("wimaxasncp.tlv.%s.value.port_high", tlv->name));

        add_reg_info(
            &tlv->hf_port_high, "Port High", abbrev, FT_UINT16, BASE_DEC, blurb);

        break;

    case WIMAXASNCP_TLV_IP_ADDRESS_MASK_LIST:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_BYTES, BASE_NONE, blurb);

        blurb = g_strdup_printf("value component for type=%u", tlv->type);

        abbrev = alnumerize(
            g_strdup_printf("wimaxasncp.tlv.%s.value.ipv4", tlv->name));

        add_reg_info(
            &tlv->hf_ipv4, "IPv4 Address", abbrev, FT_IPv4, BASE_NONE, blurb);

        abbrev = alnumerize(
            g_strdup_printf("wimaxasncp.tlv.%s.value.ipv4_mask", tlv->name));

        add_reg_info(
            &tlv->hf_ipv4_mask, "IPv4 Mask", abbrev, FT_IPv4, BASE_NONE, blurb);

        abbrev = alnumerize(
            g_strdup_printf("wimaxasncp.tlv.%s.value.ipv6", tlv->name));

        add_reg_info(
            &tlv->hf_ipv6, "IPv6 Address", abbrev, FT_IPv6, BASE_NONE, blurb);

        abbrev = alnumerize(
            g_strdup_printf("wimaxasncp.tlv.%s.value.ipv6_mask", tlv->name));

        add_reg_info(
            &tlv->hf_ipv6_mask, "IPv6 Mask", abbrev, FT_IPv6, BASE_NONE, blurb);

        break;

    case WIMAXASNCP_TLV_VENDOR_SPECIFIC:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_BYTES, BASE_NONE, blurb);

        blurb = g_strdup_printf("value component for type=%u", tlv->type);

        abbrev = alnumerize(
            g_strdup_printf("wimaxasncp.tlv.%s.value.vendor_id", tlv->name));

        add_reg_info(
            &tlv->hf_vendor_id, "Vendor ID", abbrev, FT_UINT24, BASE_DEC, blurb);

        abbrev = alnumerize(
            g_strdup_printf(
                "wimaxasncp.tlv.%s.value.vendor_rest_of_info", tlv->name));

        add_reg_info(
            &tlv->hf_vendor_rest_of_info, "Rest of Info", abbrev, FT_BYTES, BASE_NONE,
            blurb);

        break;

    case WIMAXASNCP_TLV_EAP:
        blurb = g_strdup_printf("EAP payload embedded in %s", name);

        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_BYTES, BASE_NONE, blurb);
        break;


    default:
        add_reg_info(
            &tlv->hf_value, name, abbrev, FT_BYTES, BASE_NONE, blurb);

        if (debug_enabled)
        {
            g_print(
                "fix-me: unknown decoder: %d\n", tlv->decoder);
        }

        break;
    }
}

/* ========================================================================= */
/* Register the protocol fields and subtrees with Wireshark */
static void
register_wimaxasncp_fields(const char* unused _U_)
{
    gboolean  debug_parser;
    gboolean  dump_dict;
    gchar    *dir;
    gchar*    dict_error;

    /* ------------------------------------------------------------------------
     * List of header fields
     * ------------------------------------------------------------------------
     */

    static hf_register_info hf_base[] = {
            {
                &hf_wimaxasncp_version,      /* ID */
                {
                    "Version",               /* FIELDNAME */
                    "wimaxasncp.version",    /* PROTOABBREV.FIELDABBRE */
                    FT_UINT8,                /* FIELDTYPE */
                    BASE_DEC,                /* FIELDBASE */
                    NULL,                    /* FIELDCONVERT */
                    0x0,                     /* BITMASK */
                    NULL,                      /* FIELDDESCR */
                    HFILL                    /* HFILL */
                }
            },
            {
                &hf_wimaxasncp_flags,
                {
                    "Flags",
                    "wimaxasncp.flags",
                    FT_UINT8,
                    BASE_HEX,
                    NULL,
                    0xff,
                    NULL,
                    HFILL
                }
            },
            {
                &hf_wimaxasncp_function_type,
                {
                    "Function Type",
                    "wimaxasncp.function_type",
                    FT_UINT8,
                    BASE_DEC,
                    NULL,
                    0x0,
                    NULL,
                    HFILL
                }
            },
            {
                &hf_wimaxasncp_op_id,
                {
                    "OP ID",
                    "wimaxasncp.opid",
                    FT_UINT8,
                    BASE_HEX,
                    VALS(wimaxasncp_op_id_vals),
                    0xE0,
                    NULL,
                    HFILL
                }
            },
#if 0
            {
                &hf_wimaxasncp_message_type,
                {
                    "Message Type",
                    "wimaxasncp.message_type",
                    FT_UINT8,
                    BASE_HEX,
                    NULL,
                    0x1F,
                    NULL,
                    HFILL
                }
            },
#endif
#if 0
            {
                &hf_wimaxasncp_qos_msg,
                {
                    "Message Type",
                    "wimaxasncp.qos_msg",
                    FT_UINT8,
                    BASE_HEX,
                    NULL,
                    0x1F,
                    NULL,
                    HFILL
                }
            },
#endif
#if 0
            {
                &hf_wimaxasncp_ho_control_msg,
                {
                    "Message Type",
                    "wimaxasncp.ho_control_msg",
                    FT_UINT8,
                    BASE_HEX,
                    NULL,
                    0x1F,
                    NULL,
                    HFILL
                }
            },
#endif
#if 0
            {
                &hf_wimaxasncp_data_path_control_msg,
                {
                    "Message Type",
                    "wimaxasncp.data_path_control_msg",
                    FT_UINT8,
                    BASE_HEX,
                    NULL,
                    0x1F,
                    NULL,
                    HFILL
                }
            },
#endif
#if 0
            {
                &hf_wimaxasncp_context_delivery_msg,
                {
                    "Message Type",
                    "wimaxasncp.context_delivery_msg",
                    FT_UINT8,
                    BASE_HEX,
                    NULL,
                    0x1F,
                    NULL,
                    HFILL
                }
            },
#endif
#if 0
            {
                &hf_wimaxasncp_r3_mobility_msg,
                {
                    "Message Type",
                    "wimaxasncp.r3_mobility_msg",
                    FT_UINT8,
                    BASE_HEX,
                    NULL,
                    0x1F,
                    NULL,
                    HFILL
                }
            },
#endif
#if 0
            {
                &hf_wimaxasncp_paging_msg,
                {
                    "Message Type",
                    "wimaxasncp.paging_msg",
                    FT_UINT8,
                    BASE_HEX,
                    NULL,
                    0x1F,
                    NULL,
                    HFILL
                }
            },
#endif
#if 0
            {
                &hf_wimaxasncp_rrm_msg,
                {
                    "Message Type",
                    "wimaxasncp.rrm_msg",
                    FT_UINT8,
                    BASE_HEX,
                    NULL,
                    0x1F,
                    NULL,
                    HFILL
                }
            },
#endif
#if 0
            {
                &hf_wimaxasncp_authentication_msg,
                {
                    "Message Type",
                    "wimaxasncp.authentication_msg",
                    FT_UINT8,
                    BASE_HEX,
                    NULL,
                    0x1F,
                    NULL,
                    HFILL
                }
            },
#endif
#if 0
            {
                &hf_wimaxasncp_ms_state_msg,
                {
                    "Message Type",
                    "wimaxasncp.ms_state_msg",
                    FT_UINT8,
                    BASE_HEX,
                    NULL,
                    0x1F,
                    NULL,
                    HFILL
                }
            },
#endif
#if 0
            {
                &hf_wimaxasncp_reauthentication_msg,
                {
                    "Message Type",
                    "wimaxasncp.reauthentication_msg",
                    FT_UINT8,
                    BASE_HEX,
                    NULL,
                    0x1F,
                    NULL,
                    HFILL
                }
            },
#endif
#if 0
            {
                &hf_wimaxasncp_session_msg,
                {
                    "Message Type",
                    "wimaxasncp.session_msg",
                    FT_UINT8,
                    BASE_HEX,
                    NULL,
                    0x1F,
                    NULL,
                    HFILL
                }
            },
#endif
            {
                &hf_wimaxasncp_length,
                {
                    "Length",
                    "wimaxasncp.length",
                    FT_UINT16,
                    BASE_DEC,
                    NULL,
                    0x0,
                    NULL,
                    HFILL
                }
            },
            {
                &hf_wimaxasncp_msid,
                {
                    "MSID",
                    "wimaxasncp.msid",
                    FT_ETHER,
                    BASE_NONE,
                    NULL,
                    0x0,
                    NULL,
                    HFILL
                }
            },
            {
                &hf_wimaxasncp_reserved1,
                {
                    "Reserved",
                    "wimaxasncp.reserved1",
                    FT_UINT32,
                    BASE_HEX,
                    NULL,
                    0x0,
                    NULL,
                    HFILL
                }
            },
            {
                &hf_wimaxasncp_transaction_id,
                {
                    "Transaction ID",
                    "wimaxasncp.transaction_id",
                    FT_UINT16,
                    BASE_HEX,
                    NULL,
                    0x0,
                    NULL,
                    HFILL
                }
            },
            {
                &hf_wimaxasncp_reserved2,
                {
                    "Reserved",
                    "wimaxasncp.reserved2",
                    FT_UINT16,
                    BASE_HEX,
                    NULL,
                    0x0,
                    NULL,
                    HFILL
                }
            },
#if 0
            {
                &hf_wimaxasncp_tlv,
                {
                    "TLV",
                    "wimaxasncp.tlv",
                    FT_BYTES,
                    BASE_NONE,
                    NULL,
                    0x0,
                    NULL,
                    HFILL
                }
            },
#endif
            {
                &hf_wimaxasncp_tlv_type,
                {
                    "Type",
                    "wimaxasncp.tlv.type",
                    FT_UINT16,
                    BASE_DEC,
                    NULL,
                    0x0,
                    NULL,
                    HFILL
                }
            },
            {
                &hf_wimaxasncp_tlv_length,
                {
                    "Length",
                    "wimaxasncp.tlv.length",
                    FT_UINT16,
                    BASE_DEC,
                    NULL,
                    0x0,
                    NULL,
                    HFILL
                }
            },
            {
                &hf_wimaxasncp_tlv_value_bytes,
                {
                    "Value",
                    "wimaxasncp.tlv_value_bytes",
                    FT_BYTES,
                    BASE_NONE,
                    NULL,
                    0x0,
                    NULL,
                    HFILL
                }
            },
            {
                &hf_wimaxasncp_tlv_value_bitflags8,
                {
                    "Value",
                    "wimaxasncp.tlv_value_bitflags8",
                    FT_UINT8,
                    BASE_HEX,
                    NULL,
                    0xff,
                    NULL,
                    HFILL
                }
            },
            {
                &hf_wimaxasncp_tlv_value_bitflags16,
                {
                    "Value",
                    "wimaxasncp.tlv_value_bitflags16",
                    FT_UINT16,
                    BASE_HEX,
                    NULL,
                    0xffff,
                    NULL,
                    HFILL
                }
            },
            {
                &hf_wimaxasncp_tlv_value_bitflags32,
                {
                    "Value",
                    "wimaxasncp.tlv_value_bitflags32",
                    FT_UINT32,
                    BASE_HEX,
                    NULL,
                    0xffffffff,
                    NULL,
                    HFILL
                }
            },
#if 0
            {
                &hf_wimaxasncp_tlv_value_protocol,
                {
                    "Value",
                    "wimaxasncp.tlv_value_protocol",
                    FT_UINT16,
                    BASE_DEC,
                    NULL,
                    0x0,
                    NULL,
                    HFILL
                }
            },
#endif
#if 0
            {
                &hf_wimaxasncp_tlv_value_vendor_id,
                {
                    "Vendor ID",
                    "wimaxasncp.tlv_value_vendor_id",
                    FT_UINT24,
                    BASE_DEC,
                    NULL,
                    0x0,
                    NULL,
                    HFILL
                }
            }
#endif
        };

    /* ------------------------------------------------------------------------
     * Protocol subtree array
     * ------------------------------------------------------------------------
     */

    static gint *ett_base[] = {
            &ett_wimaxasncp,
            &ett_wimaxasncp_flags,
            &ett_wimaxasncp_tlv,
            &ett_wimaxasncp_tlv_value_bitflags8,
            &ett_wimaxasncp_tlv_value_bitflags16,
            &ett_wimaxasncp_tlv_value_bitflags32,
            &ett_wimaxasncp_tlv_protocol_list,
            &ett_wimaxasncp_tlv_port_range_list,
            &ett_wimaxasncp_tlv_ip_address_mask_list,
            &ett_wimaxasncp_tlv_ip_address_mask,
            &ett_wimaxasncp_tlv_eap,
            &ett_wimaxasncp_tlv_vendor_specific_information_field,
            &ett_wimaxasncp_port_range
    };

    static ei_register_info ei[] = {
        { &ei_wimaxasncp_tlv_type, { "wimaxasncp.tlv.type.unknown", PI_UNDECODED, PI_WARN, "Unknown tlv", EXPFILL }},
        { &ei_wimaxasncp_function_type, { "wimaxasncp.function_type.unknown", PI_UNDECODED, PI_WARN, "Unknown function type", EXPFILL }},
        { &ei_wimaxasncp_op_id, { "wimaxasncp.opid.unknown", PI_UNDECODED, PI_WARN, "Unknown message op", EXPFILL }},
        { &ei_wimaxasncp_length_bad, { "wimaxasncp.length.bad", PI_MALFORMED, PI_ERROR, "Bad length", EXPFILL }},
    };

    expert_module_t* expert_wimaxasncp;

    /* ------------------------------------------------------------------------
     * load the XML dictionary
     * ------------------------------------------------------------------------
     */

    debug_parser = getenv("WIRESHARK_DEBUG_WIMAXASNCP_DICT_PARSER") != NULL;
    dump_dict    = getenv("WIRESHARK_DUMP_WIMAXASNCP_DICT") != NULL;

    dir = g_strdup_printf(
        "%s" G_DIR_SEPARATOR_S "wimaxasncp",
        get_datafile_dir());

    wimaxasncp_dict =
        wimaxasncp_dict_scan(dir, "dictionary.xml", debug_parser, &dict_error);

    g_free(dir);

    if (dict_error)
    {
        report_failure("wimaxasncp - %s", dict_error);
        g_free(dict_error);
    }

    if (wimaxasncp_dict && dump_dict)
    {
        wimaxasncp_dict_print(stdout, wimaxasncp_dict);
    }

    /* ------------------------------------------------------------------------
     * build the hf and ett dictionary entries
     * ------------------------------------------------------------------------
     */

    wimaxasncp_build_dict.hf =
        g_array_new(FALSE, TRUE, sizeof(hf_register_info));

    g_array_append_vals(
        wimaxasncp_build_dict.hf, hf_base, array_length(hf_base));

    wimaxasncp_build_dict.ett =
        g_array_new(FALSE, TRUE, sizeof(gint*));

    g_array_append_vals(
        wimaxasncp_build_dict.ett, ett_base, array_length(ett_base));

    if (wimaxasncp_dict)
    {
        wimaxasncp_dict_tlv_t *tlv;

        /* For each TLV found in XML file */
        for (tlv = wimaxasncp_dict->tlvs; tlv; tlv = tlv->next)
        {
            if (tlv->enums)
            {
                /* Create array for enums */
                wimaxasncp_dict_enum_t *e;
                GArray* array = g_array_new(TRUE, TRUE, sizeof(value_string));

                /* Copy each entry into value_string array */
                for (e = tlv->enums; e; e = e->next)
                {
                    value_string item = { e->code, e->name };
                    g_array_append_val(array, item);
                }

                /* Set enums to use with this TLV */
                tlv->enum_vs = (value_string*)(void*)array->data;
            }

            add_tlv_reg_info(tlv);
        }
    }

    /* add an entry for unknown TLVs */
    add_tlv_reg_info(&wimaxasncp_tlv_not_found);

    /* The following debug will only be printed if the debug_enabled variable
     * is set programmatically.  Setting the value via preferences will not
     * work as it will be set too late to affect this code path.
     */
    if (debug_enabled)
    {
        if (wimaxasncp_dict)
        {
            wimaxasncp_dict_tlv_t *tlv;

            for (tlv = wimaxasncp_dict->tlvs; tlv; tlv = tlv->next)
            {
                printf(
                    "%s\n"
                    "  type                   = %u\n"
                    "  description            = %s\n"
                    "  decoder                = %s\n"
                    "  hf_root                = %d\n"
                    "  hf_value               = %d\n"
                    "  hf_ipv4                = %d\n"
                    "  hf_ipv6                = %d\n"
                    "  hf_bsid                = %d\n"
                    "  hf_protocol            = %d\n"
                    "  hf_port_low            = %d\n"
                    "  hf_port_high           = %d\n"
                    "  hf_ipv4_mask           = %d\n"
                    "  hf_ipv6_mask           = %d\n"
                    "  hf_vendor_id           = %d\n"
                    "  hf_vendor_rest_of_info = %d\n",
                    tlv->name,
                    tlv->type,
                    tlv->description,
                    val_to_str(
                        tlv->decoder, wimaxasncp_decode_type_vals, "Unknown"),
                    tlv->hf_root,
                    tlv->hf_value,
                    tlv->hf_ipv4,
                    tlv->hf_ipv6,
                    tlv->hf_bsid,
                    tlv->hf_protocol,
                    tlv->hf_port_low,
                    tlv->hf_port_high,
                    tlv->hf_ipv4_mask,
                    tlv->hf_ipv6_mask,
                    tlv->hf_vendor_id,
                    tlv->hf_vendor_rest_of_info);
            }
        }
    }

        /* Required function calls to register the header fields and subtrees
         * used */
    proto_register_field_array(
        proto_wimaxasncp,
        (hf_register_info*)(void *)wimaxasncp_build_dict.hf->data,
        wimaxasncp_build_dict.hf->len);

    proto_register_subtree_array(
        (gint**)(void *)wimaxasncp_build_dict.ett->data,
        wimaxasncp_build_dict.ett->len);

    expert_wimaxasncp = expert_register_protocol(proto_wimaxasncp);
    expert_register_field_array(expert_wimaxasncp, ei, array_length(ei));

}




/* ========================================================================= */
/* Register the protocol with Wireshark */

/* this format is require because a script is used to build the C function
   that calls all the protocol registration.
*/

void
proto_register_wimaxasncp(void)
{
    module_t *wimaxasncp_module;

    /* ------------------------------------------------------------------------
     * complete registration
     * ------------------------------------------------------------------------
     */

        /* Register the protocol name and description */
    proto_wimaxasncp = proto_register_protocol(
            "WiMAX ASN Control Plane Protocol",
            "WiMAX ASN CP",
            "wimaxasncp");


        /* Register this dissector by name */
    wimaxasncp_handle = register_dissector("wimaxasncp", dissect_wimaxasncp, proto_wimaxasncp);

        /* Register preferences module (See Section 2.6 for more on
         * preferences) */
    wimaxasncp_module = prefs_register_protocol(
            proto_wimaxasncp,
            proto_reg_handoff_wimaxasncp);

        /* Register preferences */
    prefs_register_bool_preference(
            wimaxasncp_module,
            "show_transaction_id_d_bit",
            "Show transaction ID direction bit",
            "Show transaction ID direction bit separately from the rest of "
            "the transaction ID field.",
            &show_transaction_id_d_bit);

    prefs_register_bool_preference(
            wimaxasncp_module,
            "debug_enabled",
            "Enable debug output",
            "Print debug output to the console.",
            &debug_enabled);

    prefs_register_uint_preference(
        wimaxasncp_module,
        "udp.wimax_port",
        "UDP Port for WiMAX ASN Control Plane Protocol",
        "Set UDP port for WiMAX ASN Control Plane Protocol",
        10, &global_wimaxasncp_udp_port);

    prefs_register_enum_preference(
        wimaxasncp_module,
        "nwg_version",
        "NWG Version",
        "Version of the NWG that the R6 protocol complies with",
        &global_wimaxasncp_nwg_ver,
        wimaxasncp_nwg_versions,
        FALSE);

    proto_register_prefix("wimaxasncp", register_wimaxasncp_fields);
}

/* ========================================================================= */
/* If this dissector uses sub-dissector registration add a registration
   routine.  This exact format is required because a script is used to find
   these routines and create the code that calls these routines.

   This function is also called by preferences whenever "Apply" is pressed
   (see prefs_register_protocol above) so it should accommodate being called
   more than once.
*/
void
proto_reg_handoff_wimaxasncp(void)
{
    static gboolean           inited      = FALSE;
    static int                currentPort = -1;

    if (!inited)
    {

        /* Find the EAP dissector */
        eap_handle = find_dissector_add_dependency("eap", proto_wimaxasncp);

        inited = TRUE;
    }

    if (currentPort != -1)
    {
        /* Remove any previous registered port */
        dissector_delete_uint("udp.port", currentPort, wimaxasncp_handle);
    }

    /* Add the new one from preferences */
    currentPort = global_wimaxasncp_udp_port;
    dissector_add_uint("udp.port", currentPort, wimaxasncp_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:
 */
