wireshark/epan/dissectors/packet-rlc-lte.c

3751 lines
157 KiB
C

/* 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|ENC_NA);
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, ...)
{
#define MAX_INFO_BUFFER 256
static char info_buffer[MAX_INFO_BUFFER];
va_list ap;
va_start(ap, format);
g_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 g_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) {
guint8 reserved;
proto_item *ti;
/* Check 3 Reserved bits */
reserved = (tvb_get_guint8(tvb, offset) & 0xe0) >> 5;
ti = proto_tree_add_item(um_header_tree, hf_rlc_lte_um_fixed_reserved, tvb, offset, 1, ENC_BIG_ENDIAN);
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)
{
guint8 cpt, 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) */
cpt = (tvb_get_guint8(tvb, offset) & 0xf0) >> 4;
ti = proto_tree_add_item(tree, hf_rlc_lte_am_cpt, tvb, offset, 1, ENC_BIG_ENDIAN);
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 (%" G_GINT64_MODIFIER "u)",
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)
{
guint8 is_data;
guint8 is_resegmented;
guint8 polling;
guint8 fixed_extension;
guint8 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;
guint16 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 */
is_data = (tvb_get_guint8(tvb, offset) & 0x80) >> 7;
proto_tree_add_item(am_header_tree, hf_rlc_lte_am_data_control, tvb, offset, 1, ENC_BIG_ENDIAN);
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 */
is_resegmented = (tvb_get_guint8(tvb, offset) & 0x40) >> 6;
proto_tree_add_item(am_header_tree, hf_rlc_lte_am_rf, tvb, offset, 1, ENC_BIG_ENDIAN);
tap_info->isResegmented = is_resegmented;
write_pdu_label_and_info_literal(top_ti, NULL, pinfo,
(is_resegmented) ? " [DATA-SEGMENT]" : " [DATA]");
/* Polling bit */
polling = (tvb_get_guint8(tvb, offset) & 0x20) >> 5;
proto_tree_add_item(am_header_tree, hf_rlc_lte_am_p, tvb, offset, 1, ENC_BIG_ENDIAN);
write_pdu_label_and_info_literal(top_ti, NULL, pinfo, (polling) ? " (P) " : " ");
if (polling) {
proto_item_append_text(am_header_ti, " (P) ");
}
/* Framing Info */
framing_info = (tvb_get_guint8(tvb, offset) & 0x18) >> 3;
proto_tree_add_item(am_header_tree, hf_rlc_lte_am_fi, tvb, offset, 1, ENC_BIG_ENDIAN);
/* Extension bit */
fixed_extension = (tvb_get_guint8(tvb, offset) & 0x04) >> 2;
proto_tree_add_item(am_header_tree, hf_rlc_lte_am_fixed_e, tvb, offset, 1, ENC_BIG_ENDIAN);
/* Sequence Number */
if (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) {
guint8 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(am_header_tree, hf_rlc_lte_am_fixed_reserved2, tvb, offset, 1, ENC_BIG_ENDIAN);
reserved = tvb_get_guint8(tvb, offset) & 0x01;
} else {
/* Reserved (R1) */
am_ti = proto_tree_add_item(am_header_tree, hf_rlc_lte_am_fixed_reserved, tvb, offset, 1, ENC_BIG_ENDIAN);
reserved = tvb_get_guint8(tvb, offset) & 0x03;
}
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 0x%x)", reserved);
}
offset += 1;
proto_tree_add_item(am_header_tree, hf_rlc_lte_am_fixed_sn16, tvb, offset, 2, ENC_BIG_ENDIAN);
sn = tvb_get_ntohs(tvb, offset);
offset += 2;
} else {
proto_tree_add_item(am_header_tree, hf_rlc_lte_am_fixed_sn, tvb, offset, 2, ENC_BIG_ENDIAN);
sn = tvb_get_ntohs(tvb, offset) & 0x03ff;
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) {
guint16 segmentOffset;
if (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) {
/* SO */
proto_tree_add_item(am_header_tree, hf_rlc_lte_am_segment_so16, tvb, offset, 2, ENC_BIG_ENDIAN);
segmentOffset = tvb_get_ntohs(tvb, offset);
} 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(am_header_tree, hf_rlc_lte_am_segment_so, tvb, offset, 2, ENC_BIG_ENDIAN);
segmentOffset = tvb_get_ntohs(tvb, offset) & 0x7fff;
}
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(wmem_packet_scope(), 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, 0xffff,
"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, 0xffff,
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:
*/