/* Routines for NR RLC disassembly
 *
 * Pascal Quantin
 *
 * 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/exceptions.h>
#include <epan/expert.h>
#include <epan/prefs.h>
#include <epan/proto_data.h>
#include <epan/reassemble.h>

#include "packet-rlc-nr.h"
#include "packet-pdcp-nr.h"


/* Described in:
 * 3GPP TS 38.322 NR; Radio Link Control (RLC) protocol specification v15.0.0
 */

/* TODO:
- add sequence analysis
- take configuration of reordering timer, and stop reassembly if timeout exceeded?
- add tap info (for stats / SN graph)
*/

void proto_register_rlc_nr(void);
void proto_reg_handoff_rlc_nr(void);

/********************************/
/* Preference settings          */

/* By default do call PDCP/RRC dissectors for SDU data */
static gboolean global_rlc_nr_call_pdcp_for_srb = TRUE;

enum pdcp_for_drb { PDCP_drb_off, PDCP_drb_SN_12, PDCP_drb_SN_18, PDCP_drb_SN_signalled};
static const enum_val_t pdcp_drb_col_vals[] = {
    {"pdcp-drb-off",           "Off",                 PDCP_drb_off},
    {"pdcp-drb-sn-12",         "12-bit SN",           PDCP_drb_SN_12},
    {"pdcp-drb-sn-18",         "18-bit SN",           PDCP_drb_SN_18},
    {"pdcp-drb-sn-signalling", "Use signalled value", PDCP_drb_SN_signalled},
    {NULL, NULL, -1}
};
/* Separate config for UL/DL */
static gint global_rlc_nr_call_pdcp_for_ul_drb = (gint)PDCP_drb_off;
static gint global_rlc_nr_call_pdcp_for_dl_drb = (gint)PDCP_drb_off;


static gboolean global_rlc_nr_call_rrc_for_ccch = TRUE;

/* Preference to expect RLC headers without payloads */
static gboolean global_rlc_nr_headers_expected = FALSE;

/* Attempt reassembly. */
static gboolean global_rlc_nr_reassemble_um_pdus = FALSE;
static gboolean global_rlc_nr_reassemble_am_pdus = TRUE;

/* Tree storing UE related parameters (ueid, drbid) -> pdcp_bearer_parameters */
static wmem_tree_t *ue_parameters_tree;

/* Table storing starting frame for reassembly session during first pass */
/* Key is (ueId, direction, bearertype, bearerid, sn) */
static wmem_tree_t *reassembly_start_table;

/* Table storing starting frame for reassembly session during subsequent passes */
/* Key is (ueId, direction, bearertype, bearerid, sn, frame) */
static wmem_tree_t *reassembly_start_table_stored;

/**************************************************/
/* Initialize the protocol and registered fields. */
int proto_rlc_nr = -1;

extern int proto_pdcp_nr;

static dissector_handle_t pdcp_nr_handle;
static dissector_handle_t nr_rrc_bcch_bch;
static dissector_handle_t nr_rrc_bcch_dl_sch;
static dissector_handle_t nr_rrc_pcch;
static dissector_handle_t nr_rrc_ul_ccch;
static dissector_handle_t nr_rrc_ul_ccch1;
static dissector_handle_t nr_rrc_dl_ccch;


/* Decoding context */
static int hf_rlc_nr_context = -1;
static int hf_rlc_nr_context_mode = -1;
static int hf_rlc_nr_context_direction = -1;
static int hf_rlc_nr_context_ueid = -1;
static int hf_rlc_nr_context_bearer_type = -1;
static int hf_rlc_nr_context_bearer_id = -1;
static int hf_rlc_nr_context_pdu_length = -1;
static int hf_rlc_nr_context_sn_length = -1;

/* Transparent mode fields */
static int hf_rlc_nr_tm = -1;
static int hf_rlc_nr_tm_data = -1;

/* Unacknowledged mode fields */
static int hf_rlc_nr_um = -1;
static int hf_rlc_nr_um_header = -1;
static int hf_rlc_nr_um_si = -1;
static int hf_rlc_nr_um_reserved = -1;
static int hf_rlc_nr_um_sn6 = -1;
static int hf_rlc_nr_um_sn12 = -1;
static int hf_rlc_nr_um_so = -1;
static int hf_rlc_nr_um_data = -1;

/* Acknowledged mode fields */
static int hf_rlc_nr_am = -1;
static int hf_rlc_nr_am_header = -1;
static int hf_rlc_nr_am_data_control = -1;
static int hf_rlc_nr_am_p = -1;
static int hf_rlc_nr_am_si = -1;
static int hf_rlc_nr_am_sn12 = -1;
static int hf_rlc_nr_am_sn18 = -1;
static int hf_rlc_nr_am_reserved = -1;
static int hf_rlc_nr_am_so = -1;
static int hf_rlc_nr_am_data = -1;

/* Control fields */
static int hf_rlc_nr_am_cpt = -1;
static int hf_rlc_nr_am_ack_sn = -1;
static int hf_rlc_nr_am_e1 = -1;
static int hf_rlc_nr_am_e2 = -1;
static int hf_rlc_nr_am_e3 = -1;
static int hf_rlc_nr_am_nack_sn = -1;
static int hf_rlc_nr_am_so_start = -1;
static int hf_rlc_nr_am_so_end = -1;
static int hf_rlc_nr_am_nack_range = -1;
static int hf_rlc_nr_am_nacks = -1;

static int hf_rlc_nr_header_only = -1;

static int hf_rlc_nr_fragments = -1;
static int hf_rlc_nr_fragment = -1;
static int hf_rlc_nr_fragment_overlap = -1;
static int hf_rlc_nr_fragment_overlap_conflict = -1;
static int hf_rlc_nr_fragment_multiple_tails = -1;
static int hf_rlc_nr_fragment_too_long_fragment = -1;
static int hf_rlc_nr_fragment_error = -1;
static int hf_rlc_nr_fragment_count = -1;
static int hf_rlc_nr_reassembled_in = -1;
static int hf_rlc_nr_reassembled_length = -1;
static int hf_rlc_nr_reassembled_data = -1;



/* Subtrees. */
static int ett_rlc_nr = -1;
static int ett_rlc_nr_context = -1;
static int ett_rlc_nr_um_header = -1;
static int ett_rlc_nr_am_header = -1;
static int ett_rlc_nr_fragments = -1;
static int ett_rlc_nr_fragment = -1;


static const fragment_items rlc_nr_frag_items = {
  &ett_rlc_nr_fragment,
  &ett_rlc_nr_fragments,
  &hf_rlc_nr_fragments,
  &hf_rlc_nr_fragment,
  &hf_rlc_nr_fragment_overlap,
  &hf_rlc_nr_fragment_overlap_conflict,
  &hf_rlc_nr_fragment_multiple_tails,
  &hf_rlc_nr_fragment_too_long_fragment,
  &hf_rlc_nr_fragment_error,
  &hf_rlc_nr_fragment_count,
  &hf_rlc_nr_reassembled_in,
  &hf_rlc_nr_reassembled_length,
  &hf_rlc_nr_reassembled_data,
  "RLC PDU fragments"
};


static expert_field ei_rlc_nr_context_mode = EI_INIT;
static expert_field ei_rlc_nr_am_nack_sn = EI_INIT;
static expert_field ei_rlc_nr_am_nack_sn_ahead_ack = EI_INIT;
static expert_field ei_rlc_nr_am_nack_sn_ack_same = EI_INIT;
static expert_field ei_rlc_nr_am_nack_range = EI_INIT;
static expert_field ei_rlc_nr_am_cpt = EI_INIT;
static expert_field ei_rlc_nr_um_data_no_data = EI_INIT;
static expert_field ei_rlc_nr_am_data_no_data = EI_INIT;
static expert_field ei_rlc_nr_am_nack_sn_partial = EI_INIT;
static expert_field ei_rlc_nr_bytes_after_status_pdu_complete = EI_INIT;
static expert_field ei_rlc_nr_um_sn = EI_INIT;
static expert_field ei_rlc_nr_am_sn = EI_INIT;
static expert_field ei_rlc_nr_header_only = EI_INIT;
static expert_field ei_rlc_nr_reserved_bits_not_zero = EI_INIT;
static expert_field ei_rlc_nr_no_per_frame_info = EI_INIT;
static expert_field ei_rlc_nr_unknown_udp_framing_tag = EI_INIT;

/* Value-strings */
static const value_string direction_vals[] =
{
    { DIRECTION_UPLINK,   "Uplink" },
    { DIRECTION_DOWNLINK, "Downlink" },
    { 0, NULL }
};

static const value_string rlc_mode_short_vals[] =
{
    { RLC_TM_MODE, "TM" },
    { RLC_UM_MODE, "UM" },
    { RLC_AM_MODE, "AM" },
    { 0, NULL }
};

static const value_string rlc_mode_vals[] =
{
    { RLC_TM_MODE, "Transparent Mode" },
    { RLC_UM_MODE, "Unacknowledged Mode" },
    { RLC_AM_MODE, "Acknowledged Mode" },
    { 0, NULL }
};

static const value_string rlc_bearer_type_vals[] =
{
    { BEARER_TYPE_CCCH,        "CCCH" },
    { BEARER_TYPE_BCCH_BCH,    "BCCH BCH" },
    { BEARER_TYPE_PCCH,        "PCCH" },
    { BEARER_TYPE_SRB,         "SRB" },
    { BEARER_TYPE_DRB,         "DRB" },
    { BEARER_TYPE_BCCH_DL_SCH, "BCCH DL-SCH" },
    { 0, NULL }
};

static const value_string seg_info_vals[] =
{
    { 0, "Data field contains all bytes of an RLC SDU" },
    { 1, "Data field contains the first segment of an RLC SDU" },
    { 2, "Data field contains the last segment of an RLC SDU" },
    { 3, "Data field contains neither the first nor last segment of an RLC SDU" },
    { 0, NULL }
};

static const true_false_string data_or_control_vals =
{
    "Data PDU",
    "Control PDU"
};

static const true_false_string polling_bit_vals =
{
    "Status report is requested",
    "Status report not requested"
};

static const value_string control_pdu_type_vals[] =
{
    { 0, "STATUS PDU" },
    { 0, NULL }
};

static const true_false_string am_e1_vals =
{
    "A set of NACK_SN, E1, E2 and E3 follows",
    "A set of NACK_SN, E1, E2 and E3 does not follow"
};

static const true_false_string am_e2_vals =
{
    "A set of SOstart and SOend follows for this NACK_SN",
    "A set of SOstart and SOend does not follow for this NACK_SN"
};

static const true_false_string am_e3_vals =
{
    "NACK range field follows for this NACK_SN",
    "NACK range field does not follow for this NACK_SN"
};

static const true_false_string header_only_vals =
{
    "RLC PDU Headers only",
    "RLC PDU Headers and body present"
};

/* Reassembly state */
static reassembly_table pdu_reassembly_table;

static guint pdu_hash(gconstpointer k)
{
    return GPOINTER_TO_UINT(k);
}

static gint pdu_equal(gconstpointer k1, gconstpointer k2)
{
    return k1 == k2;
}

static gpointer pdu_temporary_key(const packet_info *pinfo _U_, const guint32 id _U_, const void *data)
{
    return (gpointer)data;
}

static gpointer pdu_persistent_key(const packet_info *pinfo _U_, const guint32 id _U_,
                                         const void *data)
{
    return (gpointer)data;
}

static void pdu_free_temporary_key(gpointer ptr _U_)
{
}

static void pdu_free_persistent_key(gpointer ptr _U_)
{
}

static reassembly_table_functions pdu_reassembly_table_functions =
{
    pdu_hash,
    pdu_equal,
    pdu_temporary_key,
    pdu_persistent_key,
    pdu_free_temporary_key,
    pdu_free_persistent_key
};


/********************************************************/
/* Forward declarations & functions                     */
static void dissect_rlc_nr_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gboolean is_udp_framing);


/* Write the given formatted text to:
   - the info column
   - the top-level RLC PDU item
   - another subtree item (if supplied) */
static void write_pdu_label_and_info(proto_item *pdu_ti, proto_item *sub_ti,
                                     packet_info *pinfo, const char *format, ...)
{
    #define MAX_INFO_BUFFER 256
    static char info_buffer[MAX_INFO_BUFFER];

    va_list ap;

    va_start(ap, format);
    vsnprintf(info_buffer, MAX_INFO_BUFFER, format, ap);
    va_end(ap);

    /* Add to indicated places */
    col_append_str(pinfo->cinfo, COL_INFO, info_buffer);
    proto_item_append_text(pdu_ti, "%s", info_buffer);
    if (sub_ti != NULL) {
        proto_item_append_text(sub_ti, "%s", info_buffer);
    }
}

/* Version of function above, where no vsnprintf() call needed
   - the info column
   - the top-level RLC PDU item
   - another subtree item (if supplied) */
static void write_pdu_label_and_info_literal(proto_item *pdu_ti, proto_item *sub_ti,
                                             packet_info *pinfo, const char *info_buffer)
{
    /* Add to indicated places */
    col_append_str(pinfo->cinfo, COL_INFO, info_buffer);
    proto_item_append_text(pdu_ti, "%s", info_buffer);
    if (sub_ti != NULL) {
        proto_item_append_text(sub_ti, "%s", info_buffer);
    }
}

/* Show in the info column how many bytes are in the UM/AM PDU, and indicate
   whether or not the beginning and end are included in this packet */
static void show_PDU_in_info(packet_info *pinfo, proto_item *top_ti,
                             gint32 length, guint8 seg_info)
{
    /* Reflect this PDU in the info column */
    if (length > 0) {
        write_pdu_label_and_info(top_ti, NULL, pinfo,
                                 "  %s%u-byte%s%s",
                                 (seg_info & 0x02) ? ".." : "[",
                                 length,
                                 (length > 1) ? "s" : "",
                                 (seg_info & 0x01) ? ".." : "]");
    } else {
        write_pdu_label_and_info(top_ti, NULL, pinfo,
                                 "  %sunknown-bytes%s",
                                 (seg_info & 0x02) ? ".." : "[",
                                 (seg_info & 0x01) ? ".." : "]");
    }
}


/* Show an SDU. If configured, pass to PDCP/RRC dissector */
static void show_PDU_in_tree(packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, gint offset, gint length,
                             rlc_nr_info *rlc_info, guint32 seg_info, gboolean is_reassembled)
{
    wmem_tree_key_t key[2];
    guint32 id;
    pdcp_bearer_parameters *params;

    /* Add raw data (according to mode) */
    if (!is_reassembled) {
        proto_tree_add_item(tree, (rlc_info->rlcMode == RLC_AM_MODE) ?
                            hf_rlc_nr_am_data : hf_rlc_nr_um_data,
                            tvb, offset, length, ENC_NA);
    }

    if ((seg_info == 0) || is_reassembled) {  /* i.e. contains whole SDU */
        if ((global_rlc_nr_call_pdcp_for_srb && (rlc_info->bearerType == BEARER_TYPE_SRB)) ||
            ((rlc_info->bearerType == BEARER_TYPE_DRB) &&
                 (((rlc_info->direction == PDCP_NR_DIRECTION_UPLINK) &&   (global_rlc_nr_call_pdcp_for_ul_drb != PDCP_drb_off)) ||
                  ((rlc_info->direction == PDCP_NR_DIRECTION_DOWNLINK) && (global_rlc_nr_call_pdcp_for_dl_drb != PDCP_drb_off)))))
        {

            /* Get whole PDU into tvb */
            tvbuff_t *pdcp_tvb = tvb_new_subset_length(tvb, offset, length);

            /* Reuse or allocate struct */
            struct pdcp_nr_info *p_pdcp_nr_info;
            p_pdcp_nr_info = (pdcp_nr_info *)p_get_proto_data(wmem_file_scope(), pinfo, proto_pdcp_nr, 0);
            if (p_pdcp_nr_info == NULL) {
                p_pdcp_nr_info = wmem_new0(wmem_file_scope(), pdcp_nr_info);
                /* Store info in packet */
                p_add_proto_data(wmem_file_scope(), pinfo, proto_pdcp_nr, 0, p_pdcp_nr_info);
            }

            /* Fill in struct params. */
            p_pdcp_nr_info->direction = rlc_info->direction;
            p_pdcp_nr_info->ueid = rlc_info->ueid;

            gint seqnum_len;

            switch (rlc_info->bearerType) {
                case BEARER_TYPE_SRB:
                    p_pdcp_nr_info->plane = NR_SIGNALING_PLANE;
                    p_pdcp_nr_info->bearerType = Bearer_DCCH;
                    p_pdcp_nr_info->seqnum_length = 12;
                    break;

                case BEARER_TYPE_DRB:
                    p_pdcp_nr_info->plane = NR_USER_PLANE;
                    p_pdcp_nr_info->bearerType = Bearer_DCCH;

                    seqnum_len = (rlc_info->direction == PDCP_NR_DIRECTION_UPLINK) ?
                                       global_rlc_nr_call_pdcp_for_ul_drb :
                                       global_rlc_nr_call_pdcp_for_dl_drb;
                    switch (seqnum_len) {
                        case PDCP_drb_SN_12:
                            p_pdcp_nr_info->seqnum_length = 12;
                            break;
                        case PDCP_drb_SN_18:
                            p_pdcp_nr_info->seqnum_length = 18;
                            break;
                        case PDCP_drb_SN_signalled:
                            /* Use whatever was signalled (i.e. in RRC) */
                            id = (rlc_info->bearerId << 16) | rlc_info->ueid;
                            key[0].length = 1;
                            key[0].key = &id;
                            key[1].length = 0;
                            key[1].key = NULL;

                            /* Look up configured params for this PDCP DRB. */
                            params = (pdcp_bearer_parameters *)wmem_tree_lookup32_array_le(ue_parameters_tree, key);
                            if (params && (params->id != id)) {
                                params = NULL;
                            }
                            if (params) {
                                if (p_pdcp_nr_info->direction == DIRECTION_UPLINK) {
                                    p_pdcp_nr_info->seqnum_length = params->pdcp_sn_bits_ul;
                                    if (params->pdcp_sdap_ul) {
                                        p_pdcp_nr_info->sdap_header &= PDCP_NR_UL_SDAP_HEADER_PRESENT;
                                    }
                                }
                                else {
                                    p_pdcp_nr_info->seqnum_length = params->pdcp_sn_bits_dl;
                                    if (params->pdcp_sdap_dl) {
                                        p_pdcp_nr_info->sdap_header &= PDCP_NR_DL_SDAP_HEADER_PRESENT;
                                    }
                                }
                                p_pdcp_nr_info->maci_present = params->pdcp_integrity;
                                p_pdcp_nr_info->ciphering_disabled = params->pdcp_ciphering_disabled;
                            }
                            break;

                    }
                    break;

                default:
                    /* Shouldn't get here */
                    return;
            }
            p_pdcp_nr_info->bearerId = rlc_info->bearerId;

            p_pdcp_nr_info->rohc.rohc_compression = FALSE;
            p_pdcp_nr_info->is_retx = FALSE;
            p_pdcp_nr_info->pdu_length = length;

            TRY {
                call_dissector_only(pdcp_nr_handle, pdcp_tvb, pinfo, tree, NULL);
            }
            CATCH_ALL {
            }
            ENDTRY
        }
    }
}

/***************************************************/
/* Transparent mode PDU. Call RRC if configured to */
static void dissect_rlc_nr_tm(tvbuff_t *tvb, packet_info *pinfo,
                              proto_tree *tree,
                              int offset,
                              rlc_nr_info *p_rlc_nr_info,
                              proto_item *top_ti)
{
    proto_item *raw_tm_ti;
    proto_item *tm_ti;

    /* Create hidden TM root */
    tm_ti = proto_tree_add_string_format(tree, hf_rlc_nr_tm,
                                         tvb, offset, 0, "", "TM");
    proto_item_set_hidden(tm_ti);

    /* Remaining bytes are all data */
    raw_tm_ti = proto_tree_add_item(tree, hf_rlc_nr_tm_data, tvb, offset, -1, ENC_NA);
    if (!global_rlc_nr_call_rrc_for_ccch) {
        write_pdu_label_and_info(top_ti, NULL, pinfo,
                                 "                               [%u-bytes]", tvb_reported_length_remaining(tvb, offset));
    }

    if (global_rlc_nr_call_rrc_for_ccch) {
        tvbuff_t *rrc_tvb = tvb_new_subset_remaining(tvb, offset);
        volatile dissector_handle_t protocol_handle;

        switch (p_rlc_nr_info->bearerType) {
        case BEARER_TYPE_BCCH_BCH:
            protocol_handle = nr_rrc_bcch_bch;
            break;
        case BEARER_TYPE_BCCH_DL_SCH:
            protocol_handle = nr_rrc_bcch_dl_sch;
            break;
        case BEARER_TYPE_PCCH:
            protocol_handle = nr_rrc_pcch;
            break;
        case BEARER_TYPE_CCCH:
            if (p_rlc_nr_info->direction == DIRECTION_UPLINK) {
                protocol_handle = (tvb_reported_length(rrc_tvb) == 8) ?
                                    nr_rrc_ul_ccch1 : nr_rrc_ul_ccch;
            } else {
                protocol_handle = nr_rrc_dl_ccch;
            }
            break;
        case BEARER_TYPE_SRB:
        case BEARER_TYPE_DRB:
        default:
                /* Shouldn't happen, just return... */
                return;
        }

        /* Hide raw view of bytes */
        proto_item_set_hidden(raw_tm_ti);

        /* Call it (catch exceptions) */
        TRY {
            call_dissector_only(protocol_handle, rrc_tvb, pinfo, tree, NULL);
        }
        CATCH_ALL {
        }
        ENDTRY
    }
}

/* Look up / set the frame thought to be the start segment of this RLC PDU. */
/* N.B. this algorithm will not be correct in all cases, but is good enough to be useful.. */
static guint32 get_reassembly_start_frame(packet_info *pinfo, guint32 seg_info,
                                          rlc_nr_info *p_rlc_nr_info, guint32 sn)
{
    guint32 frame_id = 0;

    guint32 key_values[] = { p_rlc_nr_info->ueid,
                             p_rlc_nr_info->direction,
                             p_rlc_nr_info->bearerType,
                             p_rlc_nr_info->bearerId,
                             sn,
                             pinfo->num          /* N.B. only used for subsquent/_stored table */
                           };

    /* Is this the first segment of SN? */
    gboolean first_segment = (seg_info & 0x2) == 0;

    /* Set Key. */
    wmem_tree_key_t key[2];
    key[0].length = 5;       /* Ignoring this frame num */
    key[0].key = key_values;
    key[1].length = 0;
    key[1].key = NULL;

    guint32 *p_frame_id = NULL;

    if (!PINFO_FD_VISITED(pinfo)) {
        /* On first pass, maintain reassembly_start_table. */

        /* Look for existing entry. */
        p_frame_id = (guint32*)wmem_tree_lookup32_array(reassembly_start_table, key);


        if (first_segment) {
            /* Let it start from here */
            wmem_tree_insert32_array(reassembly_start_table, key, GUINT_TO_POINTER(pinfo->num));
            frame_id = pinfo->num;
        }
        else {
            if (p_frame_id) {
                /* Use existing entry (or zero) if not found */
                frame_id = GPOINTER_TO_UINT(p_frame_id);
            }
        }

        /* Store this result for subsequent passes. Don't store 0 though. */
        if (frame_id) {
            key[0].length = 6;
            wmem_tree_insert32_array(reassembly_start_table_stored, key, GUINT_TO_POINTER(frame_id));
        }
    }
    else {
        /* For subsequent passes, use stored value */
        key[0].length = 6;  /* i.e. include this framenum in key */
        p_frame_id = (guint32*)wmem_tree_lookup32_array(reassembly_start_table_stored, key);
        if (p_frame_id) {
            /* Use found value */
            frame_id = GPOINTER_TO_UINT(p_frame_id);
        }
    }

    return frame_id;
}

static void reassembly_frame_complete(packet_info *pinfo,
                                      rlc_nr_info *p_rlc_nr_info, guint32 sn)
{
    if (!PINFO_FD_VISITED(pinfo)) {
        guint32 key_values[] = { p_rlc_nr_info->ueid,
                                 p_rlc_nr_info->direction,
                                 p_rlc_nr_info->bearerType,
                                 p_rlc_nr_info->bearerId,
                                 sn
                               };

        /* Set Key. */
        wmem_tree_key_t key[2];
        key[0].length = 4;       /* Ignoring this frame num */
        key[0].key = key_values;
        key[1].length = 0;
        key[1].key = NULL;

        /* Clear entry out */
        wmem_tree_insert32_array(reassembly_start_table, key, GUINT_TO_POINTER(0));
    }
}


/***************************************************/
/* Unacknowledged mode PDU                         */
static void dissect_rlc_nr_um(tvbuff_t *tvb, packet_info *pinfo,
                              proto_tree *tree, int offset,
                              rlc_nr_info *p_rlc_nr_info, proto_item *top_ti)
{
    guint32 seg_info, sn;
    guint64 reserved;
    proto_item *um_ti;
    proto_tree *um_header_tree;
    proto_item *um_header_ti;
    gboolean is_truncated = FALSE;
    proto_item *truncated_ti;
    proto_item *reserved_ti;
    int start_offset = offset;
    guint32 so = 0;

    /* Hidden UM root */
    um_ti = proto_tree_add_string_format(tree, hf_rlc_nr_um,
                                         tvb, offset, 0, "", "UM");
    proto_item_set_hidden(um_ti);

    /* Add UM header subtree */
    um_header_ti = proto_tree_add_string_format(tree, hf_rlc_nr_um_header,
                                                tvb, offset, 0,
                                                "", "UM header");
    um_header_tree = proto_item_add_subtree(um_header_ti,
                                            ett_rlc_nr_um_header);

    /* Segmentation Info */
    proto_tree_add_item_ret_uint(um_header_tree, hf_rlc_nr_um_si, tvb, offset, 1, ENC_BIG_ENDIAN, &seg_info);
    if (seg_info == 0) {
        /* Have all bytes of SDU, so no SN. */
        reserved_ti = proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_nr_um_reserved,
                                                  tvb, (offset<<3)+2, 6, &reserved, ENC_BIG_ENDIAN);
        offset++;
        if (reserved) {
            expert_add_info(pinfo, reserved_ti, &ei_rlc_nr_reserved_bits_not_zero);
        }
        write_pdu_label_and_info(top_ti, um_header_ti, pinfo, "                             ");
    } else {
        /* Add sequence number */
        if (p_rlc_nr_info->sequenceNumberLength == UM_SN_LENGTH_6_BITS) {
            proto_tree_add_item_ret_uint(um_header_tree, hf_rlc_nr_um_sn6, tvb, offset, 1, ENC_BIG_ENDIAN, &sn);
            offset++;
        } else if (p_rlc_nr_info->sequenceNumberLength == UM_SN_LENGTH_12_BITS) {
            reserved_ti = proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_nr_um_reserved, tvb,
                                                      (offset<<3)+2, 2, &reserved, ENC_BIG_ENDIAN);
            if (reserved) {
                expert_add_info(pinfo, reserved_ti, &ei_rlc_nr_reserved_bits_not_zero);
            }
            proto_tree_add_item_ret_uint(um_header_tree, hf_rlc_nr_um_sn12, tvb, offset, 2, ENC_BIG_ENDIAN, &sn);
            offset += 2;
        } else {
            /* Invalid length of sequence number */
            proto_tree_add_expert_format(um_header_tree, pinfo, &ei_rlc_nr_um_sn, tvb, 0, 0,
                                         "Invalid sequence number length (%u bits)",
                                         p_rlc_nr_info->sequenceNumberLength);
            return;
        }
        if (seg_info >= 2) {
            /* Segment offset */
            proto_tree_add_item_ret_uint(um_header_tree, hf_rlc_nr_um_so, tvb, offset, 2, ENC_BIG_ENDIAN, &so);
            offset += 2;
            write_pdu_label_and_info(top_ti, um_header_ti, pinfo, "            SN=%-6u SO=%-4u", sn, so);
        } else {
            /* Seg info is 1, so start of SDU - no SO */
            write_pdu_label_and_info(top_ti, um_header_ti, pinfo, "            SN=%-6u        ", sn);
        }
    }
    /* End of header */
    proto_item_set_len(um_header_ti, offset-start_offset);

    if (global_rlc_nr_headers_expected) {
        /* There might not be any data, if only headers (plus control data) were logged */
        is_truncated = (tvb_captured_length_remaining(tvb, offset) == 0);
        truncated_ti = proto_tree_add_boolean(tree, hf_rlc_nr_header_only, tvb, 0, 0,
                                              is_truncated);
        if (is_truncated) {
            proto_item_set_generated(truncated_ti);
            expert_add_info(pinfo, truncated_ti, &ei_rlc_nr_header_only);
            show_PDU_in_info(pinfo, top_ti, p_rlc_nr_info->pduLength - offset, seg_info);
            return;
        } else {
            proto_item_set_hidden(truncated_ti);
        }
    }

    /* Data */

    /* Handle any reassembly. */
    tvbuff_t *next_tvb = NULL;
    if (global_rlc_nr_reassemble_um_pdus && seg_info && tvb_reported_length_remaining(tvb, offset) > 0) {
        /* Set fragmented flag. */
        gboolean save_fragmented = pinfo->fragmented;
        pinfo->fragmented = TRUE;
        fragment_head *fh;
        gboolean more_frags = seg_info & 0x01;
        guint32 id = get_reassembly_start_frame(pinfo, seg_info, p_rlc_nr_info, sn);                        /* Leave 19 bits for SN - overlaps with other fields but room to overflow into msb */
        if (id != 0) {
            fh = fragment_add(&pdu_reassembly_table, tvb, offset, pinfo,
                              id,                                         /* id */
                              GUINT_TO_POINTER(id),                       /* data */
                              so,                                         /* frag_offset */
                              tvb_reported_length_remaining(tvb, offset), /* frag_data_len */
                              more_frags                                  /* more_frags */
                              );

            gboolean update_col_info = TRUE;
            next_tvb = process_reassembled_data(tvb, offset, pinfo, "Reassembled RLC SDU",
                                                fh, &rlc_nr_frag_items,
                                                &update_col_info, tree);
            pinfo->fragmented = save_fragmented;
        }
    }

    if (tvb_reported_length_remaining(tvb, offset) > 0) {
        show_PDU_in_tree(pinfo, tree, tvb, offset, tvb_reported_length_remaining(tvb, offset),
                         p_rlc_nr_info, seg_info, FALSE);
        show_PDU_in_info(pinfo, top_ti, tvb_reported_length_remaining(tvb, offset), seg_info);

        /* Also add any reassembled PDU */
        if (next_tvb) {
            add_new_data_source(pinfo, next_tvb, "Reassembled RLC-NR PDU");
            show_PDU_in_tree(pinfo, tree, next_tvb, 0, tvb_captured_length(next_tvb),
                             p_rlc_nr_info, seg_info, TRUE);

            /* Note that PDU is now completed (so won't try to add to it) */
            reassembly_frame_complete(pinfo, p_rlc_nr_info, sn);
        }
    } else if (!global_rlc_nr_headers_expected) {
        /* Report that expected data was missing (unless we know it might happen) */
        expert_add_info(pinfo, um_header_ti, &ei_rlc_nr_um_data_no_data);
    }
}



/* Dissect an AM STATUS PDU */
static void dissect_rlc_nr_am_status_pdu(tvbuff_t *tvb,
                                         packet_info *pinfo,
                                         proto_tree *tree,
                                         proto_item *status_ti,
                                         int offset,
                                         proto_item *top_ti,
                                         rlc_nr_info *p_rlc_nr_info)
{
    guint8     sn_size, reserved_bits1, reserved_bits2;
    guint32    cpt, sn_limit, nack_count = 0;
    guint64    ack_sn, nack_sn;
    guint64    e1, e2, e3, reserved;
    guint32    so_start, so_end, nack_range;
    int        bit_offset = offset << 3;
    proto_item *ti;

    /****************************************************************/
    /* Part of RLC control PDU header                               */

    /* Control PDU Type (CPT) */
    ti = proto_tree_add_item_ret_uint(tree, hf_rlc_nr_am_cpt, tvb, offset, 1, ENC_BIG_ENDIAN, &cpt);
    if (cpt != 0) {
        /* Protest and stop - only know about STATUS PDUs */
        expert_add_info_format(pinfo, ti, &ei_rlc_nr_am_cpt,
                               "RLC Control frame type %u not handled", cpt);
        return;
    }

    if (p_rlc_nr_info->sequenceNumberLength == AM_SN_LENGTH_12_BITS) {
        sn_size = 12;
        sn_limit = 4096;
        reserved_bits1 = 7;
        reserved_bits2 = 1;
    } else if (p_rlc_nr_info->sequenceNumberLength == AM_SN_LENGTH_18_BITS) {
        sn_size = 18;
        sn_limit = 262044;
        reserved_bits1 = 1;
        reserved_bits2 = 3;
    } else {
        proto_tree_add_expert_format(tree, pinfo, &ei_rlc_nr_am_sn, tvb, 0, 0,
                                     "Invalid sequence number length (%u bits)",
                                     p_rlc_nr_info->sequenceNumberLength);
        return;
    }

    /* The Status PDU itself starts 4 bits into the byte */
    bit_offset += 4;

    /* ACK SN */
    proto_tree_add_bits_ret_val(tree, hf_rlc_nr_am_ack_sn, tvb,
                                bit_offset, sn_size, &ack_sn, ENC_BIG_ENDIAN);
    bit_offset += sn_size;
    write_pdu_label_and_info(top_ti, status_ti, pinfo, "  ACK_SN=%-6u", (guint32)ack_sn);

    /* E1 */
    proto_tree_add_bits_ret_val(tree, hf_rlc_nr_am_e1, tvb,
                                bit_offset, 1, &e1, ENC_BIG_ENDIAN);
    bit_offset++;

    /* Reserved bits */
    ti = proto_tree_add_bits_ret_val(tree, hf_rlc_nr_am_reserved, tvb, bit_offset,
                                     reserved_bits1, &reserved, ENC_BIG_ENDIAN);
    bit_offset += reserved_bits1;
    if (reserved) {
        expert_add_info(pinfo, ti, &ei_rlc_nr_reserved_bits_not_zero);
    }

    /* Optional, extra fields */
    while (e1) {
        proto_item *nack_ti;

        /****************************/
        /* Read NACK_SN, E1, E2, E3 */

        /* NACK_SN */
        nack_ti = proto_tree_add_bits_ret_val(tree, hf_rlc_nr_am_nack_sn, tvb,
                                              bit_offset, sn_size, &nack_sn, ENC_BIG_ENDIAN);
        bit_offset += sn_size;
        write_pdu_label_and_info(top_ti, NULL, pinfo, "  NACK_SN=%-6u", (guint32)nack_sn);

        /* We shouldn't NACK the ACK_SN! */
        if (nack_sn == ack_sn) {
            expert_add_info_format(pinfo, nack_ti, &ei_rlc_nr_am_nack_sn_ack_same,
                                   "Status PDU shouldn't ACK and NACK the same sequence number (%" PRIu64 ")",
                                   ack_sn);
        }

        /* NACK should always be 'behind' the ACK */
        if ((sn_limit + ack_sn - nack_sn) % sn_limit > (sn_limit>>1)) {
            expert_add_info(pinfo, nack_ti, &ei_rlc_nr_am_nack_sn_ahead_ack);
        }

        nack_count++;

        /* E1 */
        proto_tree_add_bits_ret_val(tree, hf_rlc_nr_am_e1, tvb,
                                    bit_offset, 1, &e1, ENC_BIG_ENDIAN);
        bit_offset++;

        /* E2 */
        proto_tree_add_bits_ret_val(tree, hf_rlc_nr_am_e2, tvb,
                                    bit_offset, 1, &e2, ENC_BIG_ENDIAN);
        bit_offset++;

        /* Report as expert info */
        if (e2) {
            expert_add_info_format(pinfo, nack_ti, &ei_rlc_nr_am_nack_sn_partial,
                                   "Status PDU reports NACK (partial) on %s for UE %u",
                                   val_to_str_const(p_rlc_nr_info->direction, direction_vals, "Unknown"),
                                   p_rlc_nr_info->ueid);
        } else {
            expert_add_info_format(pinfo, nack_ti, &ei_rlc_nr_am_nack_sn,
                                   "Status PDU reports NACK on %s for UE %u",
                                   val_to_str_const(p_rlc_nr_info->direction, direction_vals, "Unknown"),
                                   p_rlc_nr_info->ueid);
        }

        /* E3 */
        proto_tree_add_bits_ret_val(tree, hf_rlc_nr_am_e3, tvb,
                                    bit_offset, 1, &e3, ENC_BIG_ENDIAN);
        bit_offset++;

        /* Reserved bits */
        ti = proto_tree_add_bits_ret_val(tree, hf_rlc_nr_am_reserved, tvb, bit_offset,
                                         reserved_bits2, &reserved, ENC_BIG_ENDIAN);
        bit_offset += reserved_bits2;
        if (reserved) {
            expert_add_info(pinfo, ti, &ei_rlc_nr_reserved_bits_not_zero);
        }

        if (e2) {
            /* Read SOstart, SOend */
            proto_tree_add_item_ret_uint(tree, hf_rlc_nr_am_so_start, tvb,
                                         bit_offset>>3, 2, ENC_BIG_ENDIAN, &so_start);
            bit_offset += 16;

            proto_tree_add_item_ret_uint(tree, hf_rlc_nr_am_so_end, tvb,
                                         bit_offset>>3, 2, ENC_BIG_ENDIAN, &so_end);
            bit_offset += 16;


            if (so_end == 0xffff) {
                write_pdu_label_and_info(top_ti, NULL, pinfo,
                                         " (SOstart=%u SOend=<END-OF_SDU>)",
                                         so_start);
            } else {
                write_pdu_label_and_info(top_ti, NULL, pinfo,
                                         " (SOstart=%u SOend=%u)",
                                         so_start, so_end);
            }
        }

        if (e3) {
            proto_item *nack_range_ti;

            /* Read NACK range */
            nack_range_ti = proto_tree_add_item_ret_uint(tree, hf_rlc_nr_am_nack_range, tvb,
                                                         bit_offset>>3, 1, ENC_BIG_ENDIAN, &nack_range);
            bit_offset += 8;
            if (nack_range == 0) {
                expert_add_info(pinfo, nack_range_ti, &ei_rlc_nr_am_nack_range);
            } else {
                nack_count += nack_range-1;
            }
            proto_item_append_text(nack_range_ti, " (SNs %" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT " missing)", nack_sn, nack_sn+nack_range-1);

            write_pdu_label_and_info(top_ti, NULL, pinfo," NACK range=%u", nack_range);
        }
    }

    if (nack_count > 0) {
        proto_item *count_ti = proto_tree_add_uint(tree, hf_rlc_nr_am_nacks, tvb, 0, 1, nack_count);
        proto_item_set_generated(count_ti);
        proto_item_append_text(status_ti, "  (%u NACKs)", nack_count);
    }

    /* Check that we've reached the end of the PDU. If not, show malformed */
    offset = (bit_offset+7) / 8;
    if (tvb_reported_length_remaining(tvb, offset) > 0) {
        expert_add_info_format(pinfo, status_ti, &ei_rlc_nr_bytes_after_status_pdu_complete,
                               "%cL %u bytes remaining after Status PDU complete",
                               (p_rlc_nr_info->direction == DIRECTION_UPLINK) ? 'U' : 'D',
                               tvb_reported_length_remaining(tvb, offset));
    }

    /* Set selected length of control tree */
    proto_item_set_len(status_ti, offset);
}


/***************************************************/
/* Acknowledged mode PDU                           */
static void dissect_rlc_nr_am(tvbuff_t *tvb, packet_info *pinfo,
                              proto_tree *tree, int offset,
                              rlc_nr_info *p_rlc_nr_info, proto_item *top_ti)
{
    gboolean dc, polling;
    guint32 seg_info, sn;
    guint64 reserved;
    proto_item *am_ti;
    proto_tree *am_header_tree;
    proto_item *am_header_ti;
    gint   start_offset = offset;
    gboolean is_truncated = FALSE;
    proto_item *truncated_ti;
    proto_item *reserved_ti;
    guint32 so = 0;

    /* Hidden AM root */
    am_ti = proto_tree_add_string_format(tree, hf_rlc_nr_am,
                                         tvb, offset, 0, "", "AM");
    proto_item_set_hidden(am_ti);

    /* Add AM header subtree */
    am_header_ti = proto_tree_add_string_format(tree, hf_rlc_nr_am_header,
                                                tvb, offset, 0,
                                                "", "AM Header ");
    am_header_tree = proto_item_add_subtree(am_header_ti,
                                            ett_rlc_nr_am_header);

    /* First bit is Data/Control flag */
    proto_tree_add_item_ret_boolean(am_header_tree, hf_rlc_nr_am_data_control,
                                    tvb, offset, 1, ENC_BIG_ENDIAN, &dc);

    if (dc == 0) {
        /**********************/
        /* Status PDU         */
        write_pdu_label_and_info_literal(top_ti, NULL, pinfo, " [CONTROL]");

        /* Control PDUs are a completely separate format  */
        dissect_rlc_nr_am_status_pdu(tvb, pinfo, am_header_tree, am_header_ti,
                                     offset, top_ti, p_rlc_nr_info);
        return;
    }

    /**********************/
    /* Data PDU           */
    write_pdu_label_and_info_literal(top_ti, NULL, pinfo, " [DATA]");

    /* Polling bit */
    proto_tree_add_item_ret_boolean(am_header_tree, hf_rlc_nr_am_p, tvb,
                                    offset, 1, ENC_BIG_ENDIAN, &polling);

    write_pdu_label_and_info_literal(top_ti, NULL, pinfo, (polling) ? " (P) " : "     ");
    if (polling) {
        proto_item_append_text(am_header_ti, " (P) ");
    }

    /* Segmentation Info */
    proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_nr_am_si, tvb,
                                 offset, 1, ENC_BIG_ENDIAN, &seg_info);

    /* Sequence Number */
    if (p_rlc_nr_info->sequenceNumberLength == AM_SN_LENGTH_12_BITS) {
        proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_nr_am_sn12, tvb,
                                     offset, 2, ENC_BIG_ENDIAN, &sn);
        offset += 2;
    } else if (p_rlc_nr_info->sequenceNumberLength == AM_SN_LENGTH_18_BITS) {
        reserved_ti = proto_tree_add_bits_ret_val(am_header_tree, hf_rlc_nr_am_reserved, tvb,
                                                  (offset<<3)+4, 2, &reserved, ENC_BIG_ENDIAN);
        if (reserved) {
            expert_add_info(pinfo, reserved_ti, &ei_rlc_nr_reserved_bits_not_zero);
        }
        proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_nr_am_sn18, tvb,
                                     offset, 3, ENC_BIG_ENDIAN, &sn);
        offset += 3;
    } else {
        /* Invalid length of sequence number */
        proto_tree_add_expert_format(am_header_tree, pinfo, &ei_rlc_nr_am_sn, tvb, 0, 0,
                                     "Invalid sequence number length (%u bits)",
                                     p_rlc_nr_info->sequenceNumberLength);
        return;
    }

    /* Segment Offset */
    if (seg_info >= 2) {
        proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_nr_am_so, tvb,
                                     offset, 2, ENC_BIG_ENDIAN, &so);
        offset += 2;
        write_pdu_label_and_info(top_ti, am_header_ti, pinfo, "SN=%-6u SO=%-4u",sn, so);
    } else {
        write_pdu_label_and_info(top_ti, am_header_ti, pinfo, "SN=%-6u        ", sn);
    }

    /* Header is now complete */
    proto_item_set_len(am_header_ti, offset-start_offset);

    /* There might not be any data, if only headers (plus control data) were logged */
    if (global_rlc_nr_headers_expected) {
        is_truncated = (tvb_captured_length_remaining(tvb, offset) == 0);
        truncated_ti = proto_tree_add_boolean(tree, hf_rlc_nr_header_only, tvb, 0, 0,
                                              is_truncated);
        if (is_truncated) {
            proto_item_set_generated(truncated_ti);
            expert_add_info(pinfo, truncated_ti, &ei_rlc_nr_header_only);
            show_PDU_in_info(pinfo, top_ti, p_rlc_nr_info->pduLength - offset, seg_info);
            return;
        } else {
            proto_item_set_hidden(truncated_ti);
        }
    }

    /* Data */

    /* Handle any reassembly. */
    tvbuff_t *next_tvb = NULL;
    if (global_rlc_nr_reassemble_am_pdus && seg_info && tvb_reported_length_remaining(tvb, offset) > 0) {
        /* Set fragmented flag. */
        gboolean save_fragmented = pinfo->fragmented;
        pinfo->fragmented = TRUE;
        fragment_head *fh;
        gboolean more_frags = seg_info & 0x01;
        guint32 id = get_reassembly_start_frame(pinfo, seg_info, p_rlc_nr_info, sn);
        if (id != 0) {
            fh = fragment_add(&pdu_reassembly_table, tvb, offset, pinfo,
                              id,                                         /* id */
                              GUINT_TO_POINTER(id),                       /* data */
                              so,                                         /* frag_offset */
                              tvb_reported_length_remaining(tvb, offset), /* frag_data_len */
                              more_frags                                  /* more_frags */
                              );

            gboolean update_col_info = TRUE;
            next_tvb = process_reassembled_data(tvb, offset, pinfo, "Reassembled RLC SDU",
                                                fh, &rlc_nr_frag_items,
                                                &update_col_info, tree);
            pinfo->fragmented = save_fragmented;
        }
    }


    if (tvb_reported_length_remaining(tvb, offset) > 0) {
        show_PDU_in_tree(pinfo, tree, tvb, offset, tvb_reported_length_remaining(tvb, offset),
                         p_rlc_nr_info, seg_info, FALSE);
        show_PDU_in_info(pinfo, top_ti, tvb_reported_length_remaining(tvb, offset), seg_info);

        /* Also add any reassembled PDU */
        if (next_tvb) {
            add_new_data_source(pinfo, next_tvb, "Reassembled RLC-NR PDU");
            show_PDU_in_tree(pinfo, tree, next_tvb, 0, tvb_captured_length(next_tvb),
                             p_rlc_nr_info, seg_info, TRUE);
        }
    } else if (!global_rlc_nr_headers_expected) {
        /* Report that expected data was missing (unless we know it might happen) */
        expert_add_info(pinfo, am_header_ti, &ei_rlc_nr_am_data_no_data);
    }
}


/* Heuristic dissector looks for supported framing protocol (see header file for details) */
static gboolean dissect_rlc_nr_heur(tvbuff_t *tvb, packet_info *pinfo,
                                    proto_tree *tree, void *data _U_)
{
    gint        offset = 0;
    rlc_nr_info *p_rlc_nr_info;
    tvbuff_t    *rlc_tvb;
    guint8      tag;

    /* Do this again on re-dissection to re-discover offset of actual PDU */

    /* Needs to be at least as long as:
       - the signature string
       - fixed header bytes
       - tag for data
       - at least one byte of RLC PDU payload */
    if (tvb_captured_length_remaining(tvb, offset) < (gint)(strlen(RLC_NR_START_STRING)+2+2)) {
        return FALSE;
    }

    /* OK, compare with signature string */
    if (tvb_strneql(tvb, offset, RLC_NR_START_STRING, (gint)strlen(RLC_NR_START_STRING)) != 0) {
        return FALSE;
    }
    offset += (gint)strlen(RLC_NR_START_STRING);


    /* If redissecting, use previous info struct (if available) */
    p_rlc_nr_info = (rlc_nr_info *)p_get_proto_data(wmem_file_scope(), pinfo, proto_rlc_nr, 0);
    if (p_rlc_nr_info == NULL) {
        /* Allocate new info struct for this frame */
        p_rlc_nr_info = wmem_new0(wmem_file_scope(), struct rlc_nr_info);

        /* Read fixed fields */
        p_rlc_nr_info->rlcMode = tvb_get_guint8(tvb, offset++);
        p_rlc_nr_info->sequenceNumberLength = tvb_get_guint8(tvb, offset++);

        /* Read optional fields */
        do {
            /* Process next tag */
            tag = tvb_get_guint8(tvb, offset++);
            switch (tag) {
                case RLC_NR_DIRECTION_TAG:
                    p_rlc_nr_info->direction = tvb_get_guint8(tvb, offset);
                    offset++;
                    break;
                case RLC_NR_UEID_TAG:
                    p_rlc_nr_info->ueid = tvb_get_ntohs(tvb, offset);
                    offset += 2;
                    break;
                case RLC_NR_BEARER_TYPE_TAG:
                    p_rlc_nr_info->bearerType = tvb_get_guint8(tvb, offset);
                    offset++;
                    break;
                case RLC_NR_BEARER_ID_TAG:
                    p_rlc_nr_info->bearerId = tvb_get_guint8(tvb, offset);
                    offset++;
                    break;
                case RLC_NR_PAYLOAD_TAG:
                    /* Have reached data, so set payload length and get out of loop */
                    p_rlc_nr_info->pduLength = tvb_reported_length_remaining(tvb, offset);
                    break;
                default:
                    /* It must be a recognised tag */
                    {
                        proto_item *ti;
                        proto_tree *subtree;

                        col_set_str(pinfo->cinfo, COL_PROTOCOL, "RLC-NR");
                        col_clear(pinfo->cinfo, COL_INFO);
                        ti = proto_tree_add_item(tree, proto_rlc_nr, tvb, offset, tvb_reported_length(tvb), ENC_NA);
                        subtree = proto_item_add_subtree(ti, ett_rlc_nr);
                        proto_tree_add_expert(subtree, pinfo, &ei_rlc_nr_unknown_udp_framing_tag,
                                              tvb, offset-1, 1);
                    }
                    wmem_free(wmem_file_scope(), p_rlc_nr_info);
                    return TRUE;
            }
        } while (tag != RLC_NR_PAYLOAD_TAG);

        /* Store info in packet */
        p_add_proto_data(wmem_file_scope(), pinfo, proto_rlc_nr, 0, p_rlc_nr_info);
    } else {
        offset = tvb_reported_length(tvb) - p_rlc_nr_info->pduLength;
    }

    /**************************************/
    /* OK, now dissect as RLC NR          */

    /* Create tvb that starts at actual RLC PDU */
    rlc_tvb = tvb_new_subset_remaining(tvb, offset);
    dissect_rlc_nr_common(rlc_tvb, pinfo, tree, TRUE /* udp framing */);
    return TRUE;
}

/*****************************/
/* Main dissection function. */
/*****************************/

static int dissect_rlc_nr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
    dissect_rlc_nr_common(tvb, pinfo, tree, FALSE /* not udp framing */);
    return tvb_captured_length(tvb);
}

static void dissect_rlc_nr_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gboolean is_udp_framing)
{
    proto_tree             *rlc_nr_tree;
    proto_tree             *context_tree;
    proto_item             *top_ti;
    proto_item             *context_ti;
    proto_item             *ti;
    proto_item             *mode_ti;
    gint                   offset = 0;
    struct rlc_nr_info     *p_rlc_nr_info;

    /* Set protocol name */
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "RLC-NR");

    /* Create protocol tree. */
    top_ti = proto_tree_add_item(tree, proto_rlc_nr, tvb, offset, -1, ENC_NA);
    rlc_nr_tree = proto_item_add_subtree(top_ti, ett_rlc_nr);


    /* Look for packet info! */
    p_rlc_nr_info = (rlc_nr_info *)p_get_proto_data(wmem_file_scope(), pinfo, proto_rlc_nr, 0);

    /* Can't dissect anything without it... */
    if (p_rlc_nr_info == NULL) {
        proto_tree_add_expert(rlc_nr_tree, pinfo, &ei_rlc_nr_no_per_frame_info, tvb, offset, -1);
        return;
    }

    /* Clear info column when using UDP framing */
    if (is_udp_framing) {
        col_clear(pinfo->cinfo, COL_INFO);
    }

    /*****************************************/
    /* Show context information              */

    /* Create context root */
    context_ti = proto_tree_add_string_format(rlc_nr_tree, hf_rlc_nr_context,
                                              tvb, offset, 0, "", "Context");
    context_tree = proto_item_add_subtree(context_ti, ett_rlc_nr_context);
    proto_item_set_generated(context_ti);

    ti = proto_tree_add_uint(context_tree, hf_rlc_nr_context_direction,
                             tvb, 0, 0, p_rlc_nr_info->direction);
    proto_item_set_generated(ti);

    mode_ti = proto_tree_add_uint(context_tree, hf_rlc_nr_context_mode,
                                  tvb, 0, 0, p_rlc_nr_info->rlcMode);
    proto_item_set_generated(mode_ti);

    if (p_rlc_nr_info->ueid != 0) {
        ti = proto_tree_add_uint(context_tree, hf_rlc_nr_context_ueid,
                                 tvb, 0, 0, p_rlc_nr_info->ueid);
        proto_item_set_generated(ti);
    }

    ti = proto_tree_add_uint(context_tree, hf_rlc_nr_context_bearer_type,
                             tvb, 0, 0, p_rlc_nr_info->bearerType);
    proto_item_set_generated(ti);

    if ((p_rlc_nr_info->bearerType == BEARER_TYPE_SRB) ||
        (p_rlc_nr_info->bearerType == BEARER_TYPE_DRB)) {
        ti = proto_tree_add_uint(context_tree, hf_rlc_nr_context_bearer_id,
                                 tvb, 0, 0, p_rlc_nr_info->bearerId);
        proto_item_set_generated(ti);
    }

    ti = proto_tree_add_uint(context_tree, hf_rlc_nr_context_pdu_length,
                             tvb, 0, 0, p_rlc_nr_info->pduLength);
    proto_item_set_generated(ti);

    if (p_rlc_nr_info->rlcMode != RLC_TM_MODE) {
        ti = proto_tree_add_uint(context_tree, hf_rlc_nr_context_sn_length,
                                 tvb, 0, 0, p_rlc_nr_info->sequenceNumberLength);
        proto_item_set_generated(ti);
    }

    /* Append highlights to top-level item */
    if (p_rlc_nr_info->ueid != 0) {
        proto_item_append_text(top_ti, "   UEId=%u", p_rlc_nr_info->ueid);
        col_append_fstr(pinfo->cinfo, COL_INFO, "UEId=%-4u ", p_rlc_nr_info->ueid);
    }

    /* Append context highlights to info column */
    write_pdu_label_and_info(top_ti, NULL, pinfo,
                             " [%s] [%s] ",
                             (p_rlc_nr_info->direction == 0) ? "UL" : "DL",
                             val_to_str_const(p_rlc_nr_info->rlcMode, rlc_mode_short_vals, "Unknown"));

    if (p_rlc_nr_info->bearerId == 0) {
        write_pdu_label_and_info(top_ti, NULL, pinfo, "%s   ",
                                 val_to_str_const(p_rlc_nr_info->bearerType, rlc_bearer_type_vals, "Unknown"));
    } else {
        write_pdu_label_and_info(top_ti, NULL, pinfo, "%s:%-2u",
                                 val_to_str_const(p_rlc_nr_info->bearerType, rlc_bearer_type_vals, "Unknown"),
                                 p_rlc_nr_info->bearerId);
    }

    /* Dissect the RLC PDU itself. Format depends upon mode... */
    switch (p_rlc_nr_info->rlcMode) {

        case RLC_TM_MODE:
            dissect_rlc_nr_tm(tvb, pinfo, rlc_nr_tree, offset, p_rlc_nr_info, top_ti);
            break;

        case RLC_UM_MODE:
            dissect_rlc_nr_um(tvb, pinfo, rlc_nr_tree, offset, p_rlc_nr_info, top_ti);
            break;

        case RLC_AM_MODE:
            dissect_rlc_nr_am(tvb, pinfo, rlc_nr_tree, offset, p_rlc_nr_info, top_ti);
            break;

        default:
            /* Error - unrecognised mode */
            expert_add_info_format(pinfo, mode_ti, &ei_rlc_nr_context_mode,
                                   "Unrecognised RLC Mode set (%u)", p_rlc_nr_info->rlcMode);
            break;
    }
}


/* Configure DRB PDCP channel properties. */
void set_rlc_nr_drb_pdcp_mapping(packet_info *pinfo,
                                 nr_drb_rlc_pdcp_mapping_t *drb_mapping)
{
    wmem_tree_key_t key[2];
    guint32 id;
    pdcp_bearer_parameters *params;

    if (PINFO_FD_VISITED(pinfo)) {
        return;
    }

    id = (drb_mapping->drbid << 16) | drb_mapping->ueid;
    key[0].length = 1;
    key[0].key = &id;
    key[1].length = 0;
    key[1].key = NULL;

    /* Look up entry for this UEId/drbid */
    params = (pdcp_bearer_parameters *)wmem_tree_lookup32_array_le(ue_parameters_tree, key);
    if (params && (params->id != id)) {
        params = NULL;
    }
    if (params == NULL) {
        /* Not found so create new entry */
        params = (pdcp_bearer_parameters *)wmem_new(wmem_file_scope(), pdcp_bearer_parameters);
        params->id = id;
        wmem_tree_insert32_array(ue_parameters_tree, key, (void *)params);
    }

    /* Populate params */
    params->pdcp_sn_bits_ul = drb_mapping->pdcpUlSnLength;
    params->pdcp_sn_bits_dl = drb_mapping->pdcpDlSnLength;
    params->pdcp_sdap_ul = drb_mapping->pdcpUlSdap;
    params->pdcp_sdap_dl = drb_mapping->pdcpDlSdap;
    params->pdcp_integrity = drb_mapping->pdcpIntegrityProtection;
    params->pdcp_ciphering_disabled = drb_mapping->pdcpCipheringDisabled;
}

pdcp_bearer_parameters* get_rlc_nr_drb_pdcp_mapping(guint16 ue_id, guint8 drb_id)
{
    wmem_tree_key_t key[2];
    guint32 id;
    pdcp_bearer_parameters *params;

    id = (drb_id << 16) | ue_id;
    key[0].length = 1;
    key[0].key = &id;
    key[1].length = 0;
    key[1].key = NULL;

    /* Look up configured params for this PDCP DRB. */
    params = (pdcp_bearer_parameters *)wmem_tree_lookup32_array_le(ue_parameters_tree, key);
    if (params && (params->id != id)) {
        params = NULL;
    }

    return params;
}


void proto_register_rlc_nr(void)
{
    static hf_register_info hf[] =
    {
        /**********************************/
        /* Items for decoding context     */
        { &hf_rlc_nr_context,
            { "Context",
              "rlc-nr.context", FT_STRING, BASE_NONE, NULL, 0x0,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_context_mode,
            { "RLC Mode",
              "rlc-nr.mode", FT_UINT8, BASE_DEC, VALS(rlc_mode_vals), 0x0,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_context_direction,
            { "Direction",
              "rlc-nr.direction", FT_UINT8, BASE_DEC, VALS(direction_vals), 0x0,
              "Direction of message", HFILL
            }
        },
        { &hf_rlc_nr_context_ueid,
            { "UEId",
              "rlc-nr.ueid", FT_UINT16, BASE_DEC, 0, 0x0,
              "User Equipment Identifier associated with message", HFILL
            }
        },
        { &hf_rlc_nr_context_bearer_type,
            { "Bearer Type",
              "rlc-nr.bearer-type", FT_UINT16, BASE_DEC, VALS(rlc_bearer_type_vals), 0x0,
              "Bearer Type associated with message", HFILL
            }
        },
        { &hf_rlc_nr_context_bearer_id,
            { "Bearer Id",
              "rlc-nr.bearer-id", FT_UINT16, BASE_DEC, 0, 0x0,
              "Bearer ID associated with message", HFILL
            }
        },
        { &hf_rlc_nr_context_pdu_length,
            { "PDU Length",
              "rlc-nr.pdu-length", FT_UINT16, BASE_DEC, 0, 0x0,
              "Length of PDU (in bytes)", HFILL
            }
        },
        { &hf_rlc_nr_context_sn_length,
            { "Sequence Number length",
              "rlc-nr.seqnum-length", FT_UINT8, BASE_DEC, 0, 0x0,
              "Length of sequence number in bits", HFILL
            }
        },

        /* Transparent mode fields */
        { &hf_rlc_nr_tm,
            { "TM",
              "rlc-nr.tm", FT_STRING, BASE_NONE, NULL, 0x0,
              "Transparent Mode", HFILL
            }
        },
        { &hf_rlc_nr_tm_data,
            { "TM Data",
              "rlc-nr.tm.data", FT_BYTES, BASE_NONE, 0, 0x0,
              "Transparent Mode Data", HFILL
            }
        },

        /* Unacknowledged mode fields */
        { &hf_rlc_nr_um,
            { "UM",
              "rlc-nr.um", FT_STRING, BASE_NONE, NULL, 0x0,
              "Unacknowledged Mode", HFILL
            }
        },
        { &hf_rlc_nr_um_header,
            { "UM Header",
              "rlc-nr.um.header", FT_STRING, BASE_NONE, NULL, 0x0,
              "Unacknowledged Mode Header", HFILL
            }
        },
        { &hf_rlc_nr_um_si,
            { "Segmentation Info",
              "rlc-nr.um.si", FT_UINT8, BASE_HEX, VALS(seg_info_vals), 0xc0,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_um_reserved,
            { "Reserved",
              "rlc-nr.um.reserved", FT_UINT8, BASE_HEX, 0, 0x0,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_um_sn6,
            { "Sequence Number",
              "rlc-nr.um.sn", FT_UINT8, BASE_DEC, 0, 0x3f,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_um_sn12,
            { "Sequence Number",
              "rlc-nr.um.sn", FT_UINT16, BASE_DEC, 0, 0x0fff,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_um_so,
            { "Segment Offset",
              "rlc-nr.um.so", FT_UINT16, BASE_DEC, 0, 0x0,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_um_data,
            { "UM Data",
              "rlc-nr.um.data", FT_BYTES, BASE_NONE, 0, 0x0,
              "Unacknowledged Mode Data", HFILL
            }
        },

        /* Acknowledged mode fields */
        { &hf_rlc_nr_am,
            { "AM",
              "rlc-nr.am", FT_STRING, BASE_NONE, NULL, 0x0,
              "Acknowledged Mode", HFILL
            }
        },
        { &hf_rlc_nr_am_header,
            { "AM Header",
              "rlc-nr.am.header", FT_STRING, BASE_NONE, NULL, 0x0,
              "Acknowledged Mode Header", HFILL
            }
        },
        { &hf_rlc_nr_am_data_control,
            { "Data/Control",
              "rlc-nr.am.dc", FT_BOOLEAN, 8, TFS(&data_or_control_vals), 0x80,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_am_p,
            { "Polling Bit",
              "rlc-nr.am.p", FT_BOOLEAN, 8, TFS(&polling_bit_vals), 0x40,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_am_si,
            { "Segmentation Info",
              "rlc-nr.am.si", FT_UINT8, BASE_HEX, VALS(seg_info_vals), 0x30,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_am_sn12,
            { "Sequence Number",
              "rlc-nr.am.sn", FT_UINT16, BASE_DEC, 0, 0x0fff,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_am_sn18,
            { "Sequence Number",
              "rlc-nr.am.sn", FT_UINT24, BASE_DEC, 0, 0x03ffff,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_am_reserved,
            { "Reserved",
              "rlc-nr.am.reserved", FT_UINT8, BASE_HEX, 0, 0x0,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_am_so,
            { "Segment Offset",
              "rlc-nr.am.so", FT_UINT16, BASE_DEC, 0, 0x0,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_am_data,
            { "AM Data",
              "rlc-nr.am.data", FT_BYTES, BASE_NONE, 0, 0x0,
              "Acknowledged Mode Data", HFILL
            }
        },
        { &hf_rlc_nr_am_cpt,
            { "Control PDU Type",
              "rlc-nr.am.cpt", FT_UINT8, BASE_HEX, VALS(control_pdu_type_vals), 0x70,
              "AM Control PDU Type", HFILL
            }
        },
        { &hf_rlc_nr_am_ack_sn,
            { "ACK Sequence Number",
              "rlc-nr.am.ack-sn", FT_UINT24, BASE_DEC, 0, 0x0,
              "Sequence Number we expect to receive next", HFILL
            }
        },
        { &hf_rlc_nr_am_e1,
            { "Extension bit 1",
              "rlc-nr.am.e1", FT_BOOLEAN, BASE_NONE, TFS(&am_e1_vals), 0x0,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_am_e2,
            { "Extension bit 2",
              "rlc-nr.am.e2", FT_BOOLEAN, BASE_NONE, TFS(&am_e2_vals), 0x0,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_am_e3,
            { "Extension bit 3",
              "rlc-nr.am.e3", FT_BOOLEAN, BASE_NONE, TFS(&am_e3_vals), 0x0,
              NULL, HFILL
            }
        },
        { &hf_rlc_nr_am_nacks,
            { "Number of NACKs",
              "rlc-nr.am.nacks", FT_UINT32, BASE_DEC, 0, 0x0,
              "Number of NACKs in this status PDU", HFILL
            }
        },
        { &hf_rlc_nr_am_nack_sn,
            { "NACK Sequence Number",
              "rlc-nr.am.nack-sn", FT_UINT24, BASE_DEC, 0, 0x0,
              "Negative Acknowledgement Sequence Number", HFILL
            }
        },
        { &hf_rlc_nr_am_so_start,
            { "SO start",
              "rlc-nr.am.so-start", FT_UINT16, BASE_DEC, 0, 0x0,
              "Segment Offset Start byte index", HFILL
            }
        },
        { &hf_rlc_nr_am_so_end,
            { "SO end",
              "rlc-nr.am.so-end", FT_UINT16, BASE_DEC, 0, 0x0,
              "Segment Offset End byte index", HFILL
            }
        },
        { &hf_rlc_nr_am_nack_range,
            { "NACK range",
              "rlc-nr.am.nack-range", FT_UINT16, BASE_DEC, 0, 0x0,
              "Number of consecutively lost RLC SDUs starting from and including NACK_SN", HFILL
            }
        },

        { &hf_rlc_nr_header_only,
            { "RLC PDU Header only",
              "rlc-nr.header-only", FT_BOOLEAN, BASE_NONE, TFS(&header_only_vals), 0x0,
              NULL, HFILL
            }
        },

        { &hf_rlc_nr_fragment,
          { "RLC-NR fragment",
            "rlc-nr.fragment", FT_FRAMENUM, BASE_NONE, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_rlc_nr_fragments,
          { "RLC-NR fragments",
            "rlc-nr.fragments", FT_BYTES, BASE_NONE, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_rlc_nr_fragment_overlap,
          { "Fragment overlap",
            "rlc-nr.fragment.overlap", FT_BOOLEAN, BASE_NONE, NULL, 0x0,
            "Fragment overlaps with other fragments", HFILL }
        },
        { &hf_rlc_nr_fragment_overlap_conflict,
          { "Conflicting data in fragment overlap",
            "rlc-nr.fragment.overlap.conflict",
            FT_BOOLEAN, BASE_NONE, NULL, 0x0,
            "Overlapping fragments contained conflicting data", HFILL }
        },
        { &hf_rlc_nr_fragment_multiple_tails,
          { "Multiple tail fragments found",
            "rlc-nr.fragment.multipletails",
            FT_BOOLEAN, BASE_NONE, NULL, 0x0,
            "Several tails were found when defragmenting the packet", HFILL }
        },
        { &hf_rlc_nr_fragment_too_long_fragment,
          { "Fragment too long",
            "rlc-nr.fragment.toolongfragment",
            FT_BOOLEAN, BASE_NONE, NULL, 0x0,
            "Fragment contained data past end of packet", HFILL }
        },
        { &hf_rlc_nr_fragment_error,
          { "Defragmentation error",
            "rlc-nr.fragment.error",
            FT_FRAMENUM, BASE_NONE, NULL, 0x0,
            "Defragmentation error due to illegal fragments", HFILL }
        },
        { &hf_rlc_nr_fragment_count,
          { "Fragment count",
            "rlc-nr.fragment.count",
            FT_UINT32, BASE_DEC, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_rlc_nr_reassembled_in,
          { "Reassembled RLC-NR in frame",
            "rlc-nr.reassembled_in",
            FT_FRAMENUM, BASE_NONE, NULL, 0x0,
            "This RLC-NR packet is reassembled in this frame", HFILL }
        },
        { &hf_rlc_nr_reassembled_length,
          { "Reassembled RLC-NR length",
            "rlc-nr.reassembled.length",
            FT_UINT32, BASE_DEC, NULL, 0x0,
            "The total length of the reassembled payload", HFILL }
        },
        { &hf_rlc_nr_reassembled_data,
          { "Reassembled payload",
            "rlc-nr.reassembled.data",
            FT_BYTES, BASE_NONE, NULL, 0x0,
            "The reassembled payload", HFILL }
        }
    };

    static gint *ett[] =
    {
        &ett_rlc_nr,
        &ett_rlc_nr_context,
        &ett_rlc_nr_um_header,
        &ett_rlc_nr_am_header,
        &ett_rlc_nr_fragment,
        &ett_rlc_nr_fragments
    };

    static ei_register_info ei[] = {
        { &ei_rlc_nr_reserved_bits_not_zero, { "rlc-nr.reserved-bits-not-zero", PI_MALFORMED, PI_ERROR, "Reserved bits not zero", EXPFILL }},
        { &ei_rlc_nr_um_sn, { "rlc-nr.um.sn.invalid", PI_MALFORMED, PI_ERROR, "Invalid sequence number length", EXPFILL }},
        { &ei_rlc_nr_am_sn, { "rlc-nr.am.sn.invalid", PI_MALFORMED, PI_ERROR, "Invalid sequence number length", EXPFILL }},
        { &ei_rlc_nr_header_only, { "rlc-nr.header-only.expert", PI_SEQUENCE, PI_NOTE, "RLC PDU SDUs have been omitted", EXPFILL }},
        { &ei_rlc_nr_am_cpt, { "rlc-nr.am.cpt.invalid", PI_MALFORMED, PI_ERROR, "RLC Control frame type not handled", EXPFILL }},
        { &ei_rlc_nr_am_nack_sn_ack_same, { "rlc-nr.am.nack-sn.ack-same", PI_MALFORMED, PI_ERROR, "Status PDU shouldn't ACK and NACK the same sequence number", EXPFILL }},
        { &ei_rlc_nr_am_nack_range, { "rlc-nr.am.nack-sn.nack-range", PI_MALFORMED, PI_ERROR, "Status PDU should not contain a NACK range with value 0", EXPFILL }},
        { &ei_rlc_nr_am_nack_sn_ahead_ack, { "rlc-nr.am.nack-sn.ahead-ack", PI_MALFORMED, PI_ERROR, "NACK must not be ahead of ACK in status PDU", EXPFILL }},
        { &ei_rlc_nr_am_nack_sn_partial, { "rlc-nr.am.nack-sn.partial", PI_SEQUENCE, PI_WARN, "Status PDU reports NACK (partial)", EXPFILL }},
        { &ei_rlc_nr_am_nack_sn, { "rlc-nr.am.nack-sn.expert", PI_SEQUENCE, PI_WARN, "Status PDU reports NACK", EXPFILL }},
        { &ei_rlc_nr_bytes_after_status_pdu_complete, { "rlc-nr.bytes-after-status-pdu-complete", PI_MALFORMED, PI_ERROR, "bytes remaining after Status PDU complete", EXPFILL }},
        { &ei_rlc_nr_um_data_no_data, { "rlc-nr.um-data.no-data", PI_MALFORMED, PI_ERROR, "UM data PDU doesn't contain any data", EXPFILL }},
        { &ei_rlc_nr_am_data_no_data, { "rlc-nr.am-data.no-data", PI_MALFORMED, PI_ERROR, "AM data PDU doesn't contain any data", EXPFILL }},
        { &ei_rlc_nr_context_mode, { "rlc-nr.mode.invalid", PI_MALFORMED, PI_ERROR, "Unrecognised RLC Mode set", EXPFILL }},
        { &ei_rlc_nr_no_per_frame_info, { "rlc-nr.no-per-frame-info", PI_UNDECODED, PI_ERROR, "Can't dissect NR RLC frame because no per-frame info was attached!", EXPFILL }},
        { &ei_rlc_nr_unknown_udp_framing_tag, { "rlc-nr.unknown-udp-framing-tag", PI_UNDECODED, PI_WARN, "Unknown UDP framing tag, aborting dissection", EXPFILL }}
    };

    module_t *rlc_nr_module;
    expert_module_t* expert_rlc_nr;

    /* Register protocol. */
    proto_rlc_nr = proto_register_protocol("RLC-NR", "RLC-NR", "rlc-nr");
    proto_register_field_array(proto_rlc_nr, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));
    expert_rlc_nr = expert_register_protocol(proto_rlc_nr);
    expert_register_field_array(expert_rlc_nr, ei, array_length(ei));

    /* Allow other dissectors to find this one by name. */
    register_dissector("rlc-nr", dissect_rlc_nr, proto_rlc_nr);

    /* Preferences */
    rlc_nr_module = prefs_register_protocol(proto_rlc_nr, NULL);

    prefs_register_bool_preference(rlc_nr_module, "call_pdcp_for_srb",
        "Call PDCP dissector for SRB PDUs",
        "Call PDCP dissector for signalling PDUs.  Note that without reassembly, it can"
        "only be called for complete PDUs (i.e. not segmented over RLC)",
        &global_rlc_nr_call_pdcp_for_srb);

    prefs_register_enum_preference(rlc_nr_module, "call_pdcp_for_ul_drb",
        "Call PDCP dissector for UL DRB PDUs",
        "Call PDCP dissector for UL user-plane PDUs.  Note that without reassembly, it can"
        "only be called for complete PDUs (i.e. not segmented over RLC)",
        &global_rlc_nr_call_pdcp_for_ul_drb, pdcp_drb_col_vals, FALSE);

    prefs_register_enum_preference(rlc_nr_module, "call_pdcp_for_dl_drb",
        "Call PDCP dissector for DL DRB PDUs",
        "Call PDCP dissector for DL user-plane PDUs.  Note that without reassembly, it can"
        "only be called for complete PDUs (i.e. not segmented over RLC)",
        &global_rlc_nr_call_pdcp_for_dl_drb, pdcp_drb_col_vals, FALSE);

    prefs_register_bool_preference(rlc_nr_module, "call_rrc_for_ccch",
        "Call RRC dissector for CCCH PDUs",
        "Call RRC dissector for CCCH PDUs",
        &global_rlc_nr_call_rrc_for_ccch);

    prefs_register_bool_preference(rlc_nr_module, "header_only_mode",
        "May see RLC headers only",
        "When enabled, if data is not present, don't report as an error, but instead "
        "add expert info to indicate that headers were omitted",
        &global_rlc_nr_headers_expected);

    prefs_register_bool_preference(rlc_nr_module, "reassemble_am_frames",
        "Try to reassemble AM frames",
        "N.B. This should be considered experimental/incomplete, in that it doesn't try to discard reassembled state "
        "when reestablishment happens, or in certain packet-loss cases",
        &global_rlc_nr_reassemble_am_pdus);

    prefs_register_bool_preference(rlc_nr_module, "reassemble_um_frames",
        "Try to reassemble UM frames",
        "N.B. This should be considered experimental/incomplete, in that it doesn't try to discard reassembled state "
        "when reestablishment happens, or in certain packet-loss cases",
        &global_rlc_nr_reassemble_um_pdus);

    ue_parameters_tree = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope());
    reassembly_start_table = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope());
    reassembly_start_table_stored = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope());

    /* Register reassembly table. */
    reassembly_table_register(&pdu_reassembly_table, &pdu_reassembly_table_functions);
}

void proto_reg_handoff_rlc_nr(void)
{
    /* Add as a heuristic UDP dissector */
    heur_dissector_add("udp", dissect_rlc_nr_heur, "RLC-NR over UDP", "rlc_nr_udp", proto_rlc_nr, HEURISTIC_DISABLE);

    pdcp_nr_handle = find_dissector("pdcp-nr");
    nr_rrc_bcch_bch = find_dissector_add_dependency("nr-rrc.bcch.bch", proto_rlc_nr);
    nr_rrc_bcch_dl_sch = find_dissector_add_dependency("nr-rrc.bcch.dl.sch", proto_rlc_nr);
    nr_rrc_pcch = find_dissector_add_dependency("nr-rrc.pcch", proto_pdcp_nr);
    nr_rrc_ul_ccch = find_dissector_add_dependency("nr-rrc.ul.ccch", proto_rlc_nr);
    nr_rrc_ul_ccch1 = find_dissector_add_dependency("nr-rrc.ul.ccch1", proto_rlc_nr);
    nr_rrc_dl_ccch = find_dissector_add_dependency("nr-rrc.dl.ccch", proto_rlc_nr);
}

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