/* packet-rtls.c
 * Routines for Real Time Location System dissection
 * Copyright 2016, Alexis La Goutte (See Authors)
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

/*
 * http://community.arubanetworks.com/aruba/attachments/aruba/unified-wired-wireless-access/23715/1/RTLS_integrationv6.docx
 */

#include <config.h>

#include <epan/packet.h>
#include <epan/expert.h>

void proto_reg_handoff_rtls(void);
void proto_register_rtls(void);

static dissector_handle_t rtls_handle;

static int proto_rtls = -1;
static int hf_rtls_message_type = -1;
static int hf_rtls_message_id = -1;
static int hf_rtls_version_major = -1;
static int hf_rtls_version_minor = -1;
static int hf_rtls_data_length = -1;
static int hf_rtls_ap_mac = -1;
static int hf_rtls_padding = -1;
static int hf_rtls_reserved = -1;
static int hf_rtls_signature = -1;

static int hf_rtls_as_tag_addr = -1;
static int hf_rtls_sr_mac_address = -1;
static int hf_rtls_nack_flags = -1;
static int hf_rtls_nack_flags_internal_error = -1;
static int hf_rtls_nack_flags_station_not_found = -1;
static int hf_rtls_nack_flags_reserved = -1;
static int hf_rtls_tr_bssid = -1;
static int hf_rtls_tr_rssi = -1;
static int hf_rtls_tr_rssi_calculated = -1;
static int hf_rtls_tr_noise_floor = -1;
static int hf_rtls_tr_timestamp = -1;
static int hf_rtls_tr_tag_mac = -1;
static int hf_rtls_tr_frame_control = -1;
static int hf_rtls_tr_sequence = -1;
static int hf_rtls_tr_data_rate = -1;
static int hf_rtls_tr_tx_power = -1;
static int hf_rtls_tr_channel = -1;
static int hf_rtls_tr_battery = -1;
static int hf_rtls_sr_mac = -1;
static int hf_rtls_sr_noise_floor = -1;
static int hf_rtls_sr_data_rate = -1;
static int hf_rtls_sr_channel = -1;
static int hf_rtls_sr_rssi = -1;
static int hf_rtls_sr_rssi_calculated = -1;
static int hf_rtls_sr_type = -1;
static int hf_rtls_sr_associated = -1;
static int hf_rtls_sr_radio_bssid = -1;
static int hf_rtls_sr_mon_bssid = -1;
static int hf_rtls_sr_age = -1;
static int hf_rtls_ser_mac = -1;
static int hf_rtls_ser_bssid = -1;
static int hf_rtls_ser_essid = -1;
static int hf_rtls_ser_channel = -1;
static int hf_rtls_ser_phy_type = -1;
static int hf_rtls_ser_rssi = -1;
static int hf_rtls_ser_rssi_calculated = -1;
static int hf_rtls_ser_duration = -1;
static int hf_rtls_ser_num_packets = -1;
static int hf_rtls_ser_noise_floor = -1;
static int hf_rtls_ser_classification = -1;
static int hf_rtls_aer_bssid = -1;
static int hf_rtls_aer_essid = -1;
static int hf_rtls_aer_channel = -1;
static int hf_rtls_aer_phy_type = -1;
static int hf_rtls_aer_rssi = -1;
static int hf_rtls_aer_rssi_calculated = -1;
static int hf_rtls_aer_duration = -1;
static int hf_rtls_aer_num_packets = -1;
static int hf_rtls_aer_noise_floor = -1;
static int hf_rtls_aer_classification = -1;
static int hf_rtls_aer_match_type = -1;
static int hf_rtls_aer_match_method = -1;
static int hf_rtls_cmr_messages = -1;

static int * const rtls_nack_flags[] = {
    &hf_rtls_nack_flags_internal_error,
    &hf_rtls_nack_flags_station_not_found,
    &hf_rtls_nack_flags_reserved,
    NULL
};

static expert_field ei_rtls_undecoded = EI_INIT;
static gint ett_rtls = -1;
static gint ett_rtls_message = -1;
static gint ett_rtls_nack_flags = -1;

#define RTLS_MIN_LENGTH 16

#define AR_AS_CONFIG_SET            0x0000
#define AR_STATION_REQUEST          0x0001
#define AR_ACK                      0x0010
#define AR_NACK                     0x0011
#define AR_TAG_REPORT               0x0012
#define AR_STATION_REPORT           0x0013
#define AR_COMPOUND_MESSAGE_REPORT  0x0014
#define AR_AP_NOTIFICATION          0x0015
#define AR_MMS_CONFIG_SET           0x0016
#define AR_STATION_EX_REPORT        0x0017
#define AR_AP_EX_REPORT             0x0018

static const value_string rtls_message_type_vals[] = {
    { AR_AS_CONFIG_SET, "AR_AS_CONFIG_SET" },
    { AR_STATION_REQUEST, "AR_STATION_REQUEST" },
    { AR_ACK, "AR_ACK"},
    { AR_NACK, "AR_NACK"},
    { AR_TAG_REPORT, "AR_TAG_REPORT"},
    { AR_STATION_REPORT, "AR_STATION_REPORT"},
    { AR_COMPOUND_MESSAGE_REPORT, "AR_COMPOUND_MESSAGE_REPORT"},
    { AR_AP_NOTIFICATION, "AR_AP_NOTIFICATION"},
    { AR_MMS_CONFIG_SET, "AR_MMS_CONFIG_SET"},
    { AR_STATION_EX_REPORT, "AR_STATION_EX_REPORT"},
    { AR_AP_EX_REPORT, "AR_AP_EX_REPORT"},
    { 0, NULL }
};

static const value_string rtls_sr_type_vals[] = {
    { 1, "AR_WLAN_CLIENT" },
    { 2, "AR_WLAN_AP" },
    {0, NULL}
};

static const value_string rtls_sr_associated_vals[] = {
    { 1, "AR_WLAN_ASSOCIATED (All APs and Associated Stations)" },
    { 2, "AR_WLAN_UNASSOCIATED (Unassociated Stations)" },
    {0, NULL}
};

static const value_string rtls_data_rate_vals[] = {
    { 0x00, "1 Mbits" },
    { 0x01, "2 Mbits" },
    { 0x02, "5.5 Mbits" },
    { 0x03, "6 Mbits" },
    { 0x04, "9 Mbits" },
    { 0x05, "11 Mbits" },
    { 0x06, "12 Mbits" },
    { 0x07, "18 Mbits" },
    { 0x08, "24 Mbits" },
    { 0x09, "36 Mbits" },
    { 0x0A, "48 Mbits" },
    { 0x0B, "54 Mbits" },
    {0, NULL}
};

static const value_string rtls_ex_phy_type_vals[] = {
    { 1, "802.11b" },
    { 2, "802.11a" },
    { 3, "802.11g" },
    { 4, "802.11ag" },
    {0, NULL}
};

static const value_string rtls_ex_classification_vals[] = {
    { 1, "Valid" },
    { 2, "interfering" },
    { 3, "DOS'ed" },
    {0, NULL}
};

static void
rssi_base_custom(gchar *result, guint32 rssi)
{
    /* Convert Hex to decimal and subtract 256 to get the signal value */
    snprintf(result, ITEM_LABEL_LENGTH, "%d", rssi - 256);

}

static int
dissect_rtls_header(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *rtls_tree, guint offset, guint *data_length)
{

    proto_tree_add_item(rtls_tree, hf_rtls_message_type, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;

    proto_tree_add_item(rtls_tree, hf_rtls_message_id, tvb, offset, 2, ENC_BIG_ENDIAN);
    offset += 2;

    proto_tree_add_item(rtls_tree, hf_rtls_version_major, tvb, offset, 1, ENC_BIG_ENDIAN);
    offset += 1;

    proto_tree_add_item(rtls_tree, hf_rtls_version_minor, tvb, offset, 1, ENC_BIG_ENDIAN);
    offset += 1;

    proto_tree_add_item(rtls_tree, hf_rtls_data_length, tvb, offset, 2, ENC_BIG_ENDIAN);
    if(data_length){
        *data_length = tvb_get_ntohs(tvb, offset);
    }
    offset += 2;

    proto_tree_add_item(rtls_tree, hf_rtls_ap_mac, tvb, offset, 6, ENC_NA);
    offset += 6;

    proto_tree_add_item(rtls_tree, hf_rtls_padding, tvb, offset, 2, ENC_NA);
    offset += 2;

    return offset;
}

static int
// NOLINTNEXTLINE(misc-no-recursion)
dissect_rtls_message_type(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *rtls_tree, guint offset, guint type)
{
    proto_item *ti_rssi;

    switch(type){
        case AR_AS_CONFIG_SET:
            proto_tree_add_item(rtls_tree, hf_rtls_as_tag_addr, tvb, offset, 6, ENC_NA);
            offset += 6;
            proto_tree_add_item(rtls_tree, hf_rtls_reserved, tvb, offset, 2, ENC_NA);
            offset += 2;
        break;
        case AR_STATION_REQUEST:
            proto_tree_add_item(rtls_tree, hf_rtls_sr_mac_address, tvb, offset, 6, ENC_NA);
            offset += 6;
            proto_tree_add_item(rtls_tree, hf_rtls_reserved, tvb, offset, 2, ENC_NA);
            offset += 2;
        break;
        case AR_ACK:
        case AR_AP_NOTIFICATION:
            /* No Payload */
        break;
        case AR_NACK:
            proto_tree_add_bitmask_with_flags(rtls_tree, tvb, offset,
hf_rtls_nack_flags, ett_rtls_nack_flags, rtls_nack_flags, ENC_BIG_ENDIAN, BMT_NO_APPEND);
            offset += 2;
            proto_tree_add_item(rtls_tree, hf_rtls_reserved, tvb, offset, 2, ENC_NA);
            offset += 2;
        break;
        case AR_TAG_REPORT:
            proto_tree_add_item(rtls_tree, hf_rtls_tr_bssid, tvb, offset, 6, ENC_NA);
            offset += 6;
            proto_tree_add_item(rtls_tree, hf_rtls_tr_rssi, tvb, offset, 1, ENC_BIG_ENDIAN);
            ti_rssi = proto_tree_add_item(rtls_tree, hf_rtls_tr_rssi_calculated, tvb, offset, 1, ENC_BIG_ENDIAN);
            proto_item_set_generated(ti_rssi);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_tr_noise_floor, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_tr_timestamp, tvb, offset, 4, ENC_BIG_ENDIAN);
            offset += 4;
            proto_tree_add_item(rtls_tree, hf_rtls_tr_tag_mac, tvb, offset, 6, ENC_NA);
            offset += 6;
            proto_tree_add_item(rtls_tree, hf_rtls_tr_frame_control, tvb, offset, 2, ENC_BIG_ENDIAN);
            offset += 2;
            proto_tree_add_item(rtls_tree, hf_rtls_tr_sequence, tvb, offset, 2, ENC_BIG_ENDIAN);
            offset += 2;
            proto_tree_add_item(rtls_tree, hf_rtls_tr_data_rate, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_tr_tx_power, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_tr_channel, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_tr_battery, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_reserved, tvb, offset, 2, ENC_NA);
            offset += 2;
        break;
       case AR_STATION_REPORT:
            proto_tree_add_item(rtls_tree, hf_rtls_sr_mac, tvb, offset, 6, ENC_NA);
            offset += 6;
            proto_tree_add_item(rtls_tree, hf_rtls_sr_noise_floor, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_sr_data_rate, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_sr_channel, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_sr_rssi, tvb, offset, 1, ENC_BIG_ENDIAN);
            ti_rssi = proto_tree_add_item(rtls_tree, hf_rtls_sr_rssi_calculated, tvb, offset, 1, ENC_BIG_ENDIAN);
            proto_item_set_generated(ti_rssi);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_sr_type, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_sr_associated, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_sr_radio_bssid, tvb, offset, 6, ENC_NA);
            offset += 6;
            proto_tree_add_item(rtls_tree, hf_rtls_sr_mon_bssid, tvb, offset, 6, ENC_NA);
            offset += 6;
            proto_tree_add_item(rtls_tree, hf_rtls_sr_age, tvb, offset, 4, ENC_BIG_ENDIAN);
            offset += 4;
        break;
       case AR_STATION_EX_REPORT:
            proto_tree_add_item(rtls_tree, hf_rtls_ser_mac, tvb, offset, 6, ENC_NA);
            offset += 6;
            proto_tree_add_item(rtls_tree, hf_rtls_ser_bssid, tvb, offset, 6, ENC_NA);
            offset += 6;
            proto_tree_add_item(rtls_tree, hf_rtls_ser_essid, tvb, offset, 33, ENC_ASCII);
            offset += 33;
            proto_tree_add_item(rtls_tree, hf_rtls_ser_channel, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_ser_phy_type, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_ser_rssi, tvb, offset, 1, ENC_BIG_ENDIAN);
            ti_rssi = proto_tree_add_item(rtls_tree, hf_rtls_ser_rssi_calculated, tvb, offset, 1, ENC_BIG_ENDIAN);
            proto_item_set_generated(ti_rssi);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_ser_duration, tvb, offset, 2, ENC_BIG_ENDIAN);
            offset += 2;
            proto_tree_add_item(rtls_tree, hf_rtls_ser_num_packets, tvb, offset, 2, ENC_BIG_ENDIAN);
            offset += 2;
            proto_tree_add_item(rtls_tree, hf_rtls_ser_noise_floor, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_ser_classification, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_reserved, tvb, offset, 2, ENC_NA);
            offset += 2;
        break;
       case AR_AP_EX_REPORT:
            proto_tree_add_item(rtls_tree, hf_rtls_aer_bssid, tvb, offset, 6, ENC_NA );
            offset += 6;
            proto_tree_add_item(rtls_tree, hf_rtls_aer_essid, tvb, offset, 33, ENC_ASCII);
            offset += 33;
            proto_tree_add_item(rtls_tree, hf_rtls_aer_channel, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_aer_phy_type, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_aer_rssi, tvb, offset, 1, ENC_BIG_ENDIAN);
            ti_rssi = proto_tree_add_item(rtls_tree, hf_rtls_aer_rssi_calculated, tvb, offset, 1, ENC_BIG_ENDIAN);
            proto_item_set_generated(ti_rssi);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_aer_duration, tvb, offset, 2, ENC_BIG_ENDIAN);
            offset += 2;
            proto_tree_add_item(rtls_tree, hf_rtls_aer_num_packets, tvb, offset, 2, ENC_BIG_ENDIAN);
            offset += 2;
            proto_tree_add_item(rtls_tree, hf_rtls_aer_noise_floor, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_aer_classification, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_aer_match_type, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_aer_match_method, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            proto_tree_add_item(rtls_tree, hf_rtls_reserved, tvb, offset, 2, ENC_NA);
            offset += 2;
        break;
       case AR_COMPOUND_MESSAGE_REPORT:{
            guint32 cmr_messages;
            proto_tree *sub_tree;

            proto_tree_add_item_ret_uint(rtls_tree, hf_rtls_cmr_messages, tvb, offset, 2, ENC_BIG_ENDIAN, &cmr_messages);
            offset += 2;
            proto_tree_add_item(rtls_tree, hf_rtls_reserved, tvb, offset, 2, ENC_NA);
            offset += 2;
            while(cmr_messages){
                guint32 data_length;
                type = tvb_get_ntohs(tvb, offset);
                sub_tree = proto_tree_add_subtree_format(rtls_tree, tvb, offset, -1, ett_rtls_message, NULL, "%s", val_to_str(type, rtls_message_type_vals, "(unknown %d)"));

                offset = dissect_rtls_header(tvb, pinfo, sub_tree, offset, &data_length);

                // We recurse here, but we'll run out of packet before we run out of stack.
                offset = dissect_rtls_message_type(tvb, pinfo, sub_tree, offset, type);

                proto_item_set_len(sub_tree, data_length + 16);
                cmr_messages--;
            }
            }
        break;
        default:{
            guint32 remaining;

            remaining = tvb_reported_length_remaining(tvb, offset) - 20; /* Remove 20 of signature */
            proto_tree_add_expert(rtls_tree, pinfo, &ei_rtls_undecoded, tvb, offset, remaining);
            offset += remaining;
            }
        break;
    }

    return offset;
}

static int
dissect_rtls(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
    proto_item *ti;
    proto_tree *rtls_tree;
    guint       offset = 0;
    guint32     type;

    if (tvb_reported_length(tvb) < RTLS_MIN_LENGTH)
        return 0;

    col_set_str(pinfo->cinfo, COL_PROTOCOL, "RTLS");


    ti = proto_tree_add_item(tree, proto_rtls, tvb, 0, -1, ENC_NA);

    rtls_tree = proto_item_add_subtree(ti, ett_rtls);

    /* RTLS Header */
    type = tvb_get_ntohs(tvb, offset);
    col_add_str(pinfo->cinfo, COL_INFO, val_to_str(type, rtls_message_type_vals, "(unknown %d)"));

    offset = dissect_rtls_header(tvb, pinfo, rtls_tree, offset, NULL);

    offset = dissect_rtls_message_type(tvb, pinfo, rtls_tree, offset, type);

    /* TODO: Check signature ? HMAC-SHA1 with shared key and RTLS packet data */
    proto_tree_add_item(rtls_tree, hf_rtls_signature, tvb, offset, 20, ENC_NA);
    offset += 20;

    return offset;
}

void
proto_register_rtls(void)
{
    expert_module_t *expert_rtls;

    static hf_register_info hf[] = {

        /* RTLS Header*/
        { &hf_rtls_message_type,
          { "Message Type", "rtls.message_type",
            FT_UINT16, BASE_HEX, VALS(rtls_message_type_vals), 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_message_id,
          { "Message Id", "rtls.message_id",
            FT_UINT16, BASE_DEC, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_version_major,
          { "Version Major", "rtls.version_major",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_version_minor,
          { "Version Minor", "rtls.version_minor",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_data_length,
          { "Data Length", "rtls.data_length",
            FT_UINT16, BASE_DEC, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_ap_mac,
          { "AP MAC Address", "rtls.ap_mac",
            FT_ETHER, BASE_NONE, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_padding,
          { "Padding", "rtls.padding",
            FT_BYTES, BASE_NONE, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_reserved,
          { "Reserved", "rtls.reserved",
            FT_BYTES, BASE_NONE, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_signature,
          { "Signature", "rtls.signature",
            FT_BYTES, BASE_NONE, NULL, 0x0,
            NULL, HFILL }
        },

        /* AR_AS_CONFIG_SET */
        { &hf_rtls_as_tag_addr,
          { "AS Tag Address", "rtls.as_tag_addr",
            FT_ETHER, BASE_NONE, NULL, 0x0,
            "Tag multicast address", HFILL }
        },
        /* AR_STATION_REQUEST */
        { &hf_rtls_sr_mac_address,
          { "MAC Address", "rtls.sr_mac_addr",
            FT_ETHER, BASE_NONE, NULL, 0x0,
            NULL, HFILL }
        },
        /* AR_NACK */
        { &hf_rtls_nack_flags,
          { "Flags", "rtls.nack.flags",
            FT_UINT16, BASE_HEX, NULL, 0x00,
            NULL, HFILL }
        },
        { &hf_rtls_nack_flags_internal_error,
          { "Internal Error", "rtls.nack.flags.internal_errors",
            FT_UINT16, BASE_HEX, NULL, 0x01,
            NULL, HFILL }
        },
        { &hf_rtls_nack_flags_station_not_found,
          { "Station Not found", "rtls.nack.flags.station_not_found",
            FT_UINT16, BASE_HEX, NULL, 0x02,
            NULL, HFILL }
        },
        { &hf_rtls_nack_flags_reserved,
          { "Reserved", "rtls.nack.flags.reserved",
            FT_UINT16, BASE_HEX, NULL, 0xFC,
            NULL, HFILL }
        },

        /* AR_TAG_REPORT */
        { &hf_rtls_tr_bssid,
          { "BSSID", "rtls.tr.bssid",
            FT_ETHER, BASE_NONE, NULL, 0x0,
            "MAC address of the radio where the frame was received", HFILL }
        },
        { &hf_rtls_tr_rssi,
          { "RSSI", "rtls.tr.rssi",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "Signal as a signed negative hex value", HFILL }
        },
        { &hf_rtls_tr_rssi_calculated,
          { "RSSI (calculated)", "rtls.tr.rssi.calculated",
            FT_UINT8, BASE_CUSTOM, CF_FUNC(rssi_base_custom), 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_tr_noise_floor,
          { "Noise Floor", "rtls.tr.noise_floor",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "Noise floor of the radio", HFILL }
        },
        { &hf_rtls_tr_timestamp,
          { "Timestamp", "rtls.tr.timestamp",
            FT_UINT32, BASE_DEC, NULL, 0x0,
            "Millisecond granularity timestamp that represents local time in AP when message was sent", HFILL }
        },
        { &hf_rtls_tr_tag_mac,
          { "Tag Mac", "rtls.tr.tag_mac",
            FT_ETHER, BASE_NONE, NULL, 0x0,
            "MAC address of the tag", HFILL }
        },
        { &hf_rtls_tr_frame_control,
          { "Frame Control", "rtls.tr.frame_control",
            FT_UINT16, BASE_HEX, NULL, 0x0,
            "Frame control from 802.11 header", HFILL }
        },
        { &hf_rtls_tr_sequence,
          { "Sequence", "rtls.tr.sequence",
            FT_UINT16, BASE_DEC, NULL, 0x0,
            "Sequence number from the 802.11 header", HFILL }
        },
        { &hf_rtls_tr_data_rate,
          { "Data Rate", "rtls.tr.data_rate",
            FT_UINT8, BASE_DEC, VALS(rtls_data_rate_vals), 0x0,
            "Data rate of chirp frame", HFILL }
        },
        { &hf_rtls_tr_tx_power,
          { "Tx Power", "rtls.tr.tx_power",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "Transmit power in dbm", HFILL }
        },
        { &hf_rtls_tr_channel,
          { "Channel", "rtls.tr.channel",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "Channel of tag transmission", HFILL }
        },
        { &hf_rtls_tr_battery,
          { "Battery", "rtls.tr.battery",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "Batter level information from the chirp frame if present", HFILL }
        },
        /* AR_STATION_REPORT */
        { &hf_rtls_sr_mac,
          { "MAC", "rtls.sr.mac",
            FT_ETHER, BASE_NONE, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_sr_noise_floor,
          { "Noise Floor", "rtls.sr.noise_floor",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "Noise floor of the channel where the station was last heard", HFILL }
        },
        { &hf_rtls_sr_data_rate,
          { "Data Rate", "rtls.sr.data_rate",
            FT_UINT8, BASE_DEC, VALS(rtls_data_rate_vals), 0x0,
            "Data rate of chirp frame", HFILL }
        },
        { &hf_rtls_sr_channel,
          { "Channel", "rtls.sr.channel",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "Channel where station was last heard", HFILL }
        },
        { &hf_rtls_sr_rssi,
          { "RSSI", "rtls.sr.rssi",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "Signal as a signed negative hex value", HFILL }
        },
        { &hf_rtls_sr_rssi_calculated,
          { "RSSI (calculated)", "rtls.sr.rssi.calculated",
            FT_UINT8, BASE_CUSTOM, CF_FUNC(rssi_base_custom), 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_sr_type,
          { "Type", "rtls.sr.type",
            FT_UINT8, BASE_DEC, VALS(rtls_sr_type_vals), 0x0,
            "Type of device", HFILL }
        },
        { &hf_rtls_sr_associated,
          { "Associated", "rtls.sr.associated",
            FT_UINT8, BASE_DEC, VALS(rtls_sr_associated_vals), 0x0,
            "Association status of station", HFILL }
        },
        { &hf_rtls_sr_radio_bssid,
          { "Radio BSSID", "rtls.sr.radio_bssids",
            FT_ETHER, BASE_NONE, NULL, 0x0,
            "Association status of station BSSID of the radio that detected the device", HFILL }
        },
        { &hf_rtls_sr_mon_bssid,
          { "Mon BSSID", "rtls.sr.mon_bssids",
            FT_ETHER, BASE_NONE, NULL, 0x0,
            "BSSID of the AP that the station is associated to", HFILL }
        },
        { &hf_rtls_sr_age,
          { "Age", "rtls.sr.age",
            FT_UINT32, BASE_DEC, NULL, 0x0,
            "The number of seconds since the last packet was heard from this station", HFILL }
        },
        /* AR_STATION_EX_REPORT */
        { &hf_rtls_ser_mac,
          { "MAC", "rtls.ser.mac",
            FT_ETHER, BASE_NONE, NULL, 0x0,
            "MAC address of station", HFILL }
        },
        { &hf_rtls_ser_bssid,
          { "BSSID", "rtls.ser.bssid",
            FT_ETHER, BASE_NONE, NULL, 0x0,
            "BSSID with which this station is associated", HFILL }
        },
        { &hf_rtls_ser_essid,
          { "ESSID", "rtls.ser.essid",
            FT_STRING, BASE_NONE, NULL, 0x0,
            "ESSID with which this station is associated", HFILL }
        },
        { &hf_rtls_ser_channel,
          { "Channel", "rtls.ser.channel",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "Channel where this station is active", HFILL }
        },
        { &hf_rtls_ser_phy_type,
          { "Phy type", "rtls.ser.phy_type",
            FT_UINT8, BASE_DEC, VALS(rtls_ex_phy_type_vals), 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_ser_rssi,
          { "RSSI", "rtls.ser.rssi",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "Average RSSI during the duration", HFILL }
        },
        { &hf_rtls_ser_rssi_calculated,
          { "RSSI (calculated)", "rtls.ser.rssi.calculated",
            FT_UINT8, BASE_CUSTOM, CF_FUNC(rssi_base_custom), 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_ser_duration,
          { "Duration", "rtls.ser.duration",
            FT_UINT16, BASE_DEC, NULL, 0x0,
            "Average calculation duration", HFILL }
        },
        { &hf_rtls_ser_num_packets,
          { "Num Packets", "rtls.ser.num_packets",
            FT_UINT16, BASE_DEC, NULL, 0x0,
            "Number of packets used in average RSSI calculation", HFILL }
        },
        { &hf_rtls_ser_noise_floor,
          { "Noise Floor", "rtls.ser.noise_floor",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "Noise floor of the radio", HFILL }
        },
        { &hf_rtls_ser_classification,
          { "Classification", "rtls.ser.classification",
            FT_UINT8, BASE_DEC, VALS(rtls_ex_classification_vals), 0x0,
            "Millisecond granularity timestamp that represents local time in AP when message was sent", HFILL }
        },
        /* AR_AP_EX_REPORT */
        { &hf_rtls_aer_bssid,
          { "BSSID", "rtls.aer.bssid",
            FT_ETHER, BASE_NONE, NULL, 0x0,
            "BSSID with which this station is associated", HFILL }
        },
        { &hf_rtls_aer_essid,
          { "ESSID", "rtls.aer.essid",
            FT_STRING, BASE_NONE, NULL, 0x0,
            "ESSID with which this station is associated", HFILL }
        },
        { &hf_rtls_aer_channel,
          { "Channel", "rtls.aer.channel",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "Channel where this station is active", HFILL }
        },
        { &hf_rtls_aer_phy_type,
          { "Phy type", "rtls.aer.phy_type",
            FT_UINT8, BASE_DEC, VALS(rtls_ex_phy_type_vals), 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_aer_rssi,
          { "RSSI", "rtls.aer.rssi",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "Average RSSI during the duration", HFILL }
        },
        { &hf_rtls_aer_rssi_calculated,
          { "RSSI (calculated)", "rtls.aer.rssi.calculated",
            FT_UINT8, BASE_CUSTOM, CF_FUNC(rssi_base_custom), 0x0,
            NULL, HFILL }
        },
        { &hf_rtls_aer_duration,
          { "Duration", "rtls.aer.duration",
            FT_UINT16, BASE_DEC, NULL, 0x0,
            "Average calculation duration", HFILL }
        },
        { &hf_rtls_aer_num_packets,
          { "Num Packets", "rtls.aer.num_packets",
            FT_UINT16, BASE_DEC, NULL, 0x0,
            "Number of packets used in average RSSI calculation", HFILL }
        },
        { &hf_rtls_aer_noise_floor,
          { "Noise Floor", "rtls.aer.noise_floor",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "Noise floor of the radio", HFILL }
        },
        { &hf_rtls_aer_classification,
          { "Classification", "rtls.aer.classification",
            FT_UINT8, BASE_DEC, VALS(rtls_ex_classification_vals), 0x0,
            "Millisecond granularity timestamp that represents local time in AP when message was sent", HFILL }
        },
        { &hf_rtls_aer_match_type,
          { "Match Type", "rtls.aer.match_type",
            FT_UINT8, BASE_HEX, NULL, 0x0,
            "Internal Aruba use", HFILL }
        },
        { &hf_rtls_aer_match_method,
          { "Match Method", "rtls.aer.match_method",
            FT_UINT8, BASE_HEX, NULL, 0x0,
            "Internal Aruba use", HFILL }
        },

        { &hf_rtls_cmr_messages,
          { "Messages", "rtls.cmr_messages",
            FT_UINT16, BASE_DEC, NULL, 0x0,
            "number of messages", HFILL }
        },

    };

    static gint *ett[] = {
        &ett_rtls,
        &ett_rtls_message,
        &ett_rtls_nack_flags,
    };


    /* Setup protocol expert items */
    static ei_register_info ei[] = {
        { &ei_rtls_undecoded,
          { "rtls.undecoded", PI_UNDECODED, PI_NOTE, "Undecoded Payload", EXPFILL }
        }
    };


    proto_rtls = proto_register_protocol("Real Time Location System", "RTLS", "rtls");
    rtls_handle = register_dissector("rtls", dissect_rtls, proto_rtls);

    proto_register_field_array(proto_rtls, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));

    expert_rtls = expert_register_protocol(proto_rtls);
    expert_register_field_array(expert_rtls, ei, array_length(ei));

}

void
proto_reg_handoff_rtls(void)
{
    // If this is ever streamed (transported over TCP) we need to add recursion checks
    dissector_add_for_decode_as_with_preference("udp.port", rtls_handle);
}

/*
 * Editor modelines  -  https://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:
 */
