diff options
Diffstat (limited to 'epan/dissectors/packet-rlc-lte.c')
-rw-r--r-- | epan/dissectors/packet-rlc-lte.c | 3744 |
1 files changed, 3744 insertions, 0 deletions
diff --git a/epan/dissectors/packet-rlc-lte.c b/epan/dissectors/packet-rlc-lte.c new file mode 100644 index 00000000..e32457de --- /dev/null +++ b/epan/dissectors/packet-rlc-lte.c @@ -0,0 +1,3744 @@ +/* Routines for LTE RLC disassembly + * + * Martin Mathieson + * + * 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/tap.h> +#include <epan/proto_data.h> +#include "packet-mac-lte.h" +#include "packet-rlc-lte.h" +#include "packet-pdcp-lte.h" + + +/* Described in: + * 3GPP TS 36.322 Evolved Universal Terrestial Radio Access (E-UTRA) + * Radio Link Control (RLC) Protocol specification v14.0.0 + */ + +/* TODO: + - add intermediate results to segments leading to final reassembly + - use multiple active rlc_channel_reassembly_info's per channel + - sequence analysis gets confused when we change cells and skip back + to SN 0. Maybe add cell-id to context and add to channel/result key? +*/ + +void proto_register_rlc_lte(void); +void proto_reg_handoff_rlc_lte(void); + +/********************************/ +/* Preference settings */ + +#define SEQUENCE_ANALYSIS_MAC_ONLY 1 +#define SEQUENCE_ANALYSIS_RLC_ONLY 2 + +/* By default do try to analyse the sequence of messages for AM/UM channels + using MAC PDUs */ +static gint global_rlc_lte_am_sequence_analysis = SEQUENCE_ANALYSIS_MAC_ONLY; +static gint global_rlc_lte_um_sequence_analysis = SEQUENCE_ANALYSIS_MAC_ONLY; + +/* By default do call PDCP/RRC dissectors for SDU data */ +static gboolean global_rlc_lte_call_pdcp_for_srb = TRUE; + +enum pdcp_for_drb { PDCP_drb_off, PDCP_drb_SN_7, PDCP_drb_SN_12, PDCP_drb_SN_signalled, PDCP_drb_SN_15, PDCP_drb_SN_18}; +static const enum_val_t pdcp_drb_col_vals[] = { + {"pdcp-drb-off", "Off", PDCP_drb_off}, + {"pdcp-drb-sn-7", "7-bit SN", PDCP_drb_SN_7}, + {"pdcp-drb-sn-12", "12-bit SN", PDCP_drb_SN_12}, + {"pdcp-drb-sn-15", "15-bit SN", PDCP_drb_SN_15}, + {"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} +}; +static gint global_rlc_lte_call_pdcp_for_drb = (gint)PDCP_drb_SN_signalled; + +static gboolean global_rlc_lte_call_rrc_for_ccch = TRUE; +static gboolean global_rlc_lte_call_rrc_for_mcch = FALSE; +static gboolean global_rlc_lte_call_ip_for_mtch = FALSE; + +/* Preference to expect RLC headers without payloads */ +static gboolean global_rlc_lte_headers_expected = FALSE; + +/* Re-assembly of segments */ +static gboolean global_rlc_lte_reassembly = TRUE; + +/* Tree storing UE related parameters */ +#define NO_EXT_LI 0x0 +#define UL_EXT_LI 0x1 +#define DL_EXT_LI 0x2 +typedef struct rlc_ue_parameters { + guint32 id; + guint8 ext_li_field; + guint8 pdcp_sn_bits; +} rlc_ue_parameters; +static wmem_tree_t *ue_parameters_tree; + +/**************************************************/ +/* Initialize the protocol and registered fields. */ +int proto_rlc_lte = -1; + +extern int proto_mac_lte; +extern int proto_pdcp_lte; + +static dissector_handle_t pdcp_lte_handle; +static dissector_handle_t ip_handle; +static dissector_handle_t lte_rrc_mcch; +static dissector_handle_t lte_rrc_ul_ccch; +static dissector_handle_t lte_rrc_dl_ccch; +static dissector_handle_t lte_rrc_bcch_bch; +static dissector_handle_t lte_rrc_bcch_dl_sch; +static dissector_handle_t lte_rrc_pcch; +static dissector_handle_t lte_rrc_ul_ccch_nb; +static dissector_handle_t lte_rrc_dl_ccch_nb; +static dissector_handle_t lte_rrc_bcch_bch_nb; +static dissector_handle_t lte_rrc_bcch_dl_sch_nb; +static dissector_handle_t lte_rrc_pcch_nb; + + +static int rlc_lte_tap = -1; + +/* Decoding context */ +static int hf_rlc_lte_context = -1; +static int hf_rlc_lte_context_mode = -1; +static int hf_rlc_lte_context_direction = -1; +static int hf_rlc_lte_context_priority = -1; +static int hf_rlc_lte_context_ueid = -1; +static int hf_rlc_lte_context_channel_type = -1; +static int hf_rlc_lte_context_channel_id = -1; +static int hf_rlc_lte_context_pdu_length = -1; +static int hf_rlc_lte_context_um_sn_length = -1; +static int hf_rlc_lte_context_am_sn_length = -1; + +/* Transparent mode fields */ +static int hf_rlc_lte_tm = -1; +static int hf_rlc_lte_tm_data = -1; + +/* Unacknowledged mode fields */ +static int hf_rlc_lte_um = -1; +static int hf_rlc_lte_um_header = -1; +static int hf_rlc_lte_um_fi = -1; +static int hf_rlc_lte_um_fixed_e = -1; +static int hf_rlc_lte_um_sn = -1; +static int hf_rlc_lte_um_fixed_reserved = -1; +static int hf_rlc_lte_um_data = -1; +static int hf_rlc_lte_extension_part = -1; + +/* Extended header (common to UM and AM) */ +static int hf_rlc_lte_extension_e = -1; +static int hf_rlc_lte_extension_li = -1; +static int hf_rlc_lte_extension_padding = -1; + + +/* Acknowledged mode fields */ +static int hf_rlc_lte_am = -1; +static int hf_rlc_lte_am_header = -1; +static int hf_rlc_lte_am_data_control = -1; +static int hf_rlc_lte_am_rf = -1; +static int hf_rlc_lte_am_p = -1; +static int hf_rlc_lte_am_fi = -1; +static int hf_rlc_lte_am_fixed_e = -1; +static int hf_rlc_lte_am_fixed_sn = -1; +static int hf_rlc_lte_am_fixed_reserved = -1; +static int hf_rlc_lte_am_segment_lsf16 = -1; +static int hf_rlc_lte_am_fixed_reserved2 = -1; +static int hf_rlc_lte_am_fixed_sn16 = -1; +static int hf_rlc_lte_am_segment_lsf = -1; +static int hf_rlc_lte_am_segment_so = -1; +static int hf_rlc_lte_am_segment_so16 = -1; +static int hf_rlc_lte_am_data = -1; + +/* Control fields */ +static int hf_rlc_lte_am_cpt = -1; +static int hf_rlc_lte_am_ack_sn = -1; +static int hf_rlc_lte_am_e1 = -1; +static int hf_rlc_lte_am_e2 = -1; +static int hf_rlc_lte_am_nack_sn = -1; +static int hf_rlc_lte_am_nacks = -1; +static int hf_rlc_lte_am_so_start = -1; +static int hf_rlc_lte_am_so_end = -1; + +static int hf_rlc_lte_predefined_pdu = -1; +static int hf_rlc_lte_header_only = -1; + +/* Sequence Analysis */ +static int hf_rlc_lte_sequence_analysis = -1; +static int hf_rlc_lte_sequence_analysis_ok = -1; +static int hf_rlc_lte_sequence_analysis_previous_frame = -1; +static int hf_rlc_lte_sequence_analysis_next_frame = -1; +static int hf_rlc_lte_sequence_analysis_expected_sn = -1; +static int hf_rlc_lte_sequence_analysis_framing_info_correct = -1; + +static int hf_rlc_lte_sequence_analysis_mac_retx = -1; +static int hf_rlc_lte_sequence_analysis_retx = -1; +static int hf_rlc_lte_sequence_analysis_repeated = -1; +static int hf_rlc_lte_sequence_analysis_skipped = -1; + +static int hf_rlc_lte_sequence_analysis_repeated_nack = -1; +static int hf_rlc_lte_sequence_analysis_repeated_nack_original_frame = -1; + +static int hf_rlc_lte_sequence_analysis_ack_out_of_range = -1; +static int hf_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame = -1; + +/* Reassembly */ +static int hf_rlc_lte_reassembly_source = -1; +static int hf_rlc_lte_reassembly_source_number_of_segments = -1; +static int hf_rlc_lte_reassembly_source_total_length = -1; +static int hf_rlc_lte_reassembly_source_segment = -1; +static int hf_rlc_lte_reassembly_source_segment_sn = -1; +static int hf_rlc_lte_reassembly_source_segment_framenum = -1; +static int hf_rlc_lte_reassembly_source_segment_length = -1; + +/* Subtrees. */ +static int ett_rlc_lte = -1; +static int ett_rlc_lte_context = -1; +static int ett_rlc_lte_um_header = -1; +static int ett_rlc_lte_am_header = -1; +static int ett_rlc_lte_extension_part = -1; +static int ett_rlc_lte_sequence_analysis = -1; +static int ett_rlc_lte_reassembly_source = -1; +static int ett_rlc_lte_reassembly_source_segment = -1; + +static expert_field ei_rlc_lte_context_mode = EI_INIT; +static expert_field ei_rlc_lte_am_nack_sn = EI_INIT; +static expert_field ei_rlc_lte_am_nack_sn_ahead_ack = EI_INIT; +static expert_field ei_rlc_lte_um_sn_repeated = EI_INIT; +static expert_field ei_rlc_lte_am_nack_sn_ack_same = EI_INIT; +static expert_field ei_rlc_lte_am_cpt = EI_INIT; +static expert_field ei_rlc_lte_am_data_no_data = EI_INIT; +static expert_field ei_rlc_lte_sequence_analysis_last_segment_complete = EI_INIT; +static expert_field ei_rlc_lte_sequence_analysis_mac_retx = EI_INIT; +static expert_field ei_rlc_lte_am_nack_sn_partial = EI_INIT; +static expert_field ei_rlc_lte_sequence_analysis_repeated_nack = EI_INIT; +static expert_field ei_rlc_lte_bytes_after_status_pdu_complete = EI_INIT; +static expert_field ei_rlc_lte_sequence_analysis_repeated = EI_INIT; +static expert_field ei_rlc_lte_wrong_sequence_number = EI_INIT; +static expert_field ei_rlc_lte_sequence_analysis_retx = EI_INIT; +static expert_field ei_rlc_lte_am_sn_missing = EI_INIT; +static expert_field ei_rlc_lte_um_sn = EI_INIT; +static expert_field ei_rlc_lte_header_only = EI_INIT; +static expert_field ei_rlc_lte_am_data_no_data_beyond_extensions = EI_INIT; +static expert_field ei_rlc_lte_um_sn_missing = EI_INIT; +static expert_field ei_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame = EI_INIT; +static expert_field ei_rlc_lte_sequence_analysis_last_segment_not_continued = EI_INIT; +static expert_field ei_rlc_lte_reserved_bits_not_zero = EI_INIT; +static expert_field ei_rlc_lte_no_per_frame_info = EI_INIT; +static expert_field ei_rlc_lte_unknown_udp_framing_tag = EI_INIT; +static expert_field ei_rlc_lte_missing_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"}, + { RLC_PREDEF, "PREDEFINED"}, /* For data testing */ + { 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_channel_type_vals[] = +{ + { CHANNEL_TYPE_CCCH, "CCCH"}, + { CHANNEL_TYPE_BCCH_BCH, "BCCH_BCH"}, + { CHANNEL_TYPE_PCCH, "PCCH"}, + { CHANNEL_TYPE_SRB, "SRB"}, + { CHANNEL_TYPE_DRB, "DRB"}, + { CHANNEL_TYPE_BCCH_DL_SCH, "BCCH_DL_SCH"}, + { CHANNEL_TYPE_MCCH, "MCCH"}, + { CHANNEL_TYPE_MTCH, "MTCH"}, + { 0, NULL } +}; + +static const value_string framing_info_vals[] = +{ + { 0, "First byte begins a RLC SDU and last byte ends a RLC SDU"}, + { 1, "First byte begins a RLC SDU and last byte does not end a RLC SDU"}, + { 2, "First byte does not begin a RLC SDU and last byte ends a RLC SDU"}, + { 3, "First byte does not begin a RLC SDU and last byte does not end a RLC SDU"}, + { 0, NULL } +}; + +static const value_string fixed_extension_vals[] = +{ + { 0, "Data field follows from the octet following the fixed part of the header"}, + { 1, "A set of E field and LI field follows from the octet following the fixed part of the header"}, + { 0, NULL } +}; + +static const value_string extension_extension_vals[] = +{ + { 0, "Data field follows from the octet following the LI field following this E field"}, + { 1, "A set of E field and LI field follows from the bit following the LI field following this E field"}, + { 0, NULL } +}; + +static const value_string data_or_control_vals[] = +{ + { 0, "Control PDU"}, + { 1, "Data PDU"}, + { 0, NULL } +}; + +static const value_string resegmentation_flag_vals[] = +{ + { 0, "AMD PDU"}, + { 1, "AMD PDU segment"}, + { 0, NULL } +}; + +static const value_string polling_bit_vals[] = +{ + { 0, "Status report not requested"}, + { 1, "Status report is requested"}, + { 0, NULL } +}; + +static const value_string lsf_vals[] = +{ + { 0, "Last byte of the AMD PDU segment does not correspond to the last byte of an AMD PDU"}, + { 1, "Last byte of the AMD PDU segment corresponds to the last byte of an AMD PDU"}, + { 0, NULL } +}; + +static const value_string control_pdu_type_vals[] = +{ + { 0, "STATUS PDU"}, + { 0, NULL } +}; + +static const value_string am_e1_vals[] = +{ + { 0, "A set of NACK_SN, E1 and E2 does not follow"}, + { 1, "A set of NACK_SN, E1 and E2 follows"}, + { 0, NULL } +}; + +static const value_string am_e2_vals[] = +{ + { 0, "A set of SOstart and SOend does not follow for this NACK_SN"}, + { 1, "A set of SOstart and SOend follows for this NACK_SN"}, + { 0, NULL } +}; + +static const value_string header_only_vals[] = +{ + { 0, "RLC PDU Headers and body present"}, + { 1, "RLC PDU Headers only"}, + { 0, NULL } +}; + + + +/**********************************************************************************/ +/* These are for keeping track of UM/AM extension headers, and the lengths found */ +/* in them */ +static guint8 s_number_of_extensions = 0; +#define MAX_RLC_SDUS 192 +static guint16 s_lengths[MAX_RLC_SDUS]; + + +/*********************************************************************/ +/* UM/AM sequence analysis */ + +/* Types for RLC channel hash table */ +/* This table is maintained during initial dissection of RLC */ +/* frames, mapping from channel_hash_key -> sequence_analysis_report */ + +/* Channel key */ +typedef struct +{ + guint ueId : 16; + guint channelType : 3; + guint channelId : 5; + guint direction : 1; +} channel_hash_key; + + +/******************************************************************/ +/* State maintained for AM/UM reassembly */ + +typedef struct rlc_segment { + guint32 frameNum; + guint16 SN; + guint8 *data; + guint16 length; +} rlc_segment; + +typedef struct rlc_channel_reassembly_info +{ + guint16 number_of_segments; + #define RLC_MAX_SEGMENTS 100 + rlc_segment segments[RLC_MAX_SEGMENTS]; +} rlc_channel_reassembly_info; + + + + +/*******************************************************************/ +/* Conversation-type status for sequence analysis on channel */ +typedef struct +{ + guint8 rlcMode; + + /* For UM, we always expect the SN to keep advancing, and these fields + keep track of this. + For AM, these correspond to new data */ + guint16 previousSequenceNumber; + guint32 previousFrameNum; + gboolean previousSegmentIncomplete; + + /* Accumulate info about current segmented SDU */ + struct rlc_channel_reassembly_info *reassembly_info; +} channel_sequence_analysis_status; + +/* The sequence analysis channel hash table */ +static wmem_map_t *sequence_analysis_channel_hash = NULL; + + +/* Types for sequence analysis frame report hash table */ +/* This is a table from framenum -> state_report_in_frame */ +/* This is necessary because the per-packet info is already being used */ +/* for context information before the dissector is called */ + +/* Info to attach to frame when first read, recording what to show about sequence */ +typedef enum { + SN_OK, SN_Repeated, SN_MAC_Retx, SN_Retx, SN_Missing, ACK_Out_of_Window, SN_Error +} sequence_analysis_state; + + +typedef struct +{ + gboolean sequenceExpectedCorrect; + guint16 sequenceExpected; + guint32 previousFrameNum; + gboolean previousSegmentIncomplete; + guint32 nextFrameNum; + + guint16 firstSN; + guint16 lastSN; + + /* AM/UM */ + sequence_analysis_state state; +} sequence_analysis_report; + + +/* The sequence analysis frame report hash table instance itself */ +static wmem_map_t *sequence_analysis_report_hash = NULL; + + +static gpointer get_report_hash_key(guint16 SN, guint32 frameNumber, + rlc_lte_info *p_rlc_lte_info, + gboolean do_persist); + + + + +/* The reassembly result hash table */ +static wmem_map_t *reassembly_report_hash = NULL; + + +/* Create a new struct for reassembly */ +static void reassembly_reset(channel_sequence_analysis_status *status) +{ + status->reassembly_info = wmem_new0(wmem_file_scope(), rlc_channel_reassembly_info); +} + +/* Hide previous one */ +static void reassembly_destroy(channel_sequence_analysis_status *status) +{ + /* Just "leak" it. There seems to be no way to free this memory... */ + status->reassembly_info = NULL; +} + +/* Add a new segment to the accumulating segmented SDU */ +static void reassembly_add_segment(channel_sequence_analysis_status *status, + guint16 SN, guint32 frame, + tvbuff_t *tvb, gint offset, gint length) +{ + int segment_number = status->reassembly_info->number_of_segments; + guint8 *segment_data; + + /* Give up if reach segment limit */ + if (segment_number >= (RLC_MAX_SEGMENTS-1)) { + reassembly_destroy(status); + return; + } + + segment_data = (guint8 *)tvb_memdup(wmem_file_scope(),tvb, offset, length); + + /* Add new segment */ + status->reassembly_info->segments[segment_number].frameNum = frame; + status->reassembly_info->segments[segment_number].SN = SN; + status->reassembly_info->segments[segment_number].data = segment_data; + status->reassembly_info->segments[segment_number].length = length; + + status->reassembly_info->number_of_segments++; +} + + +/* Record the current & complete segmented SDU by mapping from this frame number to + struct with segment info. */ +static void reassembly_record(channel_sequence_analysis_status *status, packet_info *pinfo, + guint16 SN, rlc_lte_info *p_rlc_lte_info) +{ + /* Just store existing info in hash table */ + wmem_map_insert(reassembly_report_hash, + get_report_hash_key(SN, pinfo->num, p_rlc_lte_info, TRUE), + status->reassembly_info); +} + +/* Create and return a tvb based upon contents of reassembly info */ +static tvbuff_t* reassembly_get_reassembled_tvb(rlc_channel_reassembly_info *reassembly_info, + tvbuff_t *parent_tvb, packet_info *pinfo) +{ + gint n; + guint combined_length = 0; + guint8 *combined_data; + guint combined_offset = 0; + tvbuff_t *reassembled_tvb; + + /* Allocate buffer big enough to hold re-assembled data */ + for (n=0; n < reassembly_info->number_of_segments; n++) { + combined_length += reassembly_info->segments[n].length; + } + combined_data = (guint8 *)wmem_alloc(pinfo->pool, combined_length); + + /* Copy data into contiguous buffer */ + for (n=0; n < reassembly_info->number_of_segments; n++) { + guint8 *data = reassembly_info->segments[n].data; + int length = reassembly_info->segments[n].length; + memcpy(combined_data+combined_offset, data, length); + combined_offset += length; + } + + /* Create and return tvb with this data */ + reassembled_tvb = tvb_new_child_real_data(parent_tvb, combined_data, combined_offset, combined_offset); + add_new_data_source(pinfo, reassembled_tvb, "Reassembled SDU"); + return reassembled_tvb; +} + +/* Show where the segments came from for a reassembled SDU */ +static void reassembly_show_source(rlc_channel_reassembly_info *reassembly_info, + proto_tree *tree, tvbuff_t *tvb, gint offset) +{ + int n; + proto_item *source_ti, *ti; + proto_tree *source_tree; + proto_item *segment_ti; + proto_tree *segment_tree; + guint total_length=0; + + /* Create root of source info */ + source_ti = proto_tree_add_item(tree, + hf_rlc_lte_reassembly_source, + tvb, 0, 0, ENC_ASCII); + source_tree = proto_item_add_subtree(source_ti, ett_rlc_lte_reassembly_source); + proto_item_set_generated(source_ti); + + for (n=0; n < reassembly_info->number_of_segments; n++) { + total_length += reassembly_info->segments[n].length; + } + proto_item_append_text(source_ti, " %u segments, %u bytes", reassembly_info->number_of_segments, + total_length); + + /* Number of segments */ + ti = proto_tree_add_uint(source_tree, + hf_rlc_lte_reassembly_source_number_of_segments, + tvb, 0, 0, reassembly_info->number_of_segments); + proto_item_set_generated(ti); + + /* Total length */ + ti = proto_tree_add_uint(source_tree, + hf_rlc_lte_reassembly_source_total_length, + tvb, 0, 0, total_length); + proto_item_set_generated(ti); + + /* Now add info about each segment in turn */ + for (n=0; n < reassembly_info->number_of_segments; n++) { + + /* Add next segment as a subtree */ + rlc_segment *segment = &(reassembly_info->segments[n]); + proto_item_append_text(source_ti, " (SN=%u frame=%u len=%u)", + segment->SN, segment->frameNum, segment->length); + + /* N.B. assume last segment from passed-in tvb! */ + segment_ti = proto_tree_add_item(source_tree, + hf_rlc_lte_reassembly_source_segment, + tvb, + (n == reassembly_info->number_of_segments-1) ? offset : 0, + (n == reassembly_info->number_of_segments-1) ? segment->length : 0, + ENC_NA); + segment_tree = proto_item_add_subtree(segment_ti, ett_rlc_lte_reassembly_source_segment); + proto_item_append_text(segment_ti, " (SN=%u frame=%u length=%u)", + segment->SN, segment->frameNum, segment->length); + proto_item_set_generated(segment_ti); + + /* Add details to segment tree */ + ti = proto_tree_add_uint(segment_tree, hf_rlc_lte_reassembly_source_segment_sn, + tvb, 0, 0, segment->SN); + proto_item_set_generated(ti); + ti = proto_tree_add_uint(segment_tree, hf_rlc_lte_reassembly_source_segment_framenum, + tvb, 0, 0, segment->frameNum); + proto_item_set_generated(ti); + ti = proto_tree_add_uint(segment_tree, hf_rlc_lte_reassembly_source_segment_length, + tvb, 0, 0, segment->length); + proto_item_set_generated(ti); + } +} + + + + +/******************************************************************/ +/* Conversation-type status for repeated NACK checking on channel */ +typedef struct +{ + guint16 noOfNACKs; + guint16 NACKs[MAX_NACKs]; + guint32 frameNum; +} channel_repeated_nack_status; + +static wmem_map_t *repeated_nack_channel_hash = NULL; + +typedef struct { + guint16 noOfNACKsRepeated; + guint16 repeatedNACKs[MAX_NACKs]; + guint32 previousFrameNum; +} channel_repeated_nack_report; + +static wmem_map_t *repeated_nack_report_hash = NULL; + + + +/********************************************************/ +/* Forward declarations & functions */ +static void dissect_rlc_lte_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, ...) G_GNUC_PRINTF(4, 5); +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); + } +} + + + +/* Dissect extension headers (common to both UM and AM) */ +static int dissect_rlc_lte_extension_header(tvbuff_t *tvb, packet_info *pinfo _U_, + proto_tree *tree, + int offset, + rlc_lte_info *p_rlc_lte_info) +{ + guint8 isOdd; + guint64 extension = 1; + guint64 length; + + /* Reset this count */ + s_number_of_extensions = 0; + + while (extension && (s_number_of_extensions < MAX_RLC_SDUS)) { + proto_tree *extension_part_tree; + proto_item *extension_part_ti; + + /* Extension part subtree */ + extension_part_ti = proto_tree_add_string_format(tree, + hf_rlc_lte_extension_part, + tvb, offset, 2, + "", + "Extension Part"); + extension_part_tree = proto_item_add_subtree(extension_part_ti, + ett_rlc_lte_extension_part); + + if (p_rlc_lte_info->extendedLiField == FALSE) { + isOdd = (s_number_of_extensions % 2); + + /* Read next extension */ + proto_tree_add_bits_ret_val(extension_part_tree, hf_rlc_lte_extension_e, tvb, + (offset*8) + ((isOdd) ? 4 : 0), + 1, + &extension, ENC_BIG_ENDIAN); + + /* Read length field */ + proto_tree_add_bits_ret_val(extension_part_tree, hf_rlc_lte_extension_li, tvb, + (offset*8) + ((isOdd) ? 5 : 1), + 11, + &length, ENC_BIG_ENDIAN); + + /* Move on to byte of next extension */ + if (isOdd) { + offset += 2; + } else { + offset++; + } + } else { + /* Read next extension */ + proto_tree_add_bits_ret_val(extension_part_tree, hf_rlc_lte_extension_e, tvb, + (offset*8), + 1, + &extension, ENC_BIG_ENDIAN); + + /* Read length field */ + proto_tree_add_bits_ret_val(extension_part_tree, hf_rlc_lte_extension_li, tvb, + (offset*8) + 1, + 15, + &length, ENC_BIG_ENDIAN); + + /* Move on to byte of next extension */ + offset += 2; + } + + proto_item_append_text(extension_part_tree, " (length=%u)", (guint16)length); + + s_lengths[s_number_of_extensions++] = (guint16)length; + } + + /* May need to skip padding after last extension part */ + isOdd = (s_number_of_extensions % 2); + if (isOdd && (p_rlc_lte_info->extendedLiField == FALSE)) { + proto_tree_add_item(tree, hf_rlc_lte_extension_padding, + tvb, offset++, 1, ENC_BIG_ENDIAN); + } + + return offset; +} + + +/* 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, + gboolean first_includes_start, + gboolean last_includes_end) +{ + /* Reflect this PDU in the info column */ + if (length > 0) { + write_pdu_label_and_info(top_ti, NULL, pinfo, + " %s%u-byte%s%s", + (first_includes_start) ? "[" : "..", + length, + (length > 1) ? "s" : "", + (last_includes_end) ? "]" : ".."); + } + else { + write_pdu_label_and_info(top_ti, NULL, pinfo, + " %sunknown-bytes%s", + (first_includes_start) ? "[" : "..", + (last_includes_end) ? "]" : ".."); + } +} + + +/* Show an SDU. If configured, pass to PDCP/RRC/IP dissector */ +static void show_PDU_in_tree(packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, gint offset, gint length, + rlc_lte_info *rlc_info, gboolean whole_pdu, rlc_channel_reassembly_info *reassembly_info, + sequence_analysis_state state) +{ + wmem_tree_key_t key[3]; + guint32 id; + rlc_ue_parameters *params; + + /* Add raw data (according to mode) */ + proto_item *data_ti = proto_tree_add_item(tree, + (rlc_info->rlcMode == RLC_AM_MODE) ? + hf_rlc_lte_am_data : + hf_rlc_lte_um_data, + tvb, offset, length, ENC_NA); + + if (whole_pdu || (reassembly_info != NULL)) { + if (((global_rlc_lte_call_pdcp_for_srb) && (rlc_info->channelType == CHANNEL_TYPE_SRB)) || + ((global_rlc_lte_call_pdcp_for_drb != PDCP_drb_off) && (rlc_info->channelType == CHANNEL_TYPE_DRB))) { + /* Send whole PDU to PDCP */ + + /* TODO: made static to avoid compiler warning... */ + static tvbuff_t *pdcp_tvb = NULL; + struct pdcp_lte_info *p_pdcp_lte_info; + + /* Get tvb for passing to LTE PDCP dissector */ + if (reassembly_info == NULL) { + pdcp_tvb = tvb_new_subset_length(tvb, offset, length); + } + else { + /* Get combined tvb. */ + pdcp_tvb = reassembly_get_reassembled_tvb(reassembly_info, tvb, pinfo); + reassembly_show_source(reassembly_info, tree, tvb, offset); + } + + /* Reuse or allocate struct */ + p_pdcp_lte_info = (pdcp_lte_info *)p_get_proto_data(wmem_file_scope(), pinfo, proto_pdcp_lte, 0); + if (p_pdcp_lte_info == NULL) { + p_pdcp_lte_info = wmem_new0(wmem_file_scope(), pdcp_lte_info); + /* Store info in packet */ + p_add_proto_data(wmem_file_scope(), pinfo, proto_pdcp_lte, 0, p_pdcp_lte_info); + } + + p_pdcp_lte_info->ueid = rlc_info->ueid; + if (rlc_info->nbMode == rlc_nb_mode) { + p_pdcp_lte_info->channelType = Channel_DCCH_NB; + } else { + p_pdcp_lte_info->channelType = Channel_DCCH; + } + p_pdcp_lte_info->channelId = rlc_info->channelId; + p_pdcp_lte_info->direction = rlc_info->direction; + p_pdcp_lte_info->is_retx = (state != SN_OK); + + /* Set plane and sequence number length */ + p_pdcp_lte_info->no_header_pdu = FALSE; + if (rlc_info->channelType == CHANNEL_TYPE_SRB) { + p_pdcp_lte_info->plane = SIGNALING_PLANE; + if ((rlc_info->nbMode == rlc_nb_mode) && (rlc_info->channelId == 3)) { + p_pdcp_lte_info->no_header_pdu = TRUE; + p_pdcp_lte_info->seqnum_length = 0; + } else { + p_pdcp_lte_info->seqnum_length = 5; + } + } + else { + p_pdcp_lte_info->plane = USER_PLANE; + switch (global_rlc_lte_call_pdcp_for_drb) { + case PDCP_drb_SN_7: + p_pdcp_lte_info->seqnum_length = 7; + break; + case PDCP_drb_SN_12: + p_pdcp_lte_info->seqnum_length = 12; + break; + case PDCP_drb_SN_15: + p_pdcp_lte_info->seqnum_length = 15; + break; + case PDCP_drb_SN_18: + p_pdcp_lte_info->seqnum_length = 18; + break; + case PDCP_drb_SN_signalled: + /* Use whatever was signalled (e.g. in RRC) */ + id = (rlc_info->channelId << 16) | rlc_info->ueid; + key[0].length = 1; + key[0].key = &id; + key[1].length = 1; + key[1].key = &pinfo->num; + key[2].length = 0; + key[2].key = NULL; + + params = (rlc_ue_parameters *)wmem_tree_lookup32_array_le(ue_parameters_tree, key); + if (params && (params->id != id)) { + params = NULL; + } + if (params) { + p_pdcp_lte_info->seqnum_length = params->pdcp_sn_bits; + } else if (rlc_info->nbMode == rlc_nb_mode) { + p_pdcp_lte_info->seqnum_length = 7; + } else { + p_pdcp_lte_info->seqnum_length = 12; + } + break; + + default: + DISSECTOR_ASSERT(FALSE); + break; + } + } + + TRY { + call_dissector_only(pdcp_lte_handle, pdcp_tvb, pinfo, tree, NULL); + } + CATCH_ALL { + } + ENDTRY + + proto_item_set_hidden(data_ti); + } + else if (global_rlc_lte_call_rrc_for_mcch && (rlc_info->channelType == CHANNEL_TYPE_MCCH)) { + /* Send whole PDU to RRC */ + static tvbuff_t *rrc_tvb = NULL; + + /* Get tvb for passing to LTE RRC dissector */ + if (reassembly_info == NULL) { + rrc_tvb = tvb_new_subset_length(tvb, offset, length); + } + else { + /* Get combined tvb. */ + rrc_tvb = reassembly_get_reassembled_tvb(reassembly_info, tvb, pinfo); + reassembly_show_source(reassembly_info, tree, tvb, offset); + } + + TRY { + call_dissector_only(lte_rrc_mcch, rrc_tvb, pinfo, tree, NULL); + } + CATCH_ALL { + } + ENDTRY + + proto_item_set_hidden(data_ti); + } + else if (global_rlc_lte_call_ip_for_mtch && (rlc_info->channelType == CHANNEL_TYPE_MTCH)) { + /* Send whole PDU to IP */ + static tvbuff_t *ip_tvb = NULL; + + /* Get tvb for passing to IP dissector */ + if (reassembly_info == NULL) { + ip_tvb = tvb_new_subset_length(tvb, offset, length); + } + else { + /* Get combined tvb. */ + ip_tvb = reassembly_get_reassembled_tvb(reassembly_info, tvb, pinfo); + reassembly_show_source(reassembly_info, tree, tvb, offset); + } + + TRY { + call_dissector_only(ip_handle, ip_tvb, pinfo, tree, NULL); + } + CATCH_ALL { + } + ENDTRY + + proto_item_set_hidden(data_ti); + } + } +} + +/* Hash table functions for RLC channels */ + +/* Equal keys */ +static gint rlc_channel_equal(gconstpointer v, gconstpointer v2) +{ + const channel_hash_key* val1 = (const channel_hash_key *)v; + const channel_hash_key* val2 = (const channel_hash_key *)v2; + + /* All fields must match */ + /* N.B. Currently fits into one word, so could return (*v == *v2) + if we're sure they're initialised to 0... */ + return ((val1->ueId == val2->ueId) && + (val1->channelType == val2->channelType) && + (val1->channelId == val2->channelId) && + (val1->direction == val2->direction)); +} + +/* Compute a hash value for a given key. */ +static guint rlc_channel_hash_func(gconstpointer v) +{ + const channel_hash_key* val1 = (const channel_hash_key *)v; + + /* TODO: check/reduce multipliers */ + return ((val1->ueId * 1024) + (val1->channelType*64) + (val1->channelId*2) + val1->direction); +} + + +/*************************************************************************/ +/* Result hash */ + +typedef struct { + guint32 frameNumber; + guint32 SN : 10; + guint32 channelType : 2; + guint32 channelId: 5; + guint32 direction: 1; +} rlc_result_hash_key; + +/* Compare 2 rlc_result_hash_key structs */ +static gint rlc_result_hash_equal(gconstpointer v, gconstpointer v2) +{ + const rlc_result_hash_key *val1 = (const rlc_result_hash_key *)v; + const rlc_result_hash_key *val2 = (const rlc_result_hash_key *)v2; + + /* All fields (and any padding...) must match */ + return (memcmp(val1, val2, sizeof(rlc_result_hash_key)) == 0); +} + +/* Compute a hash value for a given key. */ +static guint rlc_result_hash_func(gconstpointer v) +{ + const rlc_result_hash_key* val1 = (const rlc_result_hash_key *)v; + + /* Got rid of multipliers - no evidence that they reduced collisions */ + return val1->frameNumber + val1->SN + + val1->channelType + + val1->channelId + + val1->direction; +} + +/* Convenience function to get a pointer for the hash_func to work with */ +static gpointer get_report_hash_key(guint16 SN, guint32 frameNumber, + rlc_lte_info *p_rlc_lte_info, + gboolean do_persist) +{ + static rlc_result_hash_key key; + rlc_result_hash_key *p_key; + + /* Only allocate a struct when will be adding entry */ + if (do_persist) { + p_key = wmem_new0(wmem_file_scope(), rlc_result_hash_key); + } + else { + memset(&key, 0, sizeof(rlc_result_hash_key)); + p_key = &key; + } + + /* Fill in details, and return pointer */ + p_key->frameNumber = frameNumber; + p_key->SN = SN; + p_key->channelType = p_rlc_lte_info->channelType; + p_key->channelId = p_rlc_lte_info->channelId; + p_key->direction = p_rlc_lte_info->direction; + + return p_key; +} + +static void checkFIconsistency(sequence_analysis_report *p, + rlc_lte_info *p_rlc_lte_info, + gboolean newSegmentStarted, + proto_tree *seqnum_tree, + packet_info *pinfo, tvbuff_t *tvb) +{ + proto_item *ti; + + if (p->previousSegmentIncomplete) { + /* Previous segment was incomplete, so this PDU should continue it */ + if (newSegmentStarted) { + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct, + tvb, 0, 0, FALSE); + expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_last_segment_not_continued, + "Last segment of previous PDU was not continued for UE %u (%s-%u)", + p_rlc_lte_info->ueid, + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), + p_rlc_lte_info->channelId); + } + else { + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct, + tvb, 0, 0, TRUE); + proto_item_set_hidden(ti); + } + } + else { + /* Previous segment was complete, so this PDU should start a new one */ + if (!newSegmentStarted) { + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct, + tvb, 0, 0, FALSE); + expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_last_segment_complete, + "Last segment of previous PDU was complete, but new segment was not started on UE %u (%s-%u)", + p_rlc_lte_info->ueid, + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), + p_rlc_lte_info->channelId); + } + else { + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct, + tvb, 0, 0, TRUE); + proto_item_set_hidden(ti); + } + } + proto_item_set_generated(ti); +} + +/* Add to the tree values associated with sequence analysis for this frame */ +static void addChannelSequenceInfo(sequence_analysis_report *p, + gboolean isControlFrame, + rlc_lte_info *p_rlc_lte_info, + guint16 sequenceNumber, + gboolean newSegmentStarted, + rlc_lte_tap_info *tap_info, + packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb) +{ + proto_tree *seqnum_tree; + proto_item *seqnum_ti; + proto_item *ti; + + /* Create subtree */ + seqnum_ti = proto_tree_add_string_format(tree, + hf_rlc_lte_sequence_analysis, + tvb, 0, 0, + "", "Sequence Analysis"); + seqnum_tree = proto_item_add_subtree(seqnum_ti, + ett_rlc_lte_sequence_analysis); + proto_item_set_generated(seqnum_ti); + + if (p->previousFrameNum != 0) { + ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_previous_frame, + tvb, 0, 0, p->previousFrameNum); + proto_item_set_generated(ti); + } + + switch (p_rlc_lte_info->rlcMode) { + case RLC_AM_MODE: + + /********************************************/ + /* AM */ + /********************************************/ + + switch (p->state) { + case SN_OK: + if (isControlFrame) { + return; + } + + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, + tvb, 0, 0, TRUE); + proto_item_set_generated(ti); + proto_item_append_text(seqnum_ti, " - OK"); + + /* Link to next SN in channel (if known) */ + if (p->nextFrameNum != 0) { + proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_next_frame, + tvb, 0, 0, p->nextFrameNum); + } + /* Correct sequence number, so check frame indication bits consistent */ + /* Deactivated for now as it gets confused by resegmentation */ + /* checkFIconsistency(p, p_rlc_lte_info, newSegmentStarted, seqnum_tree, pinfo, tvb); */ + break; + + case SN_MAC_Retx: + if (isControlFrame) { + return; + } + + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, + tvb, 0, 0, FALSE); + proto_item_set_generated(ti); + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_mac_retx, + tvb, 0, 0, TRUE); + proto_item_set_generated(ti); + expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_mac_retx, + "AM Frame retransmitted for %s on UE %u - due to MAC retx! (%s-%u)", + val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), + p_rlc_lte_info->ueid, + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), + p_rlc_lte_info->channelId); + proto_item_append_text(seqnum_ti, " - MAC retx of SN %u", p->firstSN); + break; + + case SN_Retx: + if (isControlFrame) { + return; + } + + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, + tvb, 0, 0, FALSE); + proto_item_set_generated(ti); + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_retx, + tvb, 0, 0, TRUE); + proto_item_set_generated(ti); + expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_retx, + "AM Frame retransmitted for %s on UE %u - most likely in response to NACK (%s-%u)", + val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), + p_rlc_lte_info->ueid, + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), + p_rlc_lte_info->channelId); + proto_item_append_text(seqnum_ti, " - SN %u retransmitted", p->firstSN); + break; + + case SN_Repeated: + if (isControlFrame) { + return; + } + + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, + tvb, 0, 0, FALSE); + proto_item_set_generated(ti); + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated, + tvb, 0, 0, TRUE); + proto_item_set_generated(ti); + expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_repeated, + "AM SN Repeated for %s for UE %u - probably because didn't receive Status PDU? (%s-%u)", + val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), + p_rlc_lte_info->ueid, + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), + p_rlc_lte_info->channelId); + proto_item_append_text(seqnum_ti, "- SN %u Repeated", p->firstSN); + break; + + case SN_Missing: + if (isControlFrame) { + return; + } + + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, + tvb, 0, 0, FALSE); + proto_item_set_generated(ti); + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_skipped, + tvb, 0, 0, TRUE); + proto_item_set_generated(ti); + if (p->lastSN != p->firstSN) { + expert_add_info_format(pinfo, ti, &ei_rlc_lte_am_sn_missing, + "AM SNs (%u to %u) missing for %s on UE %u (%s-%u)", + p->firstSN, p->lastSN, + val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), + p_rlc_lte_info->ueid, + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), + p_rlc_lte_info->channelId); + proto_item_append_text(seqnum_ti, " - SNs missing (%u to %u)", + p->firstSN, p->lastSN); + if (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) { + tap_info->missingSNs = ((65536 + (guint32)p->lastSN - (guint32)p->firstSN) % 65536) + 1; + } else { + tap_info->missingSNs = ((1024 + p->lastSN - p->firstSN) % 1024) + 1; + } + } + else { + expert_add_info_format(pinfo, ti, &ei_rlc_lte_am_sn_missing, + "AM SN (%u) missing for %s on UE %u (%s-%u)", + p->firstSN, + val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), + p_rlc_lte_info->ueid, + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), + p_rlc_lte_info->channelId); + proto_item_append_text(seqnum_ti, " - SN missing (%u)", p->firstSN); + tap_info->missingSNs = 1; + } + break; + + case ACK_Out_of_Window: + if (!isControlFrame) { + return; + } + + + /* Not OK */ + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, + tvb, 0, 0, FALSE); + /* Out of range */ + proto_item_set_generated(ti); + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ack_out_of_range, + tvb, 0, 0, TRUE); + proto_item_set_generated(ti); + + /* Link back to last seen SN in other direction */ + ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame, + tvb, 0, 0, p->previousFrameNum); + proto_item_set_generated(ti); + + /* Expert error */ + expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame, + "AM ACK for SN %u - but last received SN in other direction is %u for UE %u (%s-%u)", + p->firstSN, p->sequenceExpected, + p_rlc_lte_info->ueid, + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), + p_rlc_lte_info->channelId); + proto_item_append_text(seqnum_ti, "- ACK SN %u Outside Rx Window - last received SN is %u", + p->firstSN, p->sequenceExpected); + + break; + + default: + return; + } + break; + + case RLC_UM_MODE: + + /********************************************/ + /* UM */ + /********************************************/ + + /* Expected sequence number */ + ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_expected_sn, + tvb, 0, 0, p->sequenceExpected); + proto_item_set_generated(ti); + if (p->sequenceExpectedCorrect) { + proto_item_set_hidden(ti); + } + + if (!p->sequenceExpectedCorrect) { + /* Work out SN wrap (in case needed below) */ + guint16 snLimit; + if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_5_BITS) { + snLimit = 32; + } + else { + snLimit = 1024; + } + + switch (p->state) { + case SN_Missing: + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, + tvb, 0, 0, FALSE); + proto_item_set_generated(ti); + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_skipped, + tvb, 0, 0, TRUE); + proto_item_set_generated(ti); + if (p->lastSN != p->firstSN) { + expert_add_info_format(pinfo, ti, &ei_rlc_lte_um_sn_missing, + "UM SNs (%u to %u) missing for %s on UE %u (%s-%u)", + p->firstSN, p->lastSN, + val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), + p_rlc_lte_info->ueid, + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), + p_rlc_lte_info->channelId); + proto_item_append_text(seqnum_ti, " - SNs missing (%u to %u)", + p->firstSN, p->lastSN); + tap_info->missingSNs = ((snLimit + p->lastSN - p->firstSN) % snLimit) + 1; + } + else { + expert_add_info_format(pinfo, ti, &ei_rlc_lte_um_sn_missing, + "UM SN (%u) missing for %s on UE %u (%s-%u)", + p->firstSN, + val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), + p_rlc_lte_info->ueid, + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), + p_rlc_lte_info->channelId); + proto_item_append_text(seqnum_ti, " - SN missing (%u)", + p->firstSN); + tap_info->missingSNs = 1; + } + break; + + case SN_Repeated: + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, + tvb, 0, 0, FALSE); + proto_item_set_generated(ti); + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated, + tvb, 0, 0, TRUE); + proto_item_set_generated(ti); + expert_add_info_format(pinfo, ti, &ei_rlc_lte_um_sn_repeated, + "UM SN (%u) repeated for %s for UE %u (%s-%u)", + p->firstSN, + val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), + p_rlc_lte_info->ueid, + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), + p_rlc_lte_info->channelId); + proto_item_append_text(seqnum_ti, "- SN %u Repeated", + p->firstSN); + break; + + case SN_MAC_Retx: + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, + tvb, 0, 0, FALSE); + proto_item_set_generated(ti); + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_mac_retx, + tvb, 0, 0, TRUE); + proto_item_set_generated(ti); + expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_mac_retx, + "UM Frame retransmitted for %s on UE %u - due to MAC retx! (%s-%u)", + val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), + p_rlc_lte_info->ueid, + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), + p_rlc_lte_info->channelId); + break; + + default: + /* Incorrect sequence number */ + expert_add_info_format(pinfo, ti, &ei_rlc_lte_wrong_sequence_number, + "Wrong Sequence Number for %s on UE %u - got %u, expected %u (%s-%u)", + val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), + p_rlc_lte_info->ueid, sequenceNumber, p->sequenceExpected, + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), + p_rlc_lte_info->channelId); + + break; + } + + } + else { + /* Correct sequence number, so check frame indication bits consistent */ + checkFIconsistency(p, p_rlc_lte_info, newSegmentStarted, seqnum_tree, pinfo, tvb); + + /* Set OK here! */ + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, + tvb, 0, 0, TRUE); + proto_item_set_generated(ti); + proto_item_append_text(seqnum_ti, " - OK"); + } + + /* Next channel frame */ + if (p->nextFrameNum != 0) { + ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_next_frame, + tvb, 0, 0, p->nextFrameNum); + proto_item_set_generated(ti); + } + } +} + +/* Update the channel status and set report for this frame */ +static sequence_analysis_state checkChannelSequenceInfo(packet_info *pinfo, tvbuff_t *tvb, + rlc_lte_info *p_rlc_lte_info, + gboolean isControlFrame, + guint8 number_of_segments, + guint16 firstSegmentOffset, + guint16 firstSegmentLength, + guint16 lastSegmentOffset, + guint16 sequenceNumber, + gboolean first_includes_start, gboolean last_includes_end, + gboolean is_resegmented _U_, + rlc_lte_tap_info *tap_info, + proto_tree *tree) +{ + channel_hash_key channel_key; + channel_hash_key *p_channel_key; + channel_sequence_analysis_status *p_channel_status; + sequence_analysis_report *p_report_in_frame = NULL; + gboolean createdChannel = FALSE; + guint16 expectedSequenceNumber = 0; + guint32 snLimit = 0; + + /* If find stat_report_in_frame already, use that and get out */ + if (pinfo->fd->visited) { + p_report_in_frame = (sequence_analysis_report*)wmem_map_lookup(sequence_analysis_report_hash, + get_report_hash_key(sequenceNumber, + pinfo->num, + p_rlc_lte_info, + FALSE)); + if (p_report_in_frame != NULL) { + addChannelSequenceInfo(p_report_in_frame, isControlFrame, p_rlc_lte_info, + sequenceNumber, first_includes_start, + tap_info, pinfo, tree, tvb); + return p_report_in_frame->state; + } + + /* Don't just give up here... */ + } + + + /**************************************************/ + /* Create or find an entry for this channel state */ + channel_key.ueId = p_rlc_lte_info->ueid; + channel_key.channelType = p_rlc_lte_info->channelType; + channel_key.channelId = p_rlc_lte_info->channelId; + channel_key.direction = p_rlc_lte_info->direction; + + /* Do the table lookup */ + p_channel_status = (channel_sequence_analysis_status*)wmem_map_lookup(sequence_analysis_channel_hash, &channel_key); + + /* Create table entry if necessary */ + if (p_channel_status == NULL) { + createdChannel = TRUE; + + /* Allocate a new value and duplicate key contents */ + p_channel_status = wmem_new0(wmem_file_scope(), channel_sequence_analysis_status); + p_channel_key = (channel_hash_key *)wmem_memdup(wmem_file_scope(), &channel_key, sizeof(channel_hash_key)); + + /* Set mode */ + p_channel_status->rlcMode = p_rlc_lte_info->rlcMode; + + /* Add entry */ + wmem_map_insert(sequence_analysis_channel_hash, p_channel_key, p_channel_status); + } + + /* Create space for frame state_report */ + p_report_in_frame = wmem_new0(wmem_file_scope(), sequence_analysis_report); + + + /* Deal with according to channel mode */ + switch (p_channel_status->rlcMode) { + case RLC_UM_MODE: + + if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_5_BITS) { + snLimit = 32; + } + else { + snLimit = 1024; + } + + /* Work out expected sequence number */ + if (!createdChannel) { + expectedSequenceNumber = (p_channel_status->previousSequenceNumber + 1) % snLimit; + } + else { + /* Whatever we got is fine.. */ + expectedSequenceNumber = sequenceNumber; + } + + if ((sequenceNumber == 0) && + ((p_rlc_lte_info->channelType == CHANNEL_TYPE_MCCH) || (p_rlc_lte_info->channelType == CHANNEL_TYPE_MTCH))) { + /* With eMBMS, SN restarts to 0 at each MCH Scheduling Period so we cannot deduce easily whether + there was a PDU loss or not without analysing the Frame Indicator; assume no loss when seeing 0 */ + expectedSequenceNumber = 0; + } + + /* Set report for this frame */ + /* For UM, sequence number is always expectedSequence number */ + p_report_in_frame->sequenceExpectedCorrect = (sequenceNumber == expectedSequenceNumber); + + /* For wrong sequence number... */ + if (!p_report_in_frame->sequenceExpectedCorrect) { + + /* Don't get confused by MAC (HARQ) retx */ + if (is_mac_lte_frame_retx(pinfo, p_rlc_lte_info->direction)) { + p_report_in_frame->state = SN_MAC_Retx; + p_report_in_frame->firstSN = sequenceNumber; + + /* No channel state to update */ + break; + } + + /* Frames are not missing if we get an earlier sequence number again */ + /* TODO: taking time into account would give better idea of whether missing or repeated... */ + else if ((p_rlc_lte_info->channelType == CHANNEL_TYPE_MCCH) || (p_rlc_lte_info->channelType == CHANNEL_TYPE_MTCH) || + (((snLimit + sequenceNumber - expectedSequenceNumber) % snLimit) < 10)) { + reassembly_destroy(p_channel_status); + + p_report_in_frame->state = SN_Missing; + tap_info->missingSNs = (snLimit + sequenceNumber - expectedSequenceNumber) % snLimit; + p_report_in_frame->firstSN = expectedSequenceNumber; + p_report_in_frame->lastSN = (snLimit + sequenceNumber - 1) % snLimit; + + p_report_in_frame->sequenceExpected = expectedSequenceNumber; + p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum; + p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete; + + /* Update channel status to remember *this* frame */ + p_channel_status->previousFrameNum = pinfo->num; + p_channel_status->previousSequenceNumber = sequenceNumber; + p_channel_status->previousSegmentIncomplete = !last_includes_end; + } + else { + /* An SN has been repeated */ + p_report_in_frame->state = SN_Repeated; + p_report_in_frame->firstSN = sequenceNumber; + + p_report_in_frame->sequenceExpected = expectedSequenceNumber; + p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum; + } + } + else { + /* SN was OK */ + p_report_in_frame->sequenceExpected = expectedSequenceNumber; + p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum; + p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete; + + /* Update channel status to remember *this* frame */ + p_channel_status->previousFrameNum = pinfo->num; + p_channel_status->previousSequenceNumber = sequenceNumber; + p_channel_status->previousSegmentIncomplete = !last_includes_end; + + if (p_channel_status->reassembly_info) { + /* Add next segment to reassembly info */ + reassembly_add_segment(p_channel_status, sequenceNumber, pinfo->num, + tvb, firstSegmentOffset, firstSegmentLength); + + /* The end of existing reassembly? */ + if (!first_includes_start && + ((number_of_segments > 1) || last_includes_end)) { + + reassembly_record(p_channel_status, pinfo, sequenceNumber, p_rlc_lte_info); + reassembly_destroy(p_channel_status); + } + } + + /* The start of a new reassembly? */ + if (!last_includes_end && + ((number_of_segments > 1) || first_includes_start)) { + + guint16 lastSegmentLength = tvb_reported_length(tvb)-lastSegmentOffset; + + if (global_rlc_lte_reassembly) { + reassembly_reset(p_channel_status); + reassembly_add_segment(p_channel_status, sequenceNumber, + pinfo->num, + tvb, lastSegmentOffset, lastSegmentLength); + } + } + + if (p_report_in_frame->previousFrameNum != 0) { + /* Get report for previous frame */ + sequence_analysis_report *p_previous_report; + if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_5_BITS) { + snLimit = 32; + } + else { + snLimit = 1024; + } + + /* Look up report for previous SN */ + p_previous_report = (sequence_analysis_report*)wmem_map_lookup(sequence_analysis_report_hash, + get_report_hash_key((sequenceNumber+snLimit-1) % snLimit, + p_report_in_frame->previousFrameNum, + p_rlc_lte_info, + FALSE)); + /* It really shouldn't be NULL... */ + if (p_previous_report != NULL) { + /* Point it forward to this one */ + p_previous_report->nextFrameNum = pinfo->num; + } + } + } + + break; + + case RLC_AM_MODE: + + if (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) { + snLimit = 65536; + } else { + snLimit = 1024; + } + + /* Work out expected sequence number */ + if (!createdChannel) { + expectedSequenceNumber = (p_channel_status->previousSequenceNumber + 1) % snLimit; + } + else { + /* Whatever we got is fine.. */ + expectedSequenceNumber = sequenceNumber; + } + + /* For AM, may be: + - expected Sequence number OR + - previous frame repeated + - old SN being sent (in response to NACK) + - new SN, but with frames missed out + Assume window whose front is at expectedSequenceNumber */ + + /* First of all, check to see whether frame is judged to be MAC Retx */ + if (is_mac_lte_frame_retx(pinfo, p_rlc_lte_info->direction)) { + /* Just report that this is a MAC Retx */ + p_report_in_frame->state = SN_MAC_Retx; + p_report_in_frame->firstSN = sequenceNumber; + + /* No channel state to update */ + break; + } + + if (sequenceNumber != expectedSequenceNumber) { + /* Don't trash reassembly info if this looks like a close retx... */ + if (((snLimit + sequenceNumber - expectedSequenceNumber) % snLimit) < 50) { + reassembly_destroy(p_channel_status); + } + } + + /* Expected? */ + if (sequenceNumber == expectedSequenceNumber) { + /* Set report for this frame */ + p_report_in_frame->sequenceExpectedCorrect = TRUE; + p_report_in_frame->sequenceExpected = expectedSequenceNumber; + p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum; + p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete; + p_report_in_frame->state = SN_OK; + + /* Update channel status */ + p_channel_status->previousSequenceNumber = sequenceNumber; + p_channel_status->previousFrameNum = pinfo->num; + p_channel_status->previousSegmentIncomplete = !last_includes_end; + + + if (p_channel_status->reassembly_info) { + + /* Add next segment to reassembly info */ + reassembly_add_segment(p_channel_status, sequenceNumber, pinfo->num, + tvb, firstSegmentOffset, firstSegmentLength); + + /* The end of existing reassembly? */ + if (!first_includes_start && + ((number_of_segments > 1) || last_includes_end)) { + + reassembly_record(p_channel_status, pinfo, + sequenceNumber, p_rlc_lte_info); + reassembly_destroy(p_channel_status); + } + } + + /* The start of a new reassembly? */ + if (!last_includes_end && + ((number_of_segments > 1) || first_includes_start)) { + + guint16 lastSegmentLength = tvb_reported_length(tvb)-lastSegmentOffset; + if (global_rlc_lte_reassembly) { + reassembly_reset(p_channel_status); + reassembly_add_segment(p_channel_status, sequenceNumber, + pinfo->num, + tvb, lastSegmentOffset, lastSegmentLength); + } + } + + if (p_report_in_frame->previousFrameNum != 0) { + /* Get report for previous frame */ + sequence_analysis_report *p_previous_report; + p_previous_report = (sequence_analysis_report*)wmem_map_lookup(sequence_analysis_report_hash, + get_report_hash_key((sequenceNumber+snLimit-1) % snLimit, + p_report_in_frame->previousFrameNum, + p_rlc_lte_info, + FALSE)); + /* It really shouldn't be NULL... */ + if (p_previous_report != NULL) { + /* Point it forward to this one */ + p_previous_report->nextFrameNum = pinfo->num; + } + } + + } + + /* Previous subframe repeated? */ + else if (((sequenceNumber+1) % snLimit) == expectedSequenceNumber) { + p_report_in_frame->state = SN_Repeated; + + /* Set report for this frame */ + p_report_in_frame->sequenceExpectedCorrect = FALSE; + p_report_in_frame->sequenceExpected = expectedSequenceNumber; + p_report_in_frame->firstSN = sequenceNumber; + p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum; + p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete; + + + /* Really should be nothing to update... */ + p_channel_status->previousSequenceNumber = sequenceNumber; + p_channel_status->previousFrameNum = pinfo->num; + p_channel_status->previousSegmentIncomplete = !last_includes_end; + } + + else { + /* Need to work out if new (with skips, or likely a retx (due to NACK)) */ + gint delta = (snLimit + expectedSequenceNumber - sequenceNumber) % snLimit; + + /* Rx window is 512/32768, so check to see if this is a retx */ + if (delta < (gint)(snLimit>>1)) { + /* Probably a retx due to receiving NACK */ + p_report_in_frame->state = SN_Retx; + + p_report_in_frame->firstSN = sequenceNumber; + /* Don't update anything in channel state */ + } + + else { + /* Ahead of expected SN. Assume frames have been missed */ + p_report_in_frame->state = SN_Missing; + + p_report_in_frame->firstSN = expectedSequenceNumber; + p_report_in_frame->lastSN = (snLimit + sequenceNumber-1) % snLimit; + + /* Update channel state - forget about missed SNs */ + p_report_in_frame->sequenceExpected = expectedSequenceNumber; + p_channel_status->previousSequenceNumber = sequenceNumber; + p_channel_status->previousFrameNum = pinfo->num; + p_channel_status->previousSegmentIncomplete = !last_includes_end; + } + } + break; + + default: + /* Shouldn't get here! */ + return SN_Error; + } + + /* Associate with this frame number */ + wmem_map_insert(sequence_analysis_report_hash, + get_report_hash_key(sequenceNumber, pinfo->num, p_rlc_lte_info, TRUE), + p_report_in_frame); + + /* Add state report for this frame into tree */ + addChannelSequenceInfo(p_report_in_frame, isControlFrame, p_rlc_lte_info, sequenceNumber, + first_includes_start, tap_info, pinfo, tree, tvb); + + return p_report_in_frame->state; +} + + +/* Add to the tree values associated with sequence analysis for this frame */ +static void addChannelRepeatedNACKInfo(channel_repeated_nack_report *p, + rlc_lte_info *p_rlc_lte_info, + packet_info *pinfo, proto_tree *tree, + tvbuff_t *tvb) +{ + proto_tree *seqnum_tree; + proto_item *seqnum_ti; + proto_item *ti; + gint n; + + /* Create subtree */ + seqnum_ti = proto_tree_add_string_format(tree, + hf_rlc_lte_sequence_analysis, + tvb, 0, 0, + "", "Sequence Analysis"); + seqnum_tree = proto_item_add_subtree(seqnum_ti, + ett_rlc_lte_sequence_analysis); + proto_item_set_generated(seqnum_ti); + + /* OK = FALSE */ + ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, + tvb, 0, 0, FALSE); + proto_item_set_generated(ti); + + /* Add each repeated NACK as item & expert info */ + for (n=0; n < p->noOfNACKsRepeated; n++) { + + ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated_nack, + tvb, 0, 0, p->repeatedNACKs[n]); + proto_item_set_generated(ti); + + expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_repeated_nack, + "Same SN (%u) NACKd for %s on UE %u in successive Status PDUs", + p->repeatedNACKs[n], + val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), + p_rlc_lte_info->ueid); + } + + /* Link back to previous status report */ + ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated_nack_original_frame, + tvb, 0, 0, p->previousFrameNum); + proto_item_set_generated(ti); + + /* Append count to sequence analysis root */ + proto_item_append_text(seqnum_ti, " - %u SNs repeated from previous Status PDU", + p->noOfNACKsRepeated); +} + + +/* Update the channel repeated NACK status and set report for this frame */ +static void checkChannelRepeatedNACKInfo(packet_info *pinfo, + rlc_lte_info *p_rlc_lte_info, + rlc_lte_tap_info *tap_info, + proto_tree *tree, + tvbuff_t *tvb) +{ + channel_hash_key channel_key; + channel_hash_key *p_channel_key; + channel_repeated_nack_status *p_channel_status; + channel_repeated_nack_report *p_report_in_frame = NULL; + + guint16 noOfNACKsRepeated = 0; + guint16 repeatedNACKs[MAX_NACKs]; + gint n, i, j; + + /* If find state_report_in_frame already, use that and get out */ + if (pinfo->fd->visited) { + p_report_in_frame = (channel_repeated_nack_report*)wmem_map_lookup(repeated_nack_report_hash, + get_report_hash_key(0, pinfo->num, + p_rlc_lte_info, FALSE)); + if (p_report_in_frame != NULL) { + addChannelRepeatedNACKInfo(p_report_in_frame, p_rlc_lte_info, + pinfo, tree, tvb); + return; + } + else { + /* Give up - we must have tried already... */ + return; + } + } + + + /**************************************************/ + /* Create or find an entry for this channel state */ + channel_key.ueId = p_rlc_lte_info->ueid; + channel_key.channelType = p_rlc_lte_info->channelType; + channel_key.channelId = p_rlc_lte_info->channelId; + channel_key.direction = p_rlc_lte_info->direction; + memset(repeatedNACKs, 0, sizeof(repeatedNACKs)); + + /* Do the table lookup */ + p_channel_status = (channel_repeated_nack_status*)wmem_map_lookup(repeated_nack_channel_hash, &channel_key); + + /* Create table entry if necessary */ + if (p_channel_status == NULL) { + + /* Allocate a new key and value */ + p_channel_key = wmem_new(wmem_file_scope(), channel_hash_key); + p_channel_status = wmem_new0(wmem_file_scope(), channel_repeated_nack_status); + + /* Copy key contents */ + memcpy(p_channel_key, &channel_key, sizeof(channel_hash_key)); + + /* Add entry to table */ + wmem_map_insert(repeated_nack_channel_hash, p_channel_key, p_channel_status); + } + + /* Compare NACKs in channel status with NACKs in tap_info. + Note any that are repeated */ + for (i=0; i < p_channel_status->noOfNACKs; i++) { + for (j=0; j < MIN(tap_info->noOfNACKs, MAX_NACKs); j++) { + if (tap_info->NACKs[j] == p_channel_status->NACKs[i]) { + /* Don't add the same repeated NACK twice! */ + if ((noOfNACKsRepeated == 0) || + (repeatedNACKs[noOfNACKsRepeated-1] != p_channel_status->NACKs[i])) { + + repeatedNACKs[noOfNACKsRepeated++] = p_channel_status->NACKs[i]; + } + } + } + } + + /* Copy NACKs from tap_info into channel status for next time! */ + p_channel_status->noOfNACKs = 0; + for (n=0; n < MIN(tap_info->noOfNACKs, MAX_NACKs); n++) { + p_channel_status->NACKs[p_channel_status->noOfNACKs++] = tap_info->NACKs[n]; + } + + if (noOfNACKsRepeated >= 1) { + /* Create space for frame state_report */ + p_report_in_frame = wmem_new(wmem_file_scope(), channel_repeated_nack_report); + + /* Copy in found duplicates */ + for (n=0; n < MIN(tap_info->noOfNACKs, MAX_NACKs); n++) { + p_report_in_frame->repeatedNACKs[n] = repeatedNACKs[n]; + } + p_report_in_frame->noOfNACKsRepeated = noOfNACKsRepeated; + + p_report_in_frame->previousFrameNum = p_channel_status->frameNum; + + /* Associate with this frame number */ + wmem_map_insert(repeated_nack_report_hash, + get_report_hash_key(0, pinfo->num, + p_rlc_lte_info, TRUE), + p_report_in_frame); + + /* Add state report for this frame into tree */ + addChannelRepeatedNACKInfo(p_report_in_frame, p_rlc_lte_info, + pinfo, tree, tvb); + } + + /* Save frame number for next comparison */ + p_channel_status->frameNum = pinfo->num; +} + +/* Check that the ACK is consistent with data the expected sequence number + in the other direction */ +static void checkChannelACKWindow(guint16 ack_sn, + packet_info *pinfo, + rlc_lte_info *p_rlc_lte_info, + rlc_lte_tap_info *tap_info, + proto_tree *tree, + tvbuff_t *tvb) +{ + channel_hash_key channel_key; + channel_sequence_analysis_status *p_channel_status; + sequence_analysis_report *p_report_in_frame = NULL; + guint32 snLimit; + + /* If find stat_report_in_frame already, use that and get out */ + if (pinfo->fd->visited) { + p_report_in_frame = (sequence_analysis_report*)wmem_map_lookup(sequence_analysis_report_hash, + get_report_hash_key(0, pinfo->num, + p_rlc_lte_info, + FALSE)); + if (p_report_in_frame != NULL) { + /* Add any info to tree */ + addChannelSequenceInfo(p_report_in_frame, TRUE, p_rlc_lte_info, + 0, FALSE, + tap_info, pinfo, tree, tvb); + return; + } + else { + /* Give up - we must have tried already... */ + return; + } + } + + /*******************************************************************/ + /* Find an entry for this channel state (in the opposite direction */ + channel_key.ueId = p_rlc_lte_info->ueid; + channel_key.channelType = p_rlc_lte_info->channelType; + channel_key.channelId = p_rlc_lte_info->channelId; + channel_key.direction = + (p_rlc_lte_info->direction == DIRECTION_UPLINK) ? DIRECTION_DOWNLINK : DIRECTION_UPLINK; + + /* Do the table lookup */ + p_channel_status = (channel_sequence_analysis_status*)wmem_map_lookup(sequence_analysis_channel_hash, &channel_key); + + /* Create table entry if necessary */ + if (p_channel_status == NULL) { + return; + } + + /* Is it in the rx window? This test will catch if it's ahead, but we don't + really know what the back of the tx window is... */ + snLimit = (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) ? 65536 : 1024; + if (((snLimit + (guint32)p_channel_status->previousSequenceNumber+1 - ack_sn) % snLimit) > (snLimit>>1)) { + + /* Set result */ + p_report_in_frame = wmem_new0(wmem_file_scope(), sequence_analysis_report); + p_report_in_frame->state = ACK_Out_of_Window; + p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum; + p_report_in_frame->sequenceExpected = p_channel_status->previousSequenceNumber; + p_report_in_frame->firstSN = ack_sn; + + /* Associate with this frame number */ + wmem_map_insert(sequence_analysis_report_hash, + get_report_hash_key(0, pinfo->num, + p_rlc_lte_info, TRUE), + p_report_in_frame); + + /* Add state report for this frame into tree */ + addChannelSequenceInfo(p_report_in_frame, TRUE, p_rlc_lte_info, 0, + FALSE, tap_info, pinfo, tree, tvb); + } +} + + + + +/***************************************************/ +/* Transparent mode PDU. Call RRC if configured to */ +static void dissect_rlc_lte_tm(tvbuff_t *tvb, packet_info *pinfo, + proto_tree *tree, + int offset, + rlc_lte_info *p_rlc_lte_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_lte_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_lte_tm_data, tvb, offset, -1, ENC_NA); + if (!global_rlc_lte_call_rrc_for_ccch) { + write_pdu_label_and_info(top_ti, NULL, pinfo, + " [%u-bytes]", tvb_reported_length_remaining(tvb, offset)); + } + + if (global_rlc_lte_call_rrc_for_ccch) { + tvbuff_t *rrc_tvb = tvb_new_subset_remaining(tvb, offset); + volatile dissector_handle_t protocol_handle; + + switch (p_rlc_lte_info->channelType) { + case CHANNEL_TYPE_CCCH: + if (p_rlc_lte_info->direction == DIRECTION_UPLINK) { + protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ? + lte_rrc_ul_ccch_nb : lte_rrc_ul_ccch; + } + else { + protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ? + lte_rrc_dl_ccch_nb : lte_rrc_dl_ccch; + } + break; + + case CHANNEL_TYPE_BCCH_BCH: + protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ? + lte_rrc_bcch_bch_nb : lte_rrc_bcch_bch; + break; + case CHANNEL_TYPE_BCCH_DL_SCH: + protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ? + lte_rrc_bcch_dl_sch_nb : lte_rrc_bcch_dl_sch; + break; + case CHANNEL_TYPE_PCCH: + protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ? + lte_rrc_pcch_nb : lte_rrc_pcch; + break; + + case CHANNEL_TYPE_SRB: + case CHANNEL_TYPE_DRB: + case CHANNEL_TYPE_MCCH: + case CHANNEL_TYPE_MTCH: + + 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 + } +} + + + +/***************************************************/ +/* Unacknowledged mode PDU */ +static void dissect_rlc_lte_um(tvbuff_t *tvb, packet_info *pinfo, + proto_tree *tree, + int offset, + rlc_lte_info *p_rlc_lte_info, + proto_item *top_ti, + rlc_lte_tap_info *tap_info) +{ + guint64 framing_info; + gboolean first_includes_start; + gboolean last_includes_end; + guint64 fixed_extension; + guint64 sn; + gint start_offset = offset; + proto_item *um_ti; + proto_tree *um_header_tree; + proto_item *um_header_ti; + gboolean is_truncated = FALSE; + proto_item *truncated_ti; + rlc_channel_reassembly_info *reassembly_info = NULL; + sequence_analysis_state seq_anal_state = SN_OK; + + /* Hidden UM root */ + um_ti = proto_tree_add_string_format(tree, hf_rlc_lte_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_lte_um_header, + tvb, offset, 0, + "", "UM header"); + um_header_tree = proto_item_add_subtree(um_header_ti, + ett_rlc_lte_um_header); + + + /*******************************/ + /* Fixed UM header */ + if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_5_BITS) { + /* Framing info (2 bits) */ + proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_fi, + tvb, offset*8, 2, + &framing_info, ENC_BIG_ENDIAN); + + /* Extension (1 bit) */ + proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_fixed_e, tvb, + (offset*8) + 2, 1, + &fixed_extension, ENC_BIG_ENDIAN); + + /* Sequence Number (5 bit) */ + proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_sn, tvb, + (offset*8) + 3, 5, + &sn, ENC_BIG_ENDIAN); + offset++; + } + else if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_10_BITS) { + guint32 reserved; + proto_item *ti; + + /* Check 3 Reserved bits */ + ti = proto_tree_add_item_ret_uint(um_header_tree, hf_rlc_lte_um_fixed_reserved, tvb, offset, 1, ENC_BIG_ENDIAN, &reserved); + if (reserved != 0) { + expert_add_info_format(pinfo, ti, &ei_rlc_lte_reserved_bits_not_zero, + "RLC UM Fixed header Reserved bits not zero (found 0x%x)", reserved); + } + + /* Framing info (2 bits) */ + proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_fi, + tvb, (offset*8)+3, 2, + &framing_info, ENC_BIG_ENDIAN); + + /* Extension (1 bit) */ + proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_fixed_e, tvb, + (offset*8) + 5, 1, + &fixed_extension, ENC_BIG_ENDIAN); + + /* Sequence Number (10 bits) */ + proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_sn, tvb, + (offset*8) + 6, 10, + &sn, ENC_BIG_ENDIAN); + offset += 2; + } + else { + /* Invalid length of sequence number */ + proto_tree_add_expert_format(um_header_tree, pinfo, &ei_rlc_lte_um_sn, tvb, 0, 0, + "Invalid sequence number length (%u bits)", + p_rlc_lte_info->sequenceNumberLength); + return; + } + + tap_info->sequenceNumber = (guint16)sn; + + /* Show SN in info column */ + if ((p_rlc_lte_info->channelType == CHANNEL_TYPE_MCCH) || (p_rlc_lte_info->channelType == CHANNEL_TYPE_MTCH)) { + write_pdu_label_and_info(top_ti, um_header_ti, pinfo, " sn=%-4u", (guint16)sn); + } + else { + write_pdu_label_and_info(top_ti, um_header_ti, pinfo, " sn=%-4u", (guint16)sn); + } + + proto_item_set_len(um_header_ti, offset-start_offset); + + + /*************************************/ + /* UM header extension */ + if (fixed_extension) { + offset = dissect_rlc_lte_extension_header(tvb, pinfo, um_header_tree, offset, p_rlc_lte_info); + } + + /* Extract these 2 flags from framing_info */ + first_includes_start = ((guint8)framing_info & 0x02) == 0; + last_includes_end = ((guint8)framing_info & 0x01) == 0; + + if (global_rlc_lte_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_uint(tree, hf_rlc_lte_header_only, tvb, 0, 0, + is_truncated); + if (is_truncated) { + int n; + proto_item_set_generated(truncated_ti); + expert_add_info(pinfo, truncated_ti, &ei_rlc_lte_header_only); + + /* Show in the info column how long the data would be */ + for (n=0; n < s_number_of_extensions; n++) { + show_PDU_in_info(pinfo, top_ti, s_lengths[n], + (n==0) ? first_includes_start : TRUE, + TRUE); + offset += s_lengths[n]; + } + /* Last one */ + show_PDU_in_info(pinfo, top_ti, p_rlc_lte_info->pduLength - offset, + (s_number_of_extensions == 0) ? first_includes_start : TRUE, + last_includes_end); + } + else { + proto_item_set_hidden(truncated_ti); + } + } + + /* Show number of extensions in header root */ + if (s_number_of_extensions > 0) { + proto_item_append_text(um_header_ti, " (%u extensions)", s_number_of_extensions); + } + + /* Call sequence analysis function now */ + if (((global_rlc_lte_um_sequence_analysis == SEQUENCE_ANALYSIS_MAC_ONLY) && + (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) != NULL)) || + ((global_rlc_lte_um_sequence_analysis == SEQUENCE_ANALYSIS_RLC_ONLY) && + (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) == NULL))) { + + guint16 lastSegmentOffset = offset; + if (s_number_of_extensions >= 1) { + int n; + lastSegmentOffset = offset; + for (n=0; n < s_number_of_extensions; n++) { + lastSegmentOffset += s_lengths[n]; + } + } + + seq_anal_state = checkChannelSequenceInfo(pinfo, tvb, p_rlc_lte_info, + FALSE, + s_number_of_extensions+1, + offset, + s_number_of_extensions ? + s_lengths[0] : + p_rlc_lte_info->pduLength - offset, + lastSegmentOffset, + (guint16)sn, first_includes_start, last_includes_end, + FALSE, /* UM doesn't re-segment */ + tap_info, um_header_tree); + } + + if (is_truncated) { + return; + } + + /*************************************/ + /* Data */ + + reassembly_info = (rlc_channel_reassembly_info *)wmem_map_lookup(reassembly_report_hash, + get_report_hash_key((guint16)sn, pinfo->num, + p_rlc_lte_info, FALSE)); + + if (s_number_of_extensions > 0) { + /* Show each data segment separately */ + int n; + for (n=0; n < s_number_of_extensions; n++) { + show_PDU_in_tree(pinfo, tree, tvb, offset, s_lengths[n], p_rlc_lte_info, + (n==0) ? first_includes_start : TRUE, + (n==0) ? reassembly_info : NULL, + seq_anal_state); + show_PDU_in_info(pinfo, top_ti, s_lengths[n], + (n==0) ? first_includes_start : TRUE, + TRUE); + /* Make sure we don't lose the summary of this SDU */ + col_append_str(pinfo->cinfo, COL_INFO, " | "); + col_set_fence(pinfo->cinfo, COL_INFO); + + tvb_ensure_bytes_exist(tvb, offset, s_lengths[n]); + offset += s_lengths[n]; + } + } + + /* Final data element */ + show_PDU_in_tree(pinfo, tree, tvb, offset, tvb_reported_length_remaining(tvb, offset), p_rlc_lte_info, + ((s_number_of_extensions == 0) ? first_includes_start : TRUE) && last_includes_end, + (s_number_of_extensions == 0) ? reassembly_info : NULL, + seq_anal_state); + show_PDU_in_info(pinfo, top_ti, (guint16)tvb_reported_length_remaining(tvb, offset), + (s_number_of_extensions == 0) ? first_includes_start : TRUE, + last_includes_end); +} + + + +/* Dissect an AM STATUS PDU */ +static void dissect_rlc_lte_am_status_pdu(tvbuff_t *tvb, + packet_info *pinfo, + proto_tree *tree, + proto_item *status_ti, + int offset, + proto_item *top_ti, + rlc_lte_info *p_rlc_lte_info, + rlc_lte_tap_info *tap_info) +{ + guint32 cpt; + guint8 sn_size, so_size; + guint32 sn_limit; + guint64 ack_sn, nack_sn; + guint16 nack_count = 0, so_end_of_pdu; + guint64 e1 = 0, e2 = 0; + guint64 so_start, so_end; + int bit_offset = offset * 8; + proto_item *ti; + + /****************************************************************/ + /* Part of RLC control PDU header */ + + /* Control PDU Type (CPT) */ + ti = proto_tree_add_item_ret_uint(tree, hf_rlc_lte_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_lte_am_cpt, + "RLC Control frame type %u not handled", cpt); + return; + } + + if (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) { + sn_size = 16; + sn_limit = 65536; + so_size = 16; + so_end_of_pdu = 0xffff; + } else { + sn_size = 10; + sn_limit = 1024; + so_size = 15; + so_end_of_pdu = 0x7fff; + } + + /* The Status PDU itself starts 4 bits into the byte */ + bit_offset += 4; + + /* ACK SN */ + proto_tree_add_bits_ret_val(tree, hf_rlc_lte_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=%-4u", (guint16)ack_sn); + + tap_info->ACKNo = (guint16)ack_sn; + + /* E1 */ + proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e1, tvb, + bit_offset, 1, &e1, ENC_BIG_ENDIAN); + + /* Skip another bit to byte-align the next bit... */ + bit_offset++; + + /* Optional, extra fields */ + do { + if (e1) { + proto_item *nack_ti; + + /****************************/ + /* Read NACK_SN, E1, E2 */ + + /* NACK_SN */ + nack_ti = proto_tree_add_bits_ret_val(tree, hf_rlc_lte_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=%-4u", (guint16)nack_sn); + + /* We shouldn't NACK the ACK_SN! */ + if (nack_sn == ack_sn) { + expert_add_info_format(pinfo, nack_ti, &ei_rlc_lte_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_lte_am_nack_sn_ahead_ack); + } + + /* Copy into struct, but don't exceed buffer */ + if (nack_count < MAX_NACKs) { + tap_info->NACKs[nack_count++] = (guint16)nack_sn; + } + else { + /* Let it get bigger than the array for accurate stats... */ + nack_count++; + } + + /* E1 */ + proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e1, tvb, + bit_offset, 1, &e1, ENC_BIG_ENDIAN); + bit_offset++; + + /* E2 */ + proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e2, tvb, + bit_offset, 1, &e2, ENC_BIG_ENDIAN); + + /* Report as expert info */ + if (e2) { + expert_add_info_format(pinfo, nack_ti, &ei_rlc_lte_am_nack_sn_partial, + "Status PDU reports NACK (partial) on %s for UE %u", + val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), + p_rlc_lte_info->ueid); + } + else { + expert_add_info_format(pinfo, nack_ti, &ei_rlc_lte_am_nack_sn, + "Status PDU reports NACK on %s for UE %u", + val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), + p_rlc_lte_info->ueid); + } + + bit_offset++; + } + + if (e2) { + /* Read SOstart, SOend */ + proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_so_start, tvb, + bit_offset, so_size, &so_start, ENC_BIG_ENDIAN); + bit_offset += so_size; + + proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_so_end, tvb, + bit_offset, so_size, &so_end, ENC_BIG_ENDIAN); + bit_offset += so_size; + + + if ((guint16)so_end == so_end_of_pdu) { + write_pdu_label_and_info(top_ti, NULL, pinfo, + " (SOstart=%u SOend=<END-OF_PDU>)", + (guint16)so_start); + } + else { + write_pdu_label_and_info(top_ti, NULL, pinfo, + " (SOstart=%u SOend=%u)", + (guint16)so_start, (guint16)so_end); + } + + /* Reset this flag here */ + e2 = 0; + } + } while (e1); + + if (nack_count > 0) { + proto_item *count_ti = proto_tree_add_uint(tree, hf_rlc_lte_am_nacks, tvb, 0, 1, nack_count); + proto_item_set_generated(count_ti); + proto_item_append_text(status_ti, " (%u NACKs)", nack_count); + tap_info->noOfNACKs = 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_lte_bytes_after_status_pdu_complete, + "%cL %u bytes remaining after Status PDU complete", + (p_rlc_lte_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); + + /* Repeated NACK analysis & check ACK-SN is in range */ + if (((global_rlc_lte_am_sequence_analysis == SEQUENCE_ANALYSIS_MAC_ONLY) && + (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) != NULL)) || + ((global_rlc_lte_am_sequence_analysis == SEQUENCE_ANALYSIS_RLC_ONLY) && + (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) == NULL))) { + + if (!is_mac_lte_frame_retx(pinfo, p_rlc_lte_info->direction)) { + checkChannelRepeatedNACKInfo(pinfo, p_rlc_lte_info, tap_info, tree, tvb); + checkChannelACKWindow((guint16)ack_sn, pinfo, p_rlc_lte_info, tap_info, tree, tvb); + } + } +} + + +/***************************************************/ +/* Acknowledged mode PDU */ +static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo, + proto_tree *tree, + int offset, + rlc_lte_info *p_rlc_lte_info, + proto_item *top_ti, + rlc_lte_tap_info *tap_info) +{ + guint32 is_data; + guint32 is_resegmented; + guint32 polling; + guint32 fixed_extension; + guint32 framing_info; + gboolean first_includes_start; + gboolean last_includes_end; + proto_item *am_ti; + proto_tree *am_header_tree; + proto_item *am_header_ti; + gint start_offset = offset; + guint32 sn; + gboolean is_truncated = FALSE; + proto_item *truncated_ti; + rlc_channel_reassembly_info *reassembly_info = NULL; + sequence_analysis_state seq_anal_state = SN_OK; + guint32 id; + wmem_tree_key_t key[3]; + rlc_ue_parameters *params; + + /* Hidden AM root */ + am_ti = proto_tree_add_string_format(tree, hf_rlc_lte_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_lte_am_header, + tvb, offset, 0, + "", "AM Header "); + am_header_tree = proto_item_add_subtree(am_header_ti, + ett_rlc_lte_am_header); + + /* First bit is Data/Control flag */ + proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_data_control, tvb, offset, 1, ENC_BIG_ENDIAN, &is_data); + tap_info->isControlPDU = !is_data; + + if (!is_data) { + /**********************/ + /* Status PDU */ + write_pdu_label_and_info_literal(top_ti, NULL, pinfo, " [CONTROL]"); + + /* Control PDUs are a completely separate format */ + dissect_rlc_lte_am_status_pdu(tvb, pinfo, am_header_tree, am_header_ti, + offset, top_ti, + p_rlc_lte_info, tap_info); + return; + } + + /******************************/ + /* Data PDU fixed header */ + + /* Re-segmentation Flag (RF) field */ + proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_rf, tvb, offset, 1, + ENC_BIG_ENDIAN, &is_resegmented); + tap_info->isResegmented = is_resegmented; + + write_pdu_label_and_info_literal(top_ti, NULL, pinfo, + (is_resegmented) ? " [DATA-SEGMENT]" : " [DATA]"); + + /* Polling bit */ + proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_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) "); + } + + /* Framing Info */ + proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_fi, tvb, offset, 1, + ENC_BIG_ENDIAN, &framing_info); + + /* Extension bit */ + proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_fixed_e, tvb, offset, 1, + ENC_BIG_ENDIAN, &fixed_extension); + + /* Sequence Number */ + if (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) { + guint32 reserved; + + if (is_resegmented) { + /* Last Segment Field (LSF) */ + proto_tree_add_item(am_header_tree, hf_rlc_lte_am_segment_lsf16, tvb, offset, 1, ENC_BIG_ENDIAN); + /* Reserved (R1) */ + am_ti = proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_fixed_reserved2, tvb, offset, 1, ENC_BIG_ENDIAN, &reserved); + } else { + /* Reserved (R1) */ + am_ti = proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_fixed_reserved, tvb, offset, 1, ENC_BIG_ENDIAN, &reserved); + } + if (reserved != 0) { + expert_add_info_format(pinfo, am_ti, &ei_rlc_lte_reserved_bits_not_zero, + "RLC AM Fixed header Reserved bits not zero (found 0x02%x)", reserved); + } + offset += 1; + proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_fixed_sn16, tvb, offset, 2, ENC_BIG_ENDIAN, &sn); + offset += 2; + } else { + proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_fixed_sn, tvb, offset, 2, ENC_BIG_ENDIAN, &sn); + offset += 2; + } + tap_info->sequenceNumber = sn; + + write_pdu_label_and_info(top_ti, am_header_ti, pinfo, "sn=%-4u", sn); + + /***************************************/ + /* Dissect extra segment header fields */ + if (is_resegmented) { + guint32 segmentOffset; + + if (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) { + /* SO */ + proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_segment_so16, tvb, offset, 2, ENC_BIG_ENDIAN, &segmentOffset); + } else { + /* Last Segment Field (LSF) */ + proto_tree_add_item(am_header_tree, hf_rlc_lte_am_segment_lsf, tvb, offset, 1, ENC_BIG_ENDIAN); + + /* SO */ + proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_segment_so, tvb, offset, 2, ENC_BIG_ENDIAN, &segmentOffset); + } + write_pdu_label_and_info(top_ti, am_header_ti, pinfo, " SO=%u ", segmentOffset); + offset += 2; + } + + /*************************************/ + /* AM header extension */ + if (fixed_extension) { + if (!PINFO_FD_VISITED(pinfo)) { + id = (p_rlc_lte_info->channelId << 16) | p_rlc_lte_info->ueid; + key[0].length = 1; + key[0].key = &id; + key[1].length = 1; + key[1].key = &pinfo->num; + key[2].length = 0; + key[2].key = NULL; + params = (rlc_ue_parameters *)wmem_tree_lookup32_array_le(ue_parameters_tree, key); + if (params && (params->id == id)) { + p_rlc_lte_info->extendedLiField = (p_rlc_lte_info->direction == DIRECTION_UPLINK) ? + (params->ext_li_field & UL_EXT_LI): (params->ext_li_field & DL_EXT_LI); + } + } + offset = dissect_rlc_lte_extension_header(tvb, pinfo, am_header_tree, offset, p_rlc_lte_info); + } + + /* Header is now complete */ + proto_item_set_len(am_header_ti, offset-start_offset); + + /* Show number of extensions in header root */ + if (s_number_of_extensions > 0) { + proto_item_append_text(am_header_ti, " (%u extensions)", s_number_of_extensions); + } + + /* Extract these 2 flags from framing_info */ + first_includes_start = (framing_info & 0x02) == 0; + last_includes_end = (framing_info & 0x01) == 0; + + /* There might not be any data, if only headers (plus control data) were logged */ + if (global_rlc_lte_headers_expected) { + is_truncated = (tvb_captured_length_remaining(tvb, offset) == 0); + truncated_ti = proto_tree_add_uint(tree, hf_rlc_lte_header_only, tvb, 0, 0, + is_truncated); + if (is_truncated) { + int n; + proto_item_set_generated(truncated_ti); + expert_add_info(pinfo, truncated_ti, &ei_rlc_lte_header_only); + /* Show in the info column how long the data would be */ + for (n=0; n < s_number_of_extensions; n++) { + show_PDU_in_info(pinfo, top_ti, s_lengths[n], + (n==0) ? first_includes_start : TRUE, + TRUE); + offset += s_lengths[n]; + } + /* Last one */ + show_PDU_in_info(pinfo, top_ti, p_rlc_lte_info->pduLength - offset, + (s_number_of_extensions == 0) ? first_includes_start : TRUE, + last_includes_end); + } + else { + proto_item_set_hidden(truncated_ti); + } + } + + /* Call sequence analysis function now */ + if (((global_rlc_lte_am_sequence_analysis == SEQUENCE_ANALYSIS_MAC_ONLY) && + (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) != NULL)) || + ((global_rlc_lte_am_sequence_analysis == SEQUENCE_ANALYSIS_RLC_ONLY) && + (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) == NULL))) { + + guint16 firstSegmentLength; + guint16 lastSegmentOffset = offset; + if (s_number_of_extensions >= 1) { + int n; + for (n=0; n < s_number_of_extensions; n++) { + lastSegmentOffset += s_lengths[n]; + } + + firstSegmentLength = s_lengths[0]; + } + else { + firstSegmentLength = tvb_reported_length_remaining(tvb, offset); + } + + seq_anal_state = checkChannelSequenceInfo(pinfo, tvb, p_rlc_lte_info, FALSE, + s_number_of_extensions+1, + offset, firstSegmentLength, + lastSegmentOffset, + (guint16)sn, + first_includes_start, last_includes_end, + is_resegmented, tap_info, tree); + } + + if (is_truncated) { + return; + } + + /*************************************/ + /* Data */ + + if (!first_includes_start) { + reassembly_info = (rlc_channel_reassembly_info *)wmem_map_lookup(reassembly_report_hash, + get_report_hash_key((guint16)sn, + pinfo->num, + p_rlc_lte_info, + FALSE)); + } + + if (s_number_of_extensions > 0) { + /* Show each data segment separately */ + int n; + for (n=0; n < s_number_of_extensions; n++) { + show_PDU_in_tree(pinfo, tree, tvb, offset, s_lengths[n], p_rlc_lte_info, + (n==0) ? first_includes_start : TRUE, + (n==0) ? reassembly_info : NULL, + seq_anal_state); + show_PDU_in_info(pinfo, top_ti, s_lengths[n], + (n==0) ? first_includes_start : TRUE, + TRUE); + /* Make sure we don't lose the summary of this SDU */ + col_append_str(pinfo->cinfo, COL_INFO, " | "); + col_set_fence(pinfo->cinfo, COL_INFO); + + tvb_ensure_bytes_exist(tvb, offset, s_lengths[n]); + offset += s_lengths[n]; + } + } + + /* Final data element */ + if (tvb_reported_length_remaining(tvb, offset) > 0) { + show_PDU_in_tree(pinfo, tree, tvb, offset, tvb_reported_length_remaining(tvb, offset), p_rlc_lte_info, + ((s_number_of_extensions == 0) ? first_includes_start : TRUE) && last_includes_end, + (s_number_of_extensions == 0) ? reassembly_info : NULL, + seq_anal_state); + show_PDU_in_info(pinfo, top_ti, (guint16)tvb_reported_length_remaining(tvb, offset), + (s_number_of_extensions == 0) ? first_includes_start : TRUE, + last_includes_end); + } + else { + /* Report that expected data was missing (unless we know it might happen) */ + if (!global_rlc_lte_headers_expected) { + if (s_number_of_extensions > 0) { + expert_add_info(pinfo, am_header_ti, &ei_rlc_lte_am_data_no_data_beyond_extensions); + } + else { + expert_add_info(pinfo, am_header_ti, &ei_rlc_lte_am_data_no_data); + } + } + } +} + +static void report_heur_error(proto_tree *tree, packet_info *pinfo, expert_field *eiindex, + tvbuff_t *tvb, gint start, gint length) +{ + proto_item *ti; + proto_tree *subtree; + + col_set_str(pinfo->cinfo, COL_PROTOCOL, "RLC-LTE"); + col_clear(pinfo->cinfo, COL_INFO); + ti = proto_tree_add_item(tree, proto_rlc_lte, tvb, 0, -1, ENC_NA); + subtree = proto_item_add_subtree(ti, ett_rlc_lte); + proto_tree_add_expert(subtree, pinfo, eiindex, tvb, start, length); +} + +/* Heuristic dissector looks for supported framing protocol (see wiki page) */ +static gboolean dissect_rlc_lte_heur(tvbuff_t *tvb, packet_info *pinfo, + proto_tree *tree, void *data _U_) +{ + gint offset = 0; + struct rlc_lte_info *p_rlc_lte_info; + tvbuff_t *rlc_tvb; + guint8 tag = 0; + gboolean seqNumLengthTagPresent = FALSE; + + /* 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_LTE_START_STRING)+1+2)) { + return FALSE; + } + + /* OK, compare with signature string */ + if (tvb_strneql(tvb, offset, RLC_LTE_START_STRING, (gint)strlen(RLC_LTE_START_STRING)) != 0) { + return FALSE; + } + offset += (gint)strlen(RLC_LTE_START_STRING); + + + /* If redissecting, use previous info struct (if available) */ + p_rlc_lte_info = (rlc_lte_info *)p_get_proto_data(wmem_file_scope(), pinfo, proto_rlc_lte, 0); + if (p_rlc_lte_info == NULL) { + /* Allocate new info struct for this frame */ + p_rlc_lte_info = wmem_new0(wmem_file_scope(), struct rlc_lte_info); + + /* Read fixed fields */ + p_rlc_lte_info->rlcMode = tvb_get_guint8(tvb, offset++); + if (p_rlc_lte_info->rlcMode == RLC_AM_MODE) { + p_rlc_lte_info->sequenceNumberLength = AM_SN_LENGTH_10_BITS; + } + + /* Read optional fields */ + while (tag != RLC_LTE_PAYLOAD_TAG) { + /* Process next tag */ + tag = tvb_get_guint8(tvb, offset++); + switch (tag) { + case RLC_LTE_SN_LENGTH_TAG: + p_rlc_lte_info->sequenceNumberLength = tvb_get_guint8(tvb, offset); + offset++; + seqNumLengthTagPresent = TRUE; + break; + case RLC_LTE_DIRECTION_TAG: + p_rlc_lte_info->direction = tvb_get_guint8(tvb, offset); + offset++; + break; + case RLC_LTE_PRIORITY_TAG: + p_rlc_lte_info->priority = tvb_get_guint8(tvb, offset); + offset++; + break; + case RLC_LTE_UEID_TAG: + p_rlc_lte_info->ueid = tvb_get_ntohs(tvb, offset); + offset += 2; + break; + case RLC_LTE_CHANNEL_TYPE_TAG: + p_rlc_lte_info->channelType = tvb_get_ntohs(tvb, offset); + offset += 2; + break; + case RLC_LTE_CHANNEL_ID_TAG: + p_rlc_lte_info->channelId = tvb_get_ntohs(tvb, offset); + offset += 2; + break; + case RLC_LTE_EXT_LI_FIELD_TAG: + p_rlc_lte_info->extendedLiField = TRUE; + break; + case RLC_LTE_NB_MODE_TAG: + p_rlc_lte_info->nbMode = + (rlc_lte_nb_mode)tvb_get_guint8(tvb, offset); + offset++; + break; + + case RLC_LTE_PAYLOAD_TAG: + /* Have reached data, so set payload length and get out of loop */ + p_rlc_lte_info->pduLength = tvb_reported_length_remaining(tvb, offset); + continue; + + default: + /* It must be a recognised tag */ + report_heur_error(tree, pinfo, &ei_rlc_lte_unknown_udp_framing_tag, tvb, offset-1, 1); + wmem_free(wmem_file_scope(), p_rlc_lte_info); + return TRUE; + } + } + + if ((p_rlc_lte_info->rlcMode == RLC_UM_MODE) && (seqNumLengthTagPresent == FALSE)) { + /* Conditional field is not present */ + report_heur_error(tree, pinfo, &ei_rlc_lte_missing_udp_framing_tag, tvb, 0, offset); + wmem_free(wmem_file_scope(), p_rlc_lte_info); + return TRUE; + } + + /* Store info in packet */ + p_add_proto_data(wmem_file_scope(), pinfo, proto_rlc_lte, 0, p_rlc_lte_info); + } + else { + offset = tvb_reported_length(tvb) - p_rlc_lte_info->pduLength; + } + + /**************************************/ + /* OK, now dissect as RLC LTE */ + + /* Create tvb that starts at actual RLC PDU */ + rlc_tvb = tvb_new_subset_remaining(tvb, offset); + dissect_rlc_lte_common(rlc_tvb, pinfo, tree, TRUE); + return TRUE; +} + + + +/*****************************/ +/* Main dissection function. */ +/*****************************/ + +static int dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) +{ + dissect_rlc_lte_common(tvb, pinfo, tree, FALSE); + return tvb_captured_length(tvb); +} + +static void dissect_rlc_lte_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gboolean is_udp_framing) +{ + proto_tree *rlc_lte_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_lte_info *p_rlc_lte_info; + + /* Allocate and Zero tap struct */ + rlc_lte_tap_info *tap_info = wmem_new0(pinfo->pool, rlc_lte_tap_info); + + /* Set protocol name */ + col_set_str(pinfo->cinfo, COL_PROTOCOL, "RLC-LTE"); + + /* Create protocol tree. */ + top_ti = proto_tree_add_item(tree, proto_rlc_lte, tvb, offset, -1, ENC_NA); + rlc_lte_tree = proto_item_add_subtree(top_ti, ett_rlc_lte); + + + /* Look for packet info! */ + p_rlc_lte_info = (rlc_lte_info *)p_get_proto_data(wmem_file_scope(), pinfo, proto_rlc_lte, 0); + + /* Can't dissect anything without it... */ + if (p_rlc_lte_info == NULL) { + proto_tree_add_expert(rlc_lte_tree, pinfo, &ei_rlc_lte_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_lte_tree, hf_rlc_lte_context, + tvb, offset, 0, "", "Context"); + context_tree = proto_item_add_subtree(context_ti, ett_rlc_lte_context); + proto_item_set_generated(context_ti); + + ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_direction, + tvb, 0, 0, p_rlc_lte_info->direction); + proto_item_set_generated(ti); + + mode_ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_mode, + tvb, 0, 0, p_rlc_lte_info->rlcMode); + proto_item_set_generated(mode_ti); + + if (p_rlc_lte_info->ueid != 0) { + ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_ueid, + tvb, 0, 0, p_rlc_lte_info->ueid); + proto_item_set_generated(ti); + } + + if ((p_rlc_lte_info->priority >= 1) && (p_rlc_lte_info->priority <=16)) { + ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_priority, + tvb, 0, 0, p_rlc_lte_info->priority); + proto_item_set_generated(ti); + } + + ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_channel_type, + tvb, 0, 0, p_rlc_lte_info->channelType); + proto_item_set_generated(ti); + + if ((p_rlc_lte_info->channelType == CHANNEL_TYPE_SRB) || + (p_rlc_lte_info->channelType == CHANNEL_TYPE_DRB) || + (p_rlc_lte_info->channelType == CHANNEL_TYPE_MTCH)) { + ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_channel_id, + tvb, 0, 0, p_rlc_lte_info->channelId); + proto_item_set_generated(ti); + } + + ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_pdu_length, + tvb, 0, 0, p_rlc_lte_info->pduLength); + proto_item_set_generated(ti); + + if (p_rlc_lte_info->rlcMode == RLC_UM_MODE) { + ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_um_sn_length, + tvb, 0, 0, p_rlc_lte_info->sequenceNumberLength); + proto_item_set_generated(ti); + } + + if (p_rlc_lte_info->rlcMode == RLC_AM_MODE) { + ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_am_sn_length, + tvb, 0, 0, p_rlc_lte_info->sequenceNumberLength ? + p_rlc_lte_info->sequenceNumberLength : 10); + proto_item_set_generated(ti); + } + + /* Append highlights to top-level item */ + if (p_rlc_lte_info->ueid != 0) { + proto_item_append_text(top_ti, " UEId=%u", p_rlc_lte_info->ueid); + col_append_fstr(pinfo->cinfo, COL_INFO, "UEId=%-4u ", p_rlc_lte_info->ueid); + } + + /* Append context highlights to info column */ + write_pdu_label_and_info(top_ti, NULL, pinfo, + " [%s] [%s] ", + (p_rlc_lte_info->direction == 0) ? "UL" : "DL", + val_to_str_const(p_rlc_lte_info->rlcMode, rlc_mode_short_vals, "Unknown")); + + if (p_rlc_lte_info->channelId == 0) { + write_pdu_label_and_info(top_ti, NULL, pinfo, "%s ", + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown")); + } + else { + write_pdu_label_and_info(top_ti, NULL, pinfo, "%s:%-2u", + val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), + p_rlc_lte_info->channelId); + } + + /* Set context-info parts of tap struct */ + tap_info->rlcMode = p_rlc_lte_info->rlcMode; + tap_info->direction = p_rlc_lte_info->direction; + tap_info->priority = p_rlc_lte_info->priority; + tap_info->ueid = p_rlc_lte_info->ueid; + tap_info->channelType = p_rlc_lte_info->channelType; + tap_info->channelId = p_rlc_lte_info->channelId; + tap_info->pduLength = p_rlc_lte_info->pduLength; + tap_info->sequenceNumberLength = p_rlc_lte_info->sequenceNumberLength; + tap_info->loggedInMACFrame = (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) != NULL); + + tap_info->rlc_lte_time = pinfo->abs_ts; + + /* Reset this count */ + s_number_of_extensions = 0; + + /* Dissect the RLC PDU itself. Format depends upon mode... */ + switch (p_rlc_lte_info->rlcMode) { + + case RLC_TM_MODE: + dissect_rlc_lte_tm(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info, top_ti); + break; + + case RLC_UM_MODE: + dissect_rlc_lte_um(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info, top_ti, + tap_info); + break; + + case RLC_AM_MODE: + dissect_rlc_lte_am(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info, top_ti, + tap_info); + break; + + case RLC_PREDEF: + /* Predefined data (i.e. not containing a valid RLC header */ + proto_tree_add_item(rlc_lte_tree, hf_rlc_lte_predefined_pdu, tvb, offset, -1, ENC_NA); + write_pdu_label_and_info(top_ti, NULL, pinfo, " [%u-bytes]", + tvb_reported_length_remaining(tvb, offset)); + break; + + default: + /* Error - unrecognised mode */ + expert_add_info_format(pinfo, mode_ti, &ei_rlc_lte_context_mode, + "Unrecognised RLC Mode set (%u)", p_rlc_lte_info->rlcMode); + break; + } + + /* Queue tap info */ + tap_queue_packet(rlc_lte_tap, pinfo, tap_info); +} + + + +/* Configure number of PDCP SN bits to use for DRB channels */ +void set_rlc_lte_drb_pdcp_seqnum_length(packet_info *pinfo, guint16 ueid, guint8 drbid, + guint8 userplane_seqnum_length) +{ + wmem_tree_key_t key[3]; + guint32 id; + rlc_ue_parameters *params; + + if (PINFO_FD_VISITED(pinfo)) { + return; + } + + id = (drbid << 16) | ueid; + key[0].length = 1; + key[0].key = &id; + key[1].length = 1; + key[1].key = &pinfo->num; + key[2].length = 0; + key[2].key = NULL; + + params = (rlc_ue_parameters *)wmem_tree_lookup32_array_le(ue_parameters_tree, key); + if (params && (params->id != id)) { + params = NULL; + } + if (params == NULL) { + params = (rlc_ue_parameters *)wmem_new(wmem_file_scope(), rlc_ue_parameters); + params->id = id; + params->ext_li_field = NO_EXT_LI; + wmem_tree_insert32_array(ue_parameters_tree, key, (void *)params); + } + params->pdcp_sn_bits = userplane_seqnum_length; +} + +/*Configure LI field for AM DRB channels */ +void set_rlc_lte_drb_li_field(packet_info *pinfo, guint16 ueid, guint8 drbid, + gboolean ul_ext_li_field, gboolean dl_ext_li_field) +{ + wmem_tree_key_t key[3]; + guint32 id; + rlc_ue_parameters *params; + + if (PINFO_FD_VISITED(pinfo)) { + return; + } + + id = (drbid << 16) | ueid; + key[0].length = 1; + key[0].key = &id; + key[1].length = 1; + key[1].key = &pinfo->num; + key[2].length = 0; + key[2].key = NULL; + + params = (rlc_ue_parameters *)wmem_tree_lookup32_array_le(ue_parameters_tree, key); + if (params && (params->id != id)) { + params = NULL; + } + if (params == NULL) { + params = (rlc_ue_parameters *)wmem_new(wmem_file_scope(), rlc_ue_parameters); + params->id = id; + params->pdcp_sn_bits = 12; + wmem_tree_insert32_array(ue_parameters_tree, key, (void *)params); + } + params->ext_li_field = ul_ext_li_field ? UL_EXT_LI : NO_EXT_LI; + params->ext_li_field |= dl_ext_li_field ? DL_EXT_LI : NO_EXT_LI; +} + +void proto_register_rlc_lte(void) +{ + static hf_register_info hf[] = + { + /**********************************/ + /* Items for decoding context */ + { &hf_rlc_lte_context, + { "Context", + "rlc-lte.context", FT_STRING, BASE_NONE, NULL, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_context_mode, + { "RLC Mode", + "rlc-lte.mode", FT_UINT8, BASE_DEC, VALS(rlc_mode_vals), 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_context_direction, + { "Direction", + "rlc-lte.direction", FT_UINT8, BASE_DEC, VALS(direction_vals), 0x0, + "Direction of message", HFILL + } + }, + { &hf_rlc_lte_context_priority, + { "Priority", + "rlc-lte.priority", FT_UINT8, BASE_DEC, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_context_ueid, + { "UEId", + "rlc-lte.ueid", FT_UINT16, BASE_DEC, 0, 0x0, + "User Equipment Identifier associated with message", HFILL + } + }, + { &hf_rlc_lte_context_channel_type, + { "Channel Type", + "rlc-lte.channel-type", FT_UINT16, BASE_DEC, VALS(rlc_channel_type_vals), 0x0, + "Channel Type associated with message", HFILL + } + }, + { &hf_rlc_lte_context_channel_id, + { "Channel ID", + "rlc-lte.channel-id", FT_UINT16, BASE_DEC, 0, 0x0, + "Channel ID associated with message", HFILL + } + }, + { &hf_rlc_lte_context_pdu_length, + { "PDU Length", + "rlc-lte.pdu-length", FT_UINT16, BASE_DEC, 0, 0x0, + "Length of PDU (in bytes)", HFILL + } + }, + { &hf_rlc_lte_context_um_sn_length, + { "UM Sequence number length", + "rlc-lte.um-seqnum-length", FT_UINT8, BASE_DEC, 0, 0x0, + "Length of UM sequence number in bits", HFILL + } + }, + { &hf_rlc_lte_context_am_sn_length, + { "AM Sequence number length", + "rlc-lte.am-seqnum-length", FT_UINT8, BASE_DEC, 0, 0x0, + "Length of AM sequence number in bits", HFILL + } + }, + + /* Transparent mode fields */ + { &hf_rlc_lte_tm, + { "TM", + "rlc-lte.tm", FT_STRING, BASE_NONE, NULL, 0x0, + "Transparent Mode", HFILL + } + }, + { &hf_rlc_lte_tm_data, + { "TM Data", + "rlc-lte.tm.data", FT_BYTES, BASE_NONE, 0, 0x0, + "Transparent Mode Data", HFILL + } + }, + + /* Unacknowledged mode fields */ + { &hf_rlc_lte_um, + { "UM", + "rlc-lte.um", FT_STRING, BASE_NONE, NULL, 0x0, + "Unacknowledged Mode", HFILL + } + }, + { &hf_rlc_lte_um_header, + { "UM Header", + "rlc-lte.um.header", FT_STRING, BASE_NONE, NULL, 0x0, + "Unacknowledged Mode Header", HFILL + } + }, + { &hf_rlc_lte_um_fi, + { "Framing Info", + "rlc-lte.um.fi", FT_UINT8, BASE_HEX, VALS(framing_info_vals), 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_um_fixed_e, + { "Extension", + "rlc-lte.um.fixed.e", FT_UINT8, BASE_HEX, VALS(fixed_extension_vals), 0x0, + "Extension in fixed part of UM header", HFILL + } + }, + { &hf_rlc_lte_um_sn, + { "Sequence number", + "rlc-lte.um.sn", FT_UINT8, BASE_DEC, 0, 0x0, + "Unacknowledged Mode Sequence Number", HFILL + } + }, + { &hf_rlc_lte_um_fixed_reserved, + { "Reserved", + "rlc-lte.um.reserved", FT_UINT8, BASE_DEC, 0, 0xe0, + "Unacknowledged Mode Fixed header reserved bits", HFILL + } + }, + { &hf_rlc_lte_um_data, + { "UM Data", + "rlc-lte.um.data", FT_BYTES, BASE_NONE, 0, 0x0, + "Unacknowledged Mode Data", HFILL + } + }, + { &hf_rlc_lte_extension_part, + { "Extension Part", + "rlc-lte.extension-part", FT_STRING, BASE_NONE, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_extension_e, + { "Extension", + "rlc-lte.extension.e", FT_UINT8, BASE_HEX, VALS(extension_extension_vals), 0x0, + "Extension in extended part of the header", HFILL + } + }, + { &hf_rlc_lte_extension_li, + { "Length Indicator", + "rlc-lte.extension.li", FT_UINT16, BASE_DEC, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_extension_padding, + { "Padding", + "rlc-lte.extension.padding", FT_UINT8, BASE_HEX, 0, 0x0f, + "Extension header padding", HFILL + } + }, + + /* Acknowledged mode fields */ + { &hf_rlc_lte_am, + { "AM", + "rlc-lte.am", FT_STRING, BASE_NONE, NULL, 0x0, + "Acknowledged Mode", HFILL + } + }, + { &hf_rlc_lte_am_header, + { "AM Header", + "rlc-lte.am.header", FT_STRING, BASE_NONE, NULL, 0x0, + "Acknowledged Mode Header", HFILL + } + }, + { &hf_rlc_lte_am_data_control, + { "Frame type", + "rlc-lte.am.frame-type", FT_UINT8, BASE_HEX, VALS(data_or_control_vals), 0x80, + "AM Frame Type (Control or Data)", HFILL + } + }, + { &hf_rlc_lte_am_rf, + { "Re-segmentation Flag", + "rlc-lte.am.rf", FT_UINT8, BASE_HEX, VALS(resegmentation_flag_vals), 0x40, + "AM Re-segmentation Flag", HFILL + } + }, + { &hf_rlc_lte_am_p, + { "Polling Bit", + "rlc-lte.am.p", FT_UINT8, BASE_HEX, VALS(polling_bit_vals), 0x20, + NULL, HFILL + } + }, + { &hf_rlc_lte_am_fi, + { "Framing Info", + "rlc-lte.am.fi", FT_UINT8, BASE_HEX, VALS(framing_info_vals), 0x18, + "AM Framing Info", HFILL + } + }, + { &hf_rlc_lte_am_fixed_e, + { "Extension", + "rlc-lte.am.fixed.e", FT_UINT8, BASE_HEX, VALS(fixed_extension_vals), 0x04, + "Fixed Extension Bit", HFILL + } + }, + { &hf_rlc_lte_am_fixed_sn, + { "Sequence Number", + "rlc-lte.am.fixed.sn", FT_UINT16, BASE_DEC, 0, 0x03ff, + "AM Fixed Sequence Number", HFILL + } + }, + { &hf_rlc_lte_am_fixed_reserved, + { "Reserved", + "rlc-lte.am.reserved", FT_UINT8, BASE_DEC, 0, 0x03, + "Acknowledged Mode Fixed header reserved bits", HFILL + } + }, + { &hf_rlc_lte_am_segment_lsf16, + { "Last Segment Flag", + "rlc-lte.am.segment.lsf", FT_UINT8, BASE_HEX, VALS(lsf_vals), 0x02, + NULL, HFILL + } + }, + { &hf_rlc_lte_am_fixed_reserved2, + { "Reserved", + "rlc-lte.am.reserved", FT_UINT8, BASE_DEC, 0, 0x01, + "Acknowledged Mode Fixed header reserved bit", HFILL + } + }, + { &hf_rlc_lte_am_fixed_sn16, + { "Sequence Number", + "rlc-lte.am.fixed.sn", FT_UINT16, BASE_DEC, 0, 0x0, + "AM Fixed Sequence Number", HFILL + } + }, + { &hf_rlc_lte_am_segment_lsf, + { "Last Segment Flag", + "rlc-lte.am.segment.lsf", FT_UINT8, BASE_HEX, VALS(lsf_vals), 0x80, + NULL, HFILL + } + }, + { &hf_rlc_lte_am_segment_so, + { "Segment Offset", + "rlc-lte.am.segment.offset", FT_UINT16, BASE_DEC, 0, 0x7fff, + NULL, HFILL + } + }, + { &hf_rlc_lte_am_segment_so16, + { "Segment Offset", + "rlc-lte.am.segment.offset", FT_UINT16, BASE_DEC, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_am_data, + { "AM Data", + "rlc-lte.am.data", FT_BYTES, BASE_NONE, 0, 0x0, + "Acknowledged Mode Data", HFILL + } + }, + + { &hf_rlc_lte_am_cpt, + { "Control PDU Type", + "rlc-lte.am.cpt", FT_UINT8, BASE_HEX, VALS(control_pdu_type_vals), 0x70, + "AM Control PDU Type", HFILL + } + }, + { &hf_rlc_lte_am_ack_sn, + { "ACK Sequence Number", + "rlc-lte.am.ack-sn", FT_UINT16, BASE_DEC, 0, 0x0, + "Sequence Number we expect to receive next", HFILL + } + }, + { &hf_rlc_lte_am_e1, + { "Extension bit 1", + "rlc-lte.am.e1", FT_UINT8, BASE_HEX, VALS(am_e1_vals), 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_am_e2, + { "Extension bit 2", + "rlc-lte.am.e2", FT_UINT8, BASE_HEX, VALS(am_e2_vals), 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_am_nacks, + { "Number of NACKs", + "rlc-lte.am.nacks", FT_UINT16, BASE_DEC, 0, 0x0, + "Number of NACKs in this status PDU", HFILL + } + }, + { &hf_rlc_lte_am_nack_sn, + { "NACK Sequence Number", + "rlc-lte.am.nack-sn", FT_UINT16, BASE_DEC, 0, 0x0, + "Negative Acknowledgement Sequence Number", HFILL + } + }, + { &hf_rlc_lte_am_so_start, + { "SO Start", + "rlc-lte.am.so-start", FT_UINT16, BASE_DEC, 0, 0x0, + "Segment Offset Start byte index", HFILL + } + }, + { &hf_rlc_lte_am_so_end, + { "SO End", + "rlc-lte.am.so-end", FT_UINT16, BASE_DEC, 0, 0x0, + "Segment Offset End byte index", HFILL + } + }, + + { &hf_rlc_lte_predefined_pdu, + { "Predefined data", + "rlc-lte.predefined-data", FT_BYTES, BASE_NONE, 0, 0x0, + "Predefined test data", HFILL + } + }, + + /* Sequence analysis fields */ + { &hf_rlc_lte_sequence_analysis, + { "Sequence Analysis", + "rlc-lte.sequence-analysis", FT_STRING, BASE_NONE, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_sequence_analysis_ok, + { "OK", + "rlc-lte.sequence-analysis.ok", FT_BOOLEAN, BASE_NONE, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_sequence_analysis_previous_frame, + { "Previous frame for channel", + "rlc-lte.sequence-analysis.previous-frame", FT_FRAMENUM, BASE_NONE, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_sequence_analysis_next_frame, + { "Next frame for channel", + "rlc-lte.sequence-analysis.next-frame", FT_FRAMENUM, BASE_NONE, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_sequence_analysis_expected_sn, + { "Expected SN", + "rlc-lte.sequence-analysis.expected-sn", FT_UINT16, BASE_DEC, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_sequence_analysis_framing_info_correct, + { "Frame info continued correctly", + "rlc-lte.sequence-analysis.framing-info-correct", FT_BOOLEAN, BASE_NONE, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_sequence_analysis_mac_retx, + { "Frame retransmitted by MAC", + "rlc-lte.sequence-analysis.mac-retx", FT_BOOLEAN, BASE_NONE, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_sequence_analysis_retx, + { "Retransmitted frame", + "rlc-lte.sequence-analysis.retx", FT_BOOLEAN, BASE_NONE, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_sequence_analysis_skipped, + { "Skipped frames", + "rlc-lte.sequence-analysis.skipped-frames", FT_BOOLEAN, BASE_NONE, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_sequence_analysis_repeated, + { "Repeated frame", + "rlc-lte.sequence-analysis.repeated-frame", FT_BOOLEAN, BASE_NONE, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_sequence_analysis_repeated_nack, + { "Repeated NACK", + "rlc-lte.sequence-analysis.repeated-nack", FT_UINT16, BASE_DEC, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_sequence_analysis_repeated_nack_original_frame, + { "Frame with previous status PDU", + "rlc-lte.sequence-analysis.repeated-nack.original-frame", FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_DUP_ACK), 0x0, + NULL, HFILL + } + }, + + { &hf_rlc_lte_sequence_analysis_ack_out_of_range, + { "Out of range ACK", + "rlc-lte.sequence-analysis.ack-out-of-range", FT_BOOLEAN, BASE_NONE, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame, + { "Frame with most recent SN", + "rlc-lte.sequence-analysis.ack-out-of-range.last-sn-frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0, + NULL, HFILL + } + }, + + /* Reassembly fields */ + { &hf_rlc_lte_reassembly_source, + { "Reassembly Source", + "rlc-lte.reassembly-info", FT_STRING, BASE_NONE, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_reassembly_source_number_of_segments, + { "Number of segments", + "rlc-lte.reassembly-info.number-of-segments", FT_UINT16, BASE_DEC, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_reassembly_source_total_length, + { "Total length", + "rlc-lte.reassembly-info.total-length", FT_UINT16, BASE_DEC, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_reassembly_source_segment, + { "Segment", + "rlc-lte.reassembly-info.segment", FT_NONE, BASE_NONE, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_reassembly_source_segment_sn, + { "SN", + "rlc-lte.reassembly-info.segment.sn", FT_UINT16, BASE_DEC, 0, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_reassembly_source_segment_framenum, + { "Frame", + "rlc-lte.reassembly-info.segment.frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0, + NULL, HFILL + } + }, + { &hf_rlc_lte_reassembly_source_segment_length, + { "Length", + "rlc-lte.reassembly-info.segment.length", FT_UINT32, BASE_DEC, 0, 0x0, + NULL, HFILL + } + }, + + { &hf_rlc_lte_header_only, + { "RLC PDU Header only", + "rlc-lte.header-only", FT_UINT8, BASE_DEC, VALS(header_only_vals), 0x0, + NULL, HFILL + } + }, + }; + + static gint *ett[] = + { + &ett_rlc_lte, + &ett_rlc_lte_context, + &ett_rlc_lte_um_header, + &ett_rlc_lte_am_header, + &ett_rlc_lte_extension_part, + &ett_rlc_lte_sequence_analysis, + &ett_rlc_lte_reassembly_source, + &ett_rlc_lte_reassembly_source_segment + }; + + static ei_register_info ei[] = { + { &ei_rlc_lte_sequence_analysis_last_segment_not_continued, { "rlc-lte.sequence-analysis.last-segment-not-continued", PI_SEQUENCE, PI_WARN, "Last segment of previous PDU was not continued for UE", EXPFILL }}, + { &ei_rlc_lte_sequence_analysis_last_segment_complete, { "rlc-lte.sequence-analysis.last-segment-complete", PI_SEQUENCE, PI_WARN, "Last segment of previous PDU was complete, but new segment was not started on UE", EXPFILL }}, + { &ei_rlc_lte_sequence_analysis_mac_retx, { "rlc-lte.sequence-analysis.mac-retx.expert", PI_SEQUENCE, PI_WARN, "AM Frame retransmitted due to MAC retx!", EXPFILL }}, + { &ei_rlc_lte_sequence_analysis_retx, { "rlc-lte.sequence-analysis.retx.expert", PI_SEQUENCE, PI_WARN, "AM Frame retransmitted most likely in response to NACK", EXPFILL }}, + { &ei_rlc_lte_sequence_analysis_repeated, { "rlc-lte.sequence-analysis.repeated-frame.expert", PI_SEQUENCE, PI_WARN, "AM SN Repeated - probably because didn't receive Status PDU?", EXPFILL }}, + { &ei_rlc_lte_am_sn_missing, { "rlc-lte.sequence-analysis.am-sn.missing", PI_SEQUENCE, PI_WARN, "AM SNs missing", EXPFILL }}, + { &ei_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame, { "rlc-lte.sequence-analysis.ack-out-of-range.last-sn-frame.expert", PI_SEQUENCE, PI_ERROR, "AM ACK for SN - but last received SN in other direction is X", EXPFILL }}, + { &ei_rlc_lte_um_sn_missing, { "rlc-lte.sequence-analysis.um-sn.missing", PI_SEQUENCE, PI_WARN, "UM SNs missing", EXPFILL }}, + { &ei_rlc_lte_um_sn_repeated, { "rlc-lte.sequence-analysis.um-sn.repeated", PI_SEQUENCE, PI_WARN, "UM SN repeated", EXPFILL }}, + { &ei_rlc_lte_wrong_sequence_number, { "rlc-lte.wrong-sequence-number", PI_SEQUENCE, PI_WARN, "Wrong Sequence Number", EXPFILL }}, + { &ei_rlc_lte_sequence_analysis_repeated_nack, { "rlc-lte.sequence-analysis.repeated-nack.expert", PI_SEQUENCE, PI_ERROR, "Same SN NACKd on successive Status PDUs", EXPFILL }}, + { &ei_rlc_lte_reserved_bits_not_zero, { "rlc-lte.reserved-bits-not-zero", PI_MALFORMED, PI_ERROR, "Reserved bits not zero", EXPFILL }}, + { &ei_rlc_lte_um_sn, { "rlc-lte.um.sn.invalid", PI_MALFORMED, PI_ERROR, "Invalid sequence number length", EXPFILL }}, + { &ei_rlc_lte_header_only, { "rlc-lte.header-only.expert", PI_SEQUENCE, PI_NOTE, "RLC PDU SDUs have been omitted", EXPFILL }}, + { &ei_rlc_lte_am_cpt, { "rlc-lte.am.cpt.invalid", PI_MALFORMED, PI_ERROR, "RLC Control frame type not handled", EXPFILL }}, + { &ei_rlc_lte_am_nack_sn_ack_same, { "rlc-lte.am.nack-sn.ack-same", PI_MALFORMED, PI_ERROR, "Status PDU shouldn't ACK and NACK the same sequence number", EXPFILL }}, + { &ei_rlc_lte_am_nack_sn_ahead_ack, { "rlc-lte.am.nack-sn.ahead-ack", PI_MALFORMED, PI_ERROR, "NACK must not be ahead of ACK in status PDU", EXPFILL }}, + { &ei_rlc_lte_am_nack_sn_partial, { "rlc-lte.am.nack-sn.partial", PI_SEQUENCE, PI_WARN, "Status PDU reports NACK (partial)", EXPFILL }}, + { &ei_rlc_lte_am_nack_sn, { "rlc-lte.am.nack-sn.expert", PI_SEQUENCE, PI_WARN, "Status PDU reports NACK", EXPFILL }}, + { &ei_rlc_lte_bytes_after_status_pdu_complete, { "rlc-lte.bytes-after-status-pdu-complete", PI_MALFORMED, PI_ERROR, "bytes remaining after Status PDU complete", EXPFILL }}, + { &ei_rlc_lte_am_data_no_data_beyond_extensions, { "rlc-lte.am-data.no-data-beyond-extensions", PI_MALFORMED, PI_ERROR, "AM data PDU doesn't contain any data beyond extensions", EXPFILL }}, + { &ei_rlc_lte_am_data_no_data, { "rlc-lte.am-data.no-data", PI_MALFORMED, PI_ERROR, "AM data PDU doesn't contain any data", EXPFILL }}, + { &ei_rlc_lte_context_mode, { "rlc-lte.mode.invalid", PI_MALFORMED, PI_ERROR, "Unrecognised RLC Mode set", EXPFILL }}, + { &ei_rlc_lte_no_per_frame_info, { "rlc-lte.no_per_frame_info", PI_UNDECODED, PI_ERROR, "Can't dissect LTE RLC frame because no per-frame info was attached!", EXPFILL }}, + { &ei_rlc_lte_unknown_udp_framing_tag, { "rlc-lte.unknown-udp-framing-tag", PI_UNDECODED, PI_WARN, "Unknown UDP framing tag, aborting dissection", EXPFILL }}, + { &ei_rlc_lte_missing_udp_framing_tag, { "rlc-lte.missing-udp-framing-tag", PI_UNDECODED, PI_WARN, "Missing UDP framing conditional tag, aborting dissection", EXPFILL }} + }; + + static const enum_val_t sequence_analysis_vals[] = { + {"no-analysis", "No-Analysis", FALSE}, + {"mac-only", "Only-MAC-frames", SEQUENCE_ANALYSIS_MAC_ONLY}, + {"rlc-only", "Only-RLC-frames", SEQUENCE_ANALYSIS_RLC_ONLY}, + {NULL, NULL, -1} + }; + + module_t *rlc_lte_module; + expert_module_t* expert_rlc_lte; + + /* Register protocol. */ + proto_rlc_lte = proto_register_protocol("RLC-LTE", "RLC-LTE", "rlc-lte"); + proto_register_field_array(proto_rlc_lte, hf, array_length(hf)); + proto_register_subtree_array(ett, array_length(ett)); + expert_rlc_lte = expert_register_protocol(proto_rlc_lte); + expert_register_field_array(expert_rlc_lte, ei, array_length(ei)); + + /* Allow other dissectors to find this one by name. */ + register_dissector("rlc-lte", dissect_rlc_lte, proto_rlc_lte); + + /* Register the tap name */ + rlc_lte_tap = register_tap("rlc-lte"); + + /* Preferences */ + rlc_lte_module = prefs_register_protocol(proto_rlc_lte, NULL); + + prefs_register_enum_preference(rlc_lte_module, "do_sequence_analysis_am", + "Do sequence analysis for AM channels", + "Attempt to keep track of PDUs for AM channels, and point out problems", + &global_rlc_lte_am_sequence_analysis, sequence_analysis_vals, FALSE); + + prefs_register_enum_preference(rlc_lte_module, "do_sequence_analysis", + "Do sequence analysis for UM channels", + "Attempt to keep track of PDUs for UM channels, and point out problems", + &global_rlc_lte_um_sequence_analysis, sequence_analysis_vals, FALSE); + + prefs_register_bool_preference(rlc_lte_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_lte_call_pdcp_for_srb); + + prefs_register_enum_preference(rlc_lte_module, "call_pdcp_for_drb", + "Call PDCP dissector for DRB PDUs", + "Call PDCP dissector for user-plane PDUs. Note that without reassembly, it can" + "only be called for complete PDUs (i.e. not segmented over RLC)", + &global_rlc_lte_call_pdcp_for_drb, pdcp_drb_col_vals, FALSE); + + + prefs_register_bool_preference(rlc_lte_module, "call_rrc_for_ccch", + "Call RRC dissector for CCCH PDUs", + "Call RRC dissector for CCCH PDUs", + &global_rlc_lte_call_rrc_for_ccch); + + prefs_register_bool_preference(rlc_lte_module, "call_rrc_for_mcch", + "Call RRC dissector for MCCH PDUs", + "Call RRC dissector for MCCH PDUs Note that without reassembly, it can" + "only be called for complete PDUs (i.e. not segmented over RLC)", + &global_rlc_lte_call_rrc_for_mcch); + + prefs_register_bool_preference(rlc_lte_module, "call_ip_for_mtch", + "Call IP dissector for MTCH PDUs", + "Call ip dissector for MTCH PDUs Note that without reassembly, it can" + "only be called for complete PDUs (i.e. not segmented over RLC)", + &global_rlc_lte_call_ip_for_mtch); + + prefs_register_obsolete_preference(rlc_lte_module, "heuristic_rlc_lte_over_udp"); + + prefs_register_bool_preference(rlc_lte_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_lte_headers_expected); + + prefs_register_bool_preference(rlc_lte_module, "reassembly", + "Attempt SDU reassembly", + "When enabled, attempts to re-assemble upper-layer SDUs that are split over " + "more than one RLC PDU. Note: does not currently support out-of-order or " + "re-segmentation. N.B. sequence analysis must also be turned on in order " + "for reassembly to work", + &global_rlc_lte_reassembly); + + ue_parameters_tree = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope()); + + sequence_analysis_channel_hash = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), rlc_channel_hash_func, rlc_channel_equal); + sequence_analysis_report_hash = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), rlc_result_hash_func, rlc_result_hash_equal); + repeated_nack_channel_hash = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), rlc_channel_hash_func, rlc_channel_equal); + repeated_nack_report_hash = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), rlc_result_hash_func, rlc_result_hash_equal); + reassembly_report_hash = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), rlc_result_hash_func, rlc_result_hash_equal); +} + +void proto_reg_handoff_rlc_lte(void) +{ + /* Add as a heuristic UDP dissector */ + heur_dissector_add("udp", dissect_rlc_lte_heur, "RLC-LTE over UDP", "rlc_lte_udp", proto_rlc_lte, HEURISTIC_DISABLE); + + pdcp_lte_handle = find_dissector_add_dependency("pdcp-lte", proto_rlc_lte); + ip_handle = find_dissector_add_dependency("ip", proto_rlc_lte); + lte_rrc_mcch = find_dissector_add_dependency("lte_rrc.mcch", proto_rlc_lte); + lte_rrc_ul_ccch = find_dissector_add_dependency("lte_rrc.ul_ccch", proto_rlc_lte); + lte_rrc_dl_ccch = find_dissector_add_dependency("lte_rrc.dl_dcch", proto_rlc_lte); + lte_rrc_bcch_bch = find_dissector_add_dependency("lte_rrc.bcch_bch", proto_rlc_lte); + lte_rrc_bcch_dl_sch = find_dissector_add_dependency("lte_rrc.bcch_dl_sch", proto_rlc_lte); + lte_rrc_pcch = find_dissector_add_dependency("lte_rrc.pcch", proto_rlc_lte); + lte_rrc_ul_ccch_nb = find_dissector_add_dependency("lte_rrc.ul_ccch.nb", proto_rlc_lte); + lte_rrc_dl_ccch_nb = find_dissector_add_dependency("lte_rrc.dl_ccch.nb", proto_rlc_lte); + lte_rrc_bcch_bch_nb = find_dissector_add_dependency("lte_rrc.bcch_bch.nb", proto_rlc_lte); + lte_rrc_bcch_dl_sch_nb = find_dissector_add_dependency("lte_rrc.bcch_dl_sch.nb", proto_rlc_lte); + lte_rrc_pcch_nb = find_dissector_add_dependency("lte_rrc.pcch.nb", proto_rlc_lte); +} + +/* + * 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: + */ |