summaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-rlc-lte.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
commite4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch)
tree68cb5ef9081156392f1dd62a00c6ccc1451b93df /epan/dissectors/packet-rlc-lte.c
parentInitial commit. (diff)
downloadwireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz
wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'epan/dissectors/packet-rlc-lte.c')
-rw-r--r--epan/dissectors/packet-rlc-lte.c3744
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:
+ */