/* packet-evrc.c
 * Routines for:
 *      EVRC EVRC-B EVRC-WB EVRC-NW EVRC-NW2K
 * RTP payload header dissection
 *
 * Copyright 2008, Michael Lum <michael.lum [AT] shaw.ca>
 * In association with Star Solutions
 *
 * Title                3GPP2                   Other
 *
 *   Enhanced Variable Rate Codec, Speech Service Options 3, 68, 70, 73 and 77
 *   for Wideband Spread Spectrum Digital Systems
 *                      3GPP2 C.S0014-E v1.0      TIA-127-?
 *
 * RFC 3558  https://tools.ietf.org/html/rfc3558
 * RFC 4788  https://tools.ietf.org/html/rfc4788
 * RFC 5188  https://tools.ietf.org/html/rfc5188
 * draft-agupta-payload-rtp-evrc-nw2k-00
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

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

#include <wsutil/str_util.h>

/* PROTOTYPES/FORWARDS */

void proto_register_evrc(void);
void proto_reg_handoff_evrc(void);

static dissector_handle_t evrc_handle;
static dissector_handle_t evrcb_handle;
static dissector_handle_t evrcwb_handle;
static dissector_handle_t evrcnw_handle;
static dissector_handle_t evrcnw2k_handle;
static dissector_handle_t evrc_legacy_handle;

static const value_string evrc_frame_type_vals[] = {
    { 0,        "Blank (0 bits)" },
    { 1,        "1/8 Rate (16 bits)" },
    { 2,        "Not valid (1/4 Rate : 40 bits)" },
    { 3,        "1/2 Rate (80 bits)" },
    { 4,        "Full Rate (171 bits; + 5 bits padding)" },
    { 5,        "Erasure (0 bits)" },
    { 0,        NULL }
};

static const value_string evrc_b_frame_type_vals[] = {
    { 0,        "Blank (0 bits)" },
    { 1,        "1/8 Rate (16 bits)" },
    { 2,        "1/4 Rate (40 bits)" },
    { 3,        "1/2 Rate (80 bits)" },
    { 4,        "Full Rate (171 bits; + 5 bits padding)" },
    { 5,        "Erasure (0 bits)" },
    { 0,        NULL }
};

static const value_string evrc_legacy_frame_type_vals[] = {
    { 0,        "Blank (0 bits)" },
    { 1,        "1/8 Rate (16 bits)" },
    { 3,        "1/2 Rate (80 bits)" },
    { 4,        "Full Rate (171 bits; + 5 bits padding)" },
    { 14,       "Erasure (0 bits)" },
    { 0,        NULL }
};

static const value_string evrc_mode_request_vals[] = {
    { 0,        "Rate Reduction 0 (Full Rate)" },
    { 1,        "Rate Reduction 1" },
    { 2,        "Rate Reduction 2" },
    { 3,        "Rate Reduction 3" },
    { 4,        "Rate Reduction 4" },
    { 0,        NULL }
};

static const value_string evrc_b_mode_request_vals[] = {
    { 0,        "Encoder Operating Point 0 (Full Rate)" },
    { 1,        "Encoder Operating Point 1" },
    { 2,        "Encoder Operating Point 2" },
    { 3,        "Encoder Operating Point 3" },
    { 4,        "Encoder Operating Point 4" },
    { 5,        "Encoder Operating Point 5" },
    { 6,        "Encoder Operating Point 6" },
    { 7,        "Encoder Operating Point 7 (1/2 rate max)" },
    { 0,        NULL }
};

static const value_string evrc_wb_mode_request_vals[] = {
    { 0,        "Encoder Operating Point 0 (Full Rate)" },
    { 1,        "Reserved" },
    { 2,        "Reserved" },
    { 3,        "Reserved" },
    { 4,        "Encoder Operating Point 4" },
    { 5,        "Reserved" },
    { 6,        "Reserved" },
    { 7,        "Encoder Operating Point 7 (1/2 rate max)" },
    { 0,        NULL }
};

static const value_string evrc_nw_mode_request_vals[] = {
    { 0,        "Encoder Operating Point 0 (EVRC-WB COP0)" },
    { 1,        "Encoder Operating Point 1 (EVRC-B COP0/EVRC-WB COP4)" },
    { 2,        "Encoder Operating Point 2 (EVRC-B COP2)" },
    { 3,        "Encoder Operating Point 3 (EVRC-B COP3)" },
    { 4,        "Encoder Operating Point 4 (EVRC-B COP4)" },
    { 5,        "Encoder Operating Point 5 (EVRC-B COP5)" },
    { 6,        "Encoder Operating Point 6 (EVRC-B COP6)" },
    { 7,        "Encoder Operating Point 7 (EVRC-B COP7/EVRC-WB COP7)" },
    { 0,        NULL }
};

static const value_string evrc_nw2k_mode_request_vals[] = {
    { 0,        "Encoder Operating Point 0 (EVRC-WB COP0)" },
    { 1,        "Encoder Operating Point 1 (EVRC-B COP0/EVRC-WB COP4)" },
    { 2,        "Encoder Operating Point 2 (EVRC-B COP2)" },
    { 3,        "Encoder Operating Point 3 (EVRC-B COP3/EVRC-NW2k)" },
    { 4,        "Encoder Operating Point 4 (EVRC-B COP4)" },
    { 5,        "Encoder Operating Point 5 (EVRC-B COP5)" },
    { 6,        "Encoder Operating Point 6 (EVRC-B COP6)" },
    { 7,        "Encoder Operating Point 7 (EVRC-B COP7/EVRC-WB COP7)" },
    { 0,        NULL }
};

static const true_false_string toc_further_entries_bit_vals = {
  "More ToC entries follow",
  "End of ToC entries"
};

static const true_false_string evrc_nw2k_enc_capability_bit_vals = {
  "Mode-0 wideband encoding capable",
  "Mode-0 wideband encoding incapable (i.e. narrowband encoding only)"
};

typedef enum
{
    EVRC_VARIANT_EVRC,
    EVRC_VARIANT_EVRC_B,
    EVRC_VARIANT_EVRC_WB,
    EVRC_VARIANT_EVRC_NW,
    EVRC_VARIANT_EVRC_NW2k,
    EVRC_VARIANT_EVRC_LEGACY
}
evrc_variant_t;


/* Initialize the protocol and registered fields */
static int proto_evrc;
static int proto_evrcb;
static int proto_evrcwb;
static int proto_evrcnw;
static int proto_evrcnw2k;
static int proto_evrc_legacy;

static int hf_evrc_reserved;
static int hf_evrc_reserved_2k;
static int hf_evrc_enc_capability_2k;
static int hf_evrc_interleave_length;
static int hf_evrc_interleave_index;
static int hf_evrc_mode_request;
static int hf_evrc_b_mode_request;
static int hf_evrc_wb_mode_request;
static int hf_evrc_nw_mode_request;
static int hf_evrc_nw2k_mode_request;
static int hf_evrc_frame_count;
static int hf_evrc_toc_frame_type_high;
static int hf_evrc_toc_frame_type_low;
static int hf_evrc_b_toc_frame_type_high;
static int hf_evrc_b_toc_frame_type_low;
static int hf_evrc_padding;
static int hf_evrc_speech_data;
static int hf_evrc_legacy_toc_fe_ind;
static int hf_evrc_legacy_toc_reduc_rate;
static int hf_evrc_legacy_toc_frame_type;

/* Initialize the subtree pointers */
static int ett_evrc;
static int ett_toc;

static expert_field ei_evrc_unknown_variant;

/*
 * Variables to allow for proper deletion of dissector registration when
 * the user changes values
 */
static bool legacy_pt_60;


static uint8_t
evrc_frame_type_to_octs(uint8_t frame_type)
{
    switch (frame_type)
    {
    default:
        break;

    case 1:     /* 1/8 rate */
        return 2;

    case 2:     /* 1/4 rate */
        return 5;

    case 3:     /* 1/2 rate */
        return 10;

    case 4:     /* full rate */
        return 22;
    }

    return 0;
}

/* GENERIC EVRC DISSECTOR FUNCTIONS */

static void
dissect_evrc_aux(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, evrc_variant_t evrc_variant)
{
    uint8_t                     oct;
    uint8_t                     frame_count;
    uint8_t                     i;
    uint32_t                    offset, saved_offset;
    bool                        further_entries;
    uint32_t                    len;
    proto_item                  *item = NULL;
    proto_tree                  *evrc_tree = NULL;
    proto_tree                  *toc_tree = NULL;
    int                         hf_mode_request;
    int                         hf_toc_frame_type_high;
    int                         hf_toc_frame_type_low;

    /*
     * assumed max number of speech frames based on
     * frame count being 5 bits + 1
     */
    uint8_t                     speech_data_len[0x20];


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

    if (!tree) return;

    offset = 0;
    memset(speech_data_len, 0, sizeof(speech_data_len));

    len = tvb_reported_length(tvb);

    item = proto_tree_add_item(tree, proto_evrc, tvb, 0, len, ENC_NA);

    evrc_tree = proto_item_add_subtree(item, ett_evrc);

    if (evrc_variant == EVRC_VARIANT_EVRC_LEGACY)
    {
        /* legacy 'payload type 60' draft-ietf-avt-evrc-07.txt header format */

        proto_tree_add_item(evrc_tree, hf_evrc_reserved, tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(evrc_tree, hf_evrc_interleave_length, tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(evrc_tree, hf_evrc_interleave_index, tvb, offset, 1, ENC_BIG_ENDIAN);

        offset++;

        frame_count = 0;
        further_entries = true;
        while (further_entries && (frame_count < sizeof(speech_data_len)) &&
            ((len - offset) > 0))
        {
            toc_tree =
                proto_tree_add_subtree_format(evrc_tree, tvb, offset, 1, ett_toc, NULL, "ToC [%u]", frame_count+1);

            proto_tree_add_item(toc_tree, hf_evrc_legacy_toc_fe_ind, tvb, offset, 1, ENC_BIG_ENDIAN);
            proto_tree_add_item(toc_tree, hf_evrc_legacy_toc_reduc_rate, tvb, offset, 1, ENC_BIG_ENDIAN);
            proto_tree_add_item(toc_tree, hf_evrc_legacy_toc_frame_type, tvb, offset, 1, ENC_BIG_ENDIAN);

            oct = tvb_get_uint8(tvb, offset);
            further_entries = (oct & 0x80) ? true : false;

            speech_data_len[frame_count] = evrc_frame_type_to_octs((uint8_t)(oct & 0x7f));

            frame_count++;
            offset++;
        }
    }
    else
    {
        /* RFC 3558 header format */

        switch (evrc_variant)
        {
        default:
            proto_tree_add_expert(evrc_tree, pinfo, &ei_evrc_unknown_variant, tvb, offset, len);
            return;

        case EVRC_VARIANT_EVRC:
            hf_mode_request = hf_evrc_mode_request;
            hf_toc_frame_type_high = hf_evrc_toc_frame_type_high;
            hf_toc_frame_type_low = hf_evrc_toc_frame_type_low;

            proto_tree_add_item(evrc_tree, hf_evrc_reserved, tvb, offset, 1, ENC_BIG_ENDIAN);
            break;

        case EVRC_VARIANT_EVRC_B:
            hf_mode_request = hf_evrc_b_mode_request;
            hf_toc_frame_type_high = hf_evrc_b_toc_frame_type_high;
            hf_toc_frame_type_low = hf_evrc_b_toc_frame_type_low;

            proto_tree_add_item(evrc_tree, hf_evrc_reserved, tvb, offset, 1, ENC_BIG_ENDIAN);
            break;

        case EVRC_VARIANT_EVRC_WB:
            hf_mode_request = hf_evrc_wb_mode_request;
            hf_toc_frame_type_high = hf_evrc_b_toc_frame_type_high;
            hf_toc_frame_type_low = hf_evrc_b_toc_frame_type_low;

            proto_tree_add_item(evrc_tree, hf_evrc_reserved, tvb, offset, 1, ENC_BIG_ENDIAN);
            break;

        case EVRC_VARIANT_EVRC_NW:
            hf_mode_request = hf_evrc_nw_mode_request;
            hf_toc_frame_type_high = hf_evrc_b_toc_frame_type_high;
            hf_toc_frame_type_low = hf_evrc_b_toc_frame_type_low;

            proto_tree_add_item(evrc_tree, hf_evrc_reserved, tvb, offset, 1, ENC_BIG_ENDIAN);
            break;

        case EVRC_VARIANT_EVRC_NW2k:
            hf_mode_request = hf_evrc_nw2k_mode_request;
            hf_toc_frame_type_high = hf_evrc_b_toc_frame_type_high;
            hf_toc_frame_type_low = hf_evrc_b_toc_frame_type_low;

            proto_tree_add_item(evrc_tree, hf_evrc_reserved_2k, tvb, offset, 1, ENC_BIG_ENDIAN);
            proto_tree_add_item(evrc_tree, hf_evrc_enc_capability_2k, tvb, offset, 1, ENC_BIG_ENDIAN);
            break;
        }

        proto_tree_add_item(evrc_tree, hf_evrc_interleave_length, tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(evrc_tree, hf_evrc_interleave_index, tvb, offset, 1, ENC_BIG_ENDIAN);

        offset++;

        proto_tree_add_item(evrc_tree, hf_mode_request, tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item_ret_uint8(evrc_tree, hf_evrc_frame_count, tvb, offset, 1, ENC_BIG_ENDIAN, &frame_count);

        /*
         * number of frames in PACKET is frame_count + 1
         */
        frame_count++;

        offset++;
        saved_offset = offset;

        toc_tree =
            proto_tree_add_subtree_format(evrc_tree, tvb, offset, -1, ett_toc, &item, "ToC - %u frame%s",
                frame_count, plurality(frame_count, "", "s"));

        i = 0;
        while ((i < frame_count) &&
            ((len - offset) > 0))
        {
            oct = tvb_get_uint8(tvb, offset);

            proto_tree_add_item(toc_tree, hf_toc_frame_type_high, tvb, offset, 1, ENC_BIG_ENDIAN);

            speech_data_len[i] = evrc_frame_type_to_octs((uint8_t)((oct & 0xf0) >> 4));

            i++;

            if (i < frame_count)
            {
                /* even number of frames */
                proto_tree_add_item(toc_tree, hf_toc_frame_type_low, tvb, offset, 1, ENC_BIG_ENDIAN);

                speech_data_len[i] = evrc_frame_type_to_octs((uint8_t)(oct & 0x0f));

                i++;
            }

            offset++;
        }

        if (frame_count & 0x01)
        {
            /* odd number of frames */
            proto_tree_add_item(toc_tree, hf_evrc_padding, tvb, offset-1, 1, ENC_BIG_ENDIAN);
        }

        proto_item_set_len(item, offset - saved_offset);
    }

    i = 0;
    while ((i < frame_count) &&
        ((len - offset) >= speech_data_len[i]))
    {
        proto_tree_add_bytes_format(evrc_tree, hf_evrc_speech_data, tvb, offset, speech_data_len[i], NULL, "Speech Data [%u]", i+1);

        offset += speech_data_len[i];
        i++;
    }
}

static int
dissect_evrc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
    dissect_evrc_aux(tvb, pinfo, tree, EVRC_VARIANT_EVRC);
    return tvb_captured_length(tvb);
}

static int
dissect_evrcb(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
    dissect_evrc_aux(tvb, pinfo, tree, EVRC_VARIANT_EVRC_B);
    return tvb_captured_length(tvb);
}

static int
dissect_evrcwb(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
    dissect_evrc_aux(tvb, pinfo, tree, EVRC_VARIANT_EVRC_WB);
    return tvb_captured_length(tvb);
}

static int
dissect_evrcnw(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
    dissect_evrc_aux(tvb, pinfo, tree, EVRC_VARIANT_EVRC_NW);
    return tvb_captured_length(tvb);
}

static int
dissect_evrcnw2k(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
    dissect_evrc_aux(tvb, pinfo, tree, EVRC_VARIANT_EVRC_NW2k);
    return tvb_captured_length(tvb);
}

static int
dissect_evrc_legacy(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
    dissect_evrc_aux(tvb, pinfo, tree, EVRC_VARIANT_EVRC_LEGACY);
    return tvb_captured_length(tvb);
}


/* Register the protocol with Wireshark */
void
proto_register_evrc(void)
{
    module_t            *evrc_module;

    /* Setup list of header fields */

    static hf_register_info hf[] =
    {
        { &hf_evrc_reserved,
            { "Reserved",               "evrc.reserved",
            FT_UINT8, BASE_HEX, NULL, 0xc0,
            "Reserved bits", HFILL }
        },
        { &hf_evrc_reserved_2k,
            { "Reserved",               "evrc.reserved",
            FT_UINT8, BASE_HEX, NULL, 0x80,
            "Reserved bits", HFILL }
        },
        { &hf_evrc_enc_capability_2k,
            { "Encoding Capability",               "evrc.enc_capability",
            FT_BOOLEAN, 8, TFS(&evrc_nw2k_enc_capability_bit_vals), 0x40,
            "Encoding Capability Identification", HFILL }
        },
        { &hf_evrc_interleave_length,
            { "Interleave Length",      "evrc.interleave_len",
            FT_UINT8, BASE_DEC, NULL, 0x38,
            "Interleave length bits", HFILL }
        },
        { &hf_evrc_interleave_index,
            { "Interleave Index",       "evrc.interleave_idx",
            FT_UINT8, BASE_DEC, NULL, 0x07,
            "Interleave index bits", HFILL }
        },
        { &hf_evrc_mode_request,
            { "Mode Request",           "evrc.mode_request",
            FT_UINT8, BASE_DEC, VALS(evrc_mode_request_vals), 0xe0,
            "Mode Request bits", HFILL }
        },
        { &hf_evrc_b_mode_request,
            { "Mode Request",           "evrc.b.mode_request",
            FT_UINT8, BASE_DEC, VALS(evrc_b_mode_request_vals), 0xe0,
            "Mode Request bits", HFILL }
        },
        { &hf_evrc_wb_mode_request,
            { "Mode Request",           "evrc.wb.mode_request",
            FT_UINT8, BASE_DEC, VALS(evrc_wb_mode_request_vals), 0xe0,
            "Mode Request bits", HFILL }
        },
        { &hf_evrc_nw_mode_request,
            { "Mode Request",           "evrc.nw.mode_request",
            FT_UINT8, BASE_DEC, VALS(evrc_nw_mode_request_vals), 0xe0,
            "Mode Request bits", HFILL }
        },
        { &hf_evrc_nw2k_mode_request,
            { "Mode Request",           "evrc.nw2k.mode_request",
            FT_UINT8, BASE_DEC, VALS(evrc_nw2k_mode_request_vals), 0xe0,
            "Mode Request bits", HFILL }
        },
        { &hf_evrc_frame_count,
            { "Frame Count (0 means 1 frame)",  "evrc.frame_count",
            FT_UINT8, BASE_DEC, NULL, 0x1f,
            "Frame Count bits, a value of 0 means 1 frame", HFILL }
        },
        { &hf_evrc_toc_frame_type_high,
            { "ToC Frame Type",         "evrc.toc.frame_type_hi",
            FT_UINT8, BASE_DEC, VALS(evrc_frame_type_vals), 0xf0,
            "ToC Frame Type bits", HFILL }
        },
        { &hf_evrc_toc_frame_type_low,
            { "ToC Frame Type",         "evrc.toc.frame_type_lo",
            FT_UINT8, BASE_DEC, VALS(evrc_frame_type_vals), 0x0f,
            "ToC Frame Type bits", HFILL }
        },
        { &hf_evrc_b_toc_frame_type_high,
            { "ToC Frame Type",         "evrc.b.toc.frame_type_hi",
            FT_UINT8, BASE_DEC, VALS(evrc_b_frame_type_vals), 0xf0,
            "ToC Frame Type bits", HFILL }
        },
        { &hf_evrc_b_toc_frame_type_low,
            { "ToC Frame Type",         "evrc.b.toc.frame_type_lo",
            FT_UINT8, BASE_DEC, VALS(evrc_b_frame_type_vals), 0x0f,
            "ToC Frame Type bits", HFILL }
        },
        { &hf_evrc_padding,
            { "Padding",                "evrc.padding",
            FT_UINT8, BASE_DEC, NULL, 0x0f,
            "Padding bits", HFILL }
        },
        { &hf_evrc_speech_data,
            { "Speech data",                "evrc.speech_data",
            FT_BYTES, BASE_NONE, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_evrc_legacy_toc_fe_ind,
            { "ToC Further Entries Indicator",  "evrc.legacy.toc.further_entries_ind",
            FT_BOOLEAN, 8, TFS(&toc_further_entries_bit_vals), 0x80,
            "ToC Further Entries Indicator bit", HFILL }
        },
        { &hf_evrc_legacy_toc_reduc_rate,
            { "ToC Reduced Rate",       "evrc.legacy.toc.reduced_rate",
            FT_UINT8, BASE_DEC, NULL, 0x40,
            "ToC Reduced Rate bits", HFILL }
        },
        { &hf_evrc_legacy_toc_frame_type,
            { "ToC Frame Type",         "evrc.legacy.toc.frame_type",
            FT_UINT8, BASE_DEC, VALS(evrc_legacy_frame_type_vals), 0x3f,
            "ToC Frame Type bits", HFILL }
        }
    };

    static ei_register_info ei[] = {
        { &ei_evrc_unknown_variant,
            { "evrc.unknown_variant", PI_UNDECODED, PI_WARN,
            "Variant Unknown/Unsupported - dissector bug/later version spec (report to wireshark.org)",
            EXPFILL }
        }
    };

    expert_module_t     *expert_evrc;

    /* Setup protocol subtree array */

    static int *ett[] =
    {
        &ett_evrc,
        &ett_toc
    };

    /* Register the protocol name and description */

    proto_evrc =
        proto_register_protocol("Enhanced Variable Rate Codec", "EVRC", "evrc");

    proto_evrcb =
        proto_register_protocol_in_name_only("Enhanced Variable Rate Codec B",
        "EVRC-B", "evrcb", proto_evrc, FT_PROTOCOL);
    proto_evrcwb =
        proto_register_protocol_in_name_only("Enhanced Variable Rate Codec - Wideband",
        "EVRC-WB", "evrcwb", proto_evrc, FT_PROTOCOL);
    proto_evrcnw =
        proto_register_protocol_in_name_only("Enhanced Variable Rate Codec - Narrowband-Wideband",
        "EVRC-NW", "evrcnw", proto_evrc, FT_PROTOCOL);
    proto_evrcnw2k =
        proto_register_protocol_in_name_only("Enhanced Variable Rate Codec - Narrowband-Wideband plus 2kpbs",
        "EVRC-NW2K", "evrcnw2k", proto_evrc, FT_PROTOCOL);
    proto_evrc_legacy =
        proto_register_protocol_in_name_only("Enhanced Variable Rate Codec (Legacy Encapsulation)",
        "EVRC (Legacy)", "evrc_legacy", proto_evrc, FT_PROTOCOL);
    proto_register_field_array(proto_evrc, hf, array_length(hf));

    proto_register_subtree_array(ett, array_length(ett));

    evrc_handle        = register_dissector("evrc", dissect_evrc, proto_evrc);
    evrcb_handle       = register_dissector("evrcb", dissect_evrcb, proto_evrcb);
    evrcwb_handle      = register_dissector("evrcwb", dissect_evrcwb, proto_evrcwb);
    evrcnw_handle      = register_dissector("evrcnw", dissect_evrcnw, proto_evrcnw);
    evrcnw2k_handle    = register_dissector("evrcnw2k", dissect_evrcnw2k, proto_evrcnw2k);
    evrc_legacy_handle = register_dissector("evrc_legacy", dissect_evrc_legacy, proto_evrc_legacy);

    expert_evrc =
        expert_register_protocol(proto_evrc);
    expert_register_field_array(expert_evrc, ei, array_length(ei));

    /*
     * setup for preferences
     */
    evrc_module = prefs_register_protocol(proto_evrc, proto_reg_handoff_evrc);

    prefs_register_bool_preference(evrc_module,
        "legacy_pt_60",
        "Add dissector for static payload type 60 as legacy EVRC (non-RFC3558)",
        "Whether the EVRC dissector should process payload type 60 as legacy EVRC packets",
        &legacy_pt_60);
}


void
proto_reg_handoff_evrc(void)
{
    static bool                 evrc_prefs_initialized = false;

    if (!evrc_prefs_initialized)
    {
        /* header-full mime types */
        dissector_add_string("rtp_dyn_payload_type",  "EVRC", evrc_handle);
        dissector_add_string("rtp_dyn_payload_type",  "EVRCB", evrcb_handle);
        dissector_add_string("rtp_dyn_payload_type",  "EVRCWB", evrcwb_handle);
        dissector_add_string("rtp_dyn_payload_type",  "EVRCNW", evrcnw_handle);
        dissector_add_string("rtp_dyn_payload_type",  "EVRCNW2K", evrcnw2k_handle);

        dissector_add_for_decode_as("rtp.pt", evrc_handle);
        dissector_add_for_decode_as("rtp.pt", evrcb_handle);
        dissector_add_for_decode_as("rtp.pt", evrcwb_handle);
        dissector_add_for_decode_as("rtp.pt", evrcnw_handle);
        dissector_add_for_decode_as("rtp.pt", evrcnw2k_handle);
        /* Since the draft legacy encapsulation only appears on PT 60, not
         * adding it to decode as */
        /* dissector_add_for_decode_as("rtp.pt", evrc_legacy_handle); */
        evrc_prefs_initialized = true;
    }
    else
    {
        dissector_delete_uint("rtp.pt", 60, evrc_legacy_handle);
    }

    if (legacy_pt_60)
    {
        dissector_add_uint("rtp.pt", 60, evrc_legacy_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:
 */
