8efad466c4
Do not require a useless ENC_NA parameter for string encodings.
FT_STRING and FT_STRINGZ types don't have any ndianness.
Follow-up to 6ec429622c
.
3751 lines
157 KiB
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);
|
|
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);
|
|
vsnprintf(info_buffer, MAX_INFO_BUFFER, format, ap);
|
|
va_end(ap);
|
|
|
|
/* Add to indicated places */
|
|
col_append_str(pinfo->cinfo, COL_INFO, info_buffer);
|
|
proto_item_append_text(pdu_ti, "%s", info_buffer);
|
|
if (sub_ti != NULL) {
|
|
proto_item_append_text(sub_ti, "%s", info_buffer);
|
|
}
|
|
}
|
|
|
|
/* Version of function above, where no vsnprintf() call needed
|
|
- the info column
|
|
- the top-level RLC PDU item
|
|
- another subtree item (if supplied) */
|
|
static void write_pdu_label_and_info_literal(proto_item *pdu_ti, proto_item *sub_ti,
|
|
packet_info *pinfo, const char *info_buffer)
|
|
{
|
|
/* Add to indicated places */
|
|
col_append_str(pinfo->cinfo, COL_INFO, info_buffer);
|
|
proto_item_append_text(pdu_ti, "%s", info_buffer);
|
|
if (sub_ti != NULL) {
|
|
proto_item_append_text(sub_ti, "%s", info_buffer);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Dissect extension headers (common to both UM and AM) */
|
|
static int dissect_rlc_lte_extension_header(tvbuff_t *tvb, packet_info *pinfo _U_,
|
|
proto_tree *tree,
|
|
int offset,
|
|
rlc_lte_info *p_rlc_lte_info)
|
|
{
|
|
guint8 isOdd;
|
|
guint64 extension = 1;
|
|
guint64 length;
|
|
|
|
/* Reset this count */
|
|
s_number_of_extensions = 0;
|
|
|
|
while (extension && (s_number_of_extensions < MAX_RLC_SDUS)) {
|
|
proto_tree *extension_part_tree;
|
|
proto_item *extension_part_ti;
|
|
|
|
/* Extension part subtree */
|
|
extension_part_ti = proto_tree_add_string_format(tree,
|
|
hf_rlc_lte_extension_part,
|
|
tvb, offset, 2,
|
|
"",
|
|
"Extension Part");
|
|
extension_part_tree = proto_item_add_subtree(extension_part_ti,
|
|
ett_rlc_lte_extension_part);
|
|
|
|
if (p_rlc_lte_info->extendedLiField == FALSE) {
|
|
isOdd = (s_number_of_extensions % 2);
|
|
|
|
/* Read next extension */
|
|
proto_tree_add_bits_ret_val(extension_part_tree, hf_rlc_lte_extension_e, tvb,
|
|
(offset*8) + ((isOdd) ? 4 : 0),
|
|
1,
|
|
&extension, ENC_BIG_ENDIAN);
|
|
|
|
/* Read length field */
|
|
proto_tree_add_bits_ret_val(extension_part_tree, hf_rlc_lte_extension_li, tvb,
|
|
(offset*8) + ((isOdd) ? 5 : 1),
|
|
11,
|
|
&length, ENC_BIG_ENDIAN);
|
|
|
|
/* Move on to byte of next extension */
|
|
if (isOdd) {
|
|
offset += 2;
|
|
} else {
|
|
offset++;
|
|
}
|
|
} else {
|
|
/* Read next extension */
|
|
proto_tree_add_bits_ret_val(extension_part_tree, hf_rlc_lte_extension_e, tvb,
|
|
(offset*8),
|
|
1,
|
|
&extension, ENC_BIG_ENDIAN);
|
|
|
|
/* Read length field */
|
|
proto_tree_add_bits_ret_val(extension_part_tree, hf_rlc_lte_extension_li, tvb,
|
|
(offset*8) + 1,
|
|
15,
|
|
&length, ENC_BIG_ENDIAN);
|
|
|
|
/* Move on to byte of next extension */
|
|
offset += 2;
|
|
}
|
|
|
|
proto_item_append_text(extension_part_tree, " (length=%u)", (guint16)length);
|
|
|
|
s_lengths[s_number_of_extensions++] = (guint16)length;
|
|
}
|
|
|
|
/* May need to skip padding after last extension part */
|
|
isOdd = (s_number_of_extensions % 2);
|
|
if (isOdd && (p_rlc_lte_info->extendedLiField == FALSE)) {
|
|
proto_tree_add_item(tree, hf_rlc_lte_extension_padding,
|
|
tvb, offset++, 1, ENC_BIG_ENDIAN);
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
|
|
/* Show in the info column how many bytes are in the UM/AM PDU, and indicate
|
|
whether or not the beginning and end are included in this packet */
|
|
static void show_PDU_in_info(packet_info *pinfo,
|
|
proto_item *top_ti,
|
|
gint32 length,
|
|
gboolean first_includes_start,
|
|
gboolean last_includes_end)
|
|
{
|
|
/* Reflect this PDU in the info column */
|
|
if (length > 0) {
|
|
write_pdu_label_and_info(top_ti, NULL, pinfo,
|
|
" %s%u-byte%s%s",
|
|
(first_includes_start) ? "[" : "..",
|
|
length,
|
|
(length > 1) ? "s" : "",
|
|
(last_includes_end) ? "]" : "..");
|
|
}
|
|
else {
|
|
write_pdu_label_and_info(top_ti, NULL, pinfo,
|
|
" %sunknown-bytes%s",
|
|
(first_includes_start) ? "[" : "..",
|
|
(last_includes_end) ? "]" : "..");
|
|
}
|
|
}
|
|
|
|
|
|
/* Show an SDU. If configured, pass to PDCP/RRC/IP dissector */
|
|
static void show_PDU_in_tree(packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, gint offset, gint length,
|
|
rlc_lte_info *rlc_info, gboolean whole_pdu, rlc_channel_reassembly_info *reassembly_info,
|
|
sequence_analysis_state state)
|
|
{
|
|
wmem_tree_key_t key[3];
|
|
guint32 id;
|
|
rlc_ue_parameters *params;
|
|
|
|
/* Add raw data (according to mode) */
|
|
proto_item *data_ti = proto_tree_add_item(tree,
|
|
(rlc_info->rlcMode == RLC_AM_MODE) ?
|
|
hf_rlc_lte_am_data :
|
|
hf_rlc_lte_um_data,
|
|
tvb, offset, length, ENC_NA);
|
|
|
|
if (whole_pdu || (reassembly_info != NULL)) {
|
|
if (((global_rlc_lte_call_pdcp_for_srb) && (rlc_info->channelType == CHANNEL_TYPE_SRB)) ||
|
|
((global_rlc_lte_call_pdcp_for_drb != PDCP_drb_off) && (rlc_info->channelType == CHANNEL_TYPE_DRB))) {
|
|
/* Send whole PDU to PDCP */
|
|
|
|
/* TODO: made static to avoid compiler warning... */
|
|
static tvbuff_t *pdcp_tvb = NULL;
|
|
struct pdcp_lte_info *p_pdcp_lte_info;
|
|
|
|
/* Get tvb for passing to LTE PDCP dissector */
|
|
if (reassembly_info == NULL) {
|
|
pdcp_tvb = tvb_new_subset_length(tvb, offset, length);
|
|
}
|
|
else {
|
|
/* Get combined tvb. */
|
|
pdcp_tvb = reassembly_get_reassembled_tvb(reassembly_info, tvb, pinfo);
|
|
reassembly_show_source(reassembly_info, tree, tvb, offset);
|
|
}
|
|
|
|
/* Reuse or allocate struct */
|
|
p_pdcp_lte_info = (pdcp_lte_info *)p_get_proto_data(wmem_file_scope(), pinfo, proto_pdcp_lte, 0);
|
|
if (p_pdcp_lte_info == NULL) {
|
|
p_pdcp_lte_info = wmem_new0(wmem_file_scope(), pdcp_lte_info);
|
|
/* Store info in packet */
|
|
p_add_proto_data(wmem_file_scope(), pinfo, proto_pdcp_lte, 0, p_pdcp_lte_info);
|
|
}
|
|
|
|
p_pdcp_lte_info->ueid = rlc_info->ueid;
|
|
if (rlc_info->nbMode == rlc_nb_mode) {
|
|
p_pdcp_lte_info->channelType = Channel_DCCH_NB;
|
|
} else {
|
|
p_pdcp_lte_info->channelType = Channel_DCCH;
|
|
}
|
|
p_pdcp_lte_info->channelId = rlc_info->channelId;
|
|
p_pdcp_lte_info->direction = rlc_info->direction;
|
|
p_pdcp_lte_info->is_retx = (state != SN_OK);
|
|
|
|
/* Set plane and sequence number length */
|
|
p_pdcp_lte_info->no_header_pdu = FALSE;
|
|
if (rlc_info->channelType == CHANNEL_TYPE_SRB) {
|
|
p_pdcp_lte_info->plane = SIGNALING_PLANE;
|
|
if ((rlc_info->nbMode == rlc_nb_mode) && (rlc_info->channelId == 3)) {
|
|
p_pdcp_lte_info->no_header_pdu = TRUE;
|
|
p_pdcp_lte_info->seqnum_length = 0;
|
|
} else {
|
|
p_pdcp_lte_info->seqnum_length = 5;
|
|
}
|
|
}
|
|
else {
|
|
p_pdcp_lte_info->plane = USER_PLANE;
|
|
switch (global_rlc_lte_call_pdcp_for_drb) {
|
|
case PDCP_drb_SN_7:
|
|
p_pdcp_lte_info->seqnum_length = 7;
|
|
break;
|
|
case PDCP_drb_SN_12:
|
|
p_pdcp_lte_info->seqnum_length = 12;
|
|
break;
|
|
case PDCP_drb_SN_15:
|
|
p_pdcp_lte_info->seqnum_length = 15;
|
|
break;
|
|
case PDCP_drb_SN_18:
|
|
p_pdcp_lte_info->seqnum_length = 18;
|
|
break;
|
|
case PDCP_drb_SN_signalled:
|
|
/* Use whatever was signalled (e.g. in RRC) */
|
|
id = (rlc_info->channelId << 16) | rlc_info->ueid;
|
|
key[0].length = 1;
|
|
key[0].key = &id;
|
|
key[1].length = 1;
|
|
key[1].key = &pinfo->num;
|
|
key[2].length = 0;
|
|
key[2].key = NULL;
|
|
|
|
params = (rlc_ue_parameters *)wmem_tree_lookup32_array_le(ue_parameters_tree, key);
|
|
if (params && (params->id != id)) {
|
|
params = NULL;
|
|
}
|
|
if (params) {
|
|
p_pdcp_lte_info->seqnum_length = params->pdcp_sn_bits;
|
|
} else if (rlc_info->nbMode == rlc_nb_mode) {
|
|
p_pdcp_lte_info->seqnum_length = 7;
|
|
} else {
|
|
p_pdcp_lte_info->seqnum_length = 12;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
DISSECTOR_ASSERT(FALSE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
TRY {
|
|
call_dissector_only(pdcp_lte_handle, pdcp_tvb, pinfo, tree, NULL);
|
|
}
|
|
CATCH_ALL {
|
|
}
|
|
ENDTRY
|
|
|
|
proto_item_set_hidden(data_ti);
|
|
}
|
|
else if (global_rlc_lte_call_rrc_for_mcch && (rlc_info->channelType == CHANNEL_TYPE_MCCH)) {
|
|
/* Send whole PDU to RRC */
|
|
static tvbuff_t *rrc_tvb = NULL;
|
|
|
|
/* Get tvb for passing to LTE RRC dissector */
|
|
if (reassembly_info == NULL) {
|
|
rrc_tvb = tvb_new_subset_length(tvb, offset, length);
|
|
}
|
|
else {
|
|
/* Get combined tvb. */
|
|
rrc_tvb = reassembly_get_reassembled_tvb(reassembly_info, tvb, pinfo);
|
|
reassembly_show_source(reassembly_info, tree, tvb, offset);
|
|
}
|
|
|
|
TRY {
|
|
call_dissector_only(lte_rrc_mcch, rrc_tvb, pinfo, tree, NULL);
|
|
}
|
|
CATCH_ALL {
|
|
}
|
|
ENDTRY
|
|
|
|
proto_item_set_hidden(data_ti);
|
|
}
|
|
else if (global_rlc_lte_call_ip_for_mtch && (rlc_info->channelType == CHANNEL_TYPE_MTCH)) {
|
|
/* Send whole PDU to IP */
|
|
static tvbuff_t *ip_tvb = NULL;
|
|
|
|
/* Get tvb for passing to IP dissector */
|
|
if (reassembly_info == NULL) {
|
|
ip_tvb = tvb_new_subset_length(tvb, offset, length);
|
|
}
|
|
else {
|
|
/* Get combined tvb. */
|
|
ip_tvb = reassembly_get_reassembled_tvb(reassembly_info, tvb, pinfo);
|
|
reassembly_show_source(reassembly_info, tree, tvb, offset);
|
|
}
|
|
|
|
TRY {
|
|
call_dissector_only(ip_handle, ip_tvb, pinfo, tree, NULL);
|
|
}
|
|
CATCH_ALL {
|
|
}
|
|
ENDTRY
|
|
|
|
proto_item_set_hidden(data_ti);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Hash table functions for RLC channels */
|
|
|
|
/* Equal keys */
|
|
static gint rlc_channel_equal(gconstpointer v, gconstpointer v2)
|
|
{
|
|
const channel_hash_key* val1 = (const channel_hash_key *)v;
|
|
const channel_hash_key* val2 = (const channel_hash_key *)v2;
|
|
|
|
/* All fields must match */
|
|
/* N.B. Currently fits into one word, so could return (*v == *v2)
|
|
if we're sure they're initialised to 0... */
|
|
return ((val1->ueId == val2->ueId) &&
|
|
(val1->channelType == val2->channelType) &&
|
|
(val1->channelId == val2->channelId) &&
|
|
(val1->direction == val2->direction));
|
|
}
|
|
|
|
/* Compute a hash value for a given key. */
|
|
static guint rlc_channel_hash_func(gconstpointer v)
|
|
{
|
|
const channel_hash_key* val1 = (const channel_hash_key *)v;
|
|
|
|
/* TODO: check/reduce multipliers */
|
|
return ((val1->ueId * 1024) + (val1->channelType*64) + (val1->channelId*2) + val1->direction);
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/* Result hash */
|
|
|
|
typedef struct {
|
|
guint32 frameNumber;
|
|
guint32 SN : 10;
|
|
guint32 channelType : 2;
|
|
guint32 channelId: 5;
|
|
guint32 direction: 1;
|
|
} rlc_result_hash_key;
|
|
|
|
/* Compare 2 rlc_result_hash_key structs */
|
|
static gint rlc_result_hash_equal(gconstpointer v, gconstpointer v2)
|
|
{
|
|
const rlc_result_hash_key *val1 = (const rlc_result_hash_key *)v;
|
|
const rlc_result_hash_key *val2 = (const rlc_result_hash_key *)v2;
|
|
|
|
/* All fields (and any padding...) must match */
|
|
return (memcmp(val1, val2, sizeof(rlc_result_hash_key)) == 0);
|
|
}
|
|
|
|
/* Compute a hash value for a given key. */
|
|
static guint rlc_result_hash_func(gconstpointer v)
|
|
{
|
|
const rlc_result_hash_key* val1 = (const rlc_result_hash_key *)v;
|
|
|
|
/* Got rid of multipliers - no evidence that they reduced collisions */
|
|
return val1->frameNumber + val1->SN +
|
|
val1->channelType +
|
|
val1->channelId +
|
|
val1->direction;
|
|
}
|
|
|
|
/* Convenience function to get a pointer for the hash_func to work with */
|
|
static gpointer get_report_hash_key(guint16 SN, guint32 frameNumber,
|
|
rlc_lte_info *p_rlc_lte_info,
|
|
gboolean do_persist)
|
|
{
|
|
static rlc_result_hash_key key;
|
|
rlc_result_hash_key *p_key;
|
|
|
|
/* Only allocate a struct when will be adding entry */
|
|
if (do_persist) {
|
|
p_key = wmem_new0(wmem_file_scope(), rlc_result_hash_key);
|
|
}
|
|
else {
|
|
memset(&key, 0, sizeof(rlc_result_hash_key));
|
|
p_key = &key;
|
|
}
|
|
|
|
/* Fill in details, and return pointer */
|
|
p_key->frameNumber = frameNumber;
|
|
p_key->SN = SN;
|
|
p_key->channelType = p_rlc_lte_info->channelType;
|
|
p_key->channelId = p_rlc_lte_info->channelId;
|
|
p_key->direction = p_rlc_lte_info->direction;
|
|
|
|
return p_key;
|
|
}
|
|
|
|
static void checkFIconsistency(sequence_analysis_report *p,
|
|
rlc_lte_info *p_rlc_lte_info,
|
|
gboolean newSegmentStarted,
|
|
proto_tree *seqnum_tree,
|
|
packet_info *pinfo, tvbuff_t *tvb)
|
|
{
|
|
proto_item *ti;
|
|
|
|
if (p->previousSegmentIncomplete) {
|
|
/* Previous segment was incomplete, so this PDU should continue it */
|
|
if (newSegmentStarted) {
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct,
|
|
tvb, 0, 0, FALSE);
|
|
expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_last_segment_not_continued,
|
|
"Last segment of previous PDU was not continued for UE %u (%s-%u)",
|
|
p_rlc_lte_info->ueid,
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
|
|
p_rlc_lte_info->channelId);
|
|
}
|
|
else {
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct,
|
|
tvb, 0, 0, TRUE);
|
|
proto_item_set_hidden(ti);
|
|
}
|
|
}
|
|
else {
|
|
/* Previous segment was complete, so this PDU should start a new one */
|
|
if (!newSegmentStarted) {
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct,
|
|
tvb, 0, 0, FALSE);
|
|
expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_last_segment_complete,
|
|
"Last segment of previous PDU was complete, but new segment was not started on UE %u (%s-%u)",
|
|
p_rlc_lte_info->ueid,
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
|
|
p_rlc_lte_info->channelId);
|
|
}
|
|
else {
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct,
|
|
tvb, 0, 0, TRUE);
|
|
proto_item_set_hidden(ti);
|
|
}
|
|
}
|
|
proto_item_set_generated(ti);
|
|
}
|
|
|
|
/* Add to the tree values associated with sequence analysis for this frame */
|
|
static void addChannelSequenceInfo(sequence_analysis_report *p,
|
|
gboolean isControlFrame,
|
|
rlc_lte_info *p_rlc_lte_info,
|
|
guint16 sequenceNumber,
|
|
gboolean newSegmentStarted,
|
|
rlc_lte_tap_info *tap_info,
|
|
packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb)
|
|
{
|
|
proto_tree *seqnum_tree;
|
|
proto_item *seqnum_ti;
|
|
proto_item *ti;
|
|
|
|
/* Create subtree */
|
|
seqnum_ti = proto_tree_add_string_format(tree,
|
|
hf_rlc_lte_sequence_analysis,
|
|
tvb, 0, 0,
|
|
"", "Sequence Analysis");
|
|
seqnum_tree = proto_item_add_subtree(seqnum_ti,
|
|
ett_rlc_lte_sequence_analysis);
|
|
proto_item_set_generated(seqnum_ti);
|
|
|
|
if (p->previousFrameNum != 0) {
|
|
ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_previous_frame,
|
|
tvb, 0, 0, p->previousFrameNum);
|
|
proto_item_set_generated(ti);
|
|
}
|
|
|
|
switch (p_rlc_lte_info->rlcMode) {
|
|
case RLC_AM_MODE:
|
|
|
|
/********************************************/
|
|
/* AM */
|
|
/********************************************/
|
|
|
|
switch (p->state) {
|
|
case SN_OK:
|
|
if (isControlFrame) {
|
|
return;
|
|
}
|
|
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
|
|
tvb, 0, 0, TRUE);
|
|
proto_item_set_generated(ti);
|
|
proto_item_append_text(seqnum_ti, " - OK");
|
|
|
|
/* Link to next SN in channel (if known) */
|
|
if (p->nextFrameNum != 0) {
|
|
proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_next_frame,
|
|
tvb, 0, 0, p->nextFrameNum);
|
|
}
|
|
/* Correct sequence number, so check frame indication bits consistent */
|
|
/* Deactivated for now as it gets confused by resegmentation */
|
|
/* checkFIconsistency(p, p_rlc_lte_info, newSegmentStarted, seqnum_tree, pinfo, tvb); */
|
|
break;
|
|
|
|
case SN_MAC_Retx:
|
|
if (isControlFrame) {
|
|
return;
|
|
}
|
|
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
|
|
tvb, 0, 0, FALSE);
|
|
proto_item_set_generated(ti);
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_mac_retx,
|
|
tvb, 0, 0, TRUE);
|
|
proto_item_set_generated(ti);
|
|
expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_mac_retx,
|
|
"AM Frame retransmitted for %s on UE %u - due to MAC retx! (%s-%u)",
|
|
val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
|
|
p_rlc_lte_info->ueid,
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
|
|
p_rlc_lte_info->channelId);
|
|
proto_item_append_text(seqnum_ti, " - MAC retx of SN %u", p->firstSN);
|
|
break;
|
|
|
|
case SN_Retx:
|
|
if (isControlFrame) {
|
|
return;
|
|
}
|
|
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
|
|
tvb, 0, 0, FALSE);
|
|
proto_item_set_generated(ti);
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_retx,
|
|
tvb, 0, 0, TRUE);
|
|
proto_item_set_generated(ti);
|
|
expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_retx,
|
|
"AM Frame retransmitted for %s on UE %u - most likely in response to NACK (%s-%u)",
|
|
val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
|
|
p_rlc_lte_info->ueid,
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
|
|
p_rlc_lte_info->channelId);
|
|
proto_item_append_text(seqnum_ti, " - SN %u retransmitted", p->firstSN);
|
|
break;
|
|
|
|
case SN_Repeated:
|
|
if (isControlFrame) {
|
|
return;
|
|
}
|
|
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
|
|
tvb, 0, 0, FALSE);
|
|
proto_item_set_generated(ti);
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated,
|
|
tvb, 0, 0, TRUE);
|
|
proto_item_set_generated(ti);
|
|
expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_repeated,
|
|
"AM SN Repeated for %s for UE %u - probably because didn't receive Status PDU? (%s-%u)",
|
|
val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
|
|
p_rlc_lte_info->ueid,
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
|
|
p_rlc_lte_info->channelId);
|
|
proto_item_append_text(seqnum_ti, "- SN %u Repeated", p->firstSN);
|
|
break;
|
|
|
|
case SN_Missing:
|
|
if (isControlFrame) {
|
|
return;
|
|
}
|
|
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
|
|
tvb, 0, 0, FALSE);
|
|
proto_item_set_generated(ti);
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_skipped,
|
|
tvb, 0, 0, TRUE);
|
|
proto_item_set_generated(ti);
|
|
if (p->lastSN != p->firstSN) {
|
|
expert_add_info_format(pinfo, ti, &ei_rlc_lte_am_sn_missing,
|
|
"AM SNs (%u to %u) missing for %s on UE %u (%s-%u)",
|
|
p->firstSN, p->lastSN,
|
|
val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
|
|
p_rlc_lte_info->ueid,
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
|
|
p_rlc_lte_info->channelId);
|
|
proto_item_append_text(seqnum_ti, " - SNs missing (%u to %u)",
|
|
p->firstSN, p->lastSN);
|
|
if (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) {
|
|
tap_info->missingSNs = ((65536 + (guint32)p->lastSN - (guint32)p->firstSN) % 65536) + 1;
|
|
} else {
|
|
tap_info->missingSNs = ((1024 + p->lastSN - p->firstSN) % 1024) + 1;
|
|
}
|
|
}
|
|
else {
|
|
expert_add_info_format(pinfo, ti, &ei_rlc_lte_am_sn_missing,
|
|
"AM SN (%u) missing for %s on UE %u (%s-%u)",
|
|
p->firstSN,
|
|
val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
|
|
p_rlc_lte_info->ueid,
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
|
|
p_rlc_lte_info->channelId);
|
|
proto_item_append_text(seqnum_ti, " - SN missing (%u)", p->firstSN);
|
|
tap_info->missingSNs = 1;
|
|
}
|
|
break;
|
|
|
|
case ACK_Out_of_Window:
|
|
if (!isControlFrame) {
|
|
return;
|
|
}
|
|
|
|
|
|
/* Not OK */
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
|
|
tvb, 0, 0, FALSE);
|
|
/* Out of range */
|
|
proto_item_set_generated(ti);
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ack_out_of_range,
|
|
tvb, 0, 0, TRUE);
|
|
proto_item_set_generated(ti);
|
|
|
|
/* Link back to last seen SN in other direction */
|
|
ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame,
|
|
tvb, 0, 0, p->previousFrameNum);
|
|
proto_item_set_generated(ti);
|
|
|
|
/* Expert error */
|
|
expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame,
|
|
"AM ACK for SN %u - but last received SN in other direction is %u for UE %u (%s-%u)",
|
|
p->firstSN, p->sequenceExpected,
|
|
p_rlc_lte_info->ueid,
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
|
|
p_rlc_lte_info->channelId);
|
|
proto_item_append_text(seqnum_ti, "- ACK SN %u Outside Rx Window - last received SN is %u",
|
|
p->firstSN, p->sequenceExpected);
|
|
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case RLC_UM_MODE:
|
|
|
|
/********************************************/
|
|
/* UM */
|
|
/********************************************/
|
|
|
|
/* Expected sequence number */
|
|
ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_expected_sn,
|
|
tvb, 0, 0, p->sequenceExpected);
|
|
proto_item_set_generated(ti);
|
|
if (p->sequenceExpectedCorrect) {
|
|
proto_item_set_hidden(ti);
|
|
}
|
|
|
|
if (!p->sequenceExpectedCorrect) {
|
|
/* Work out SN wrap (in case needed below) */
|
|
guint16 snLimit;
|
|
if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_5_BITS) {
|
|
snLimit = 32;
|
|
}
|
|
else {
|
|
snLimit = 1024;
|
|
}
|
|
|
|
switch (p->state) {
|
|
case SN_Missing:
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
|
|
tvb, 0, 0, FALSE);
|
|
proto_item_set_generated(ti);
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_skipped,
|
|
tvb, 0, 0, TRUE);
|
|
proto_item_set_generated(ti);
|
|
if (p->lastSN != p->firstSN) {
|
|
expert_add_info_format(pinfo, ti, &ei_rlc_lte_um_sn_missing,
|
|
"UM SNs (%u to %u) missing for %s on UE %u (%s-%u)",
|
|
p->firstSN, p->lastSN,
|
|
val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
|
|
p_rlc_lte_info->ueid,
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
|
|
p_rlc_lte_info->channelId);
|
|
proto_item_append_text(seqnum_ti, " - SNs missing (%u to %u)",
|
|
p->firstSN, p->lastSN);
|
|
tap_info->missingSNs = ((snLimit + p->lastSN - p->firstSN) % snLimit) + 1;
|
|
}
|
|
else {
|
|
expert_add_info_format(pinfo, ti, &ei_rlc_lte_um_sn_missing,
|
|
"UM SN (%u) missing for %s on UE %u (%s-%u)",
|
|
p->firstSN,
|
|
val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
|
|
p_rlc_lte_info->ueid,
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
|
|
p_rlc_lte_info->channelId);
|
|
proto_item_append_text(seqnum_ti, " - SN missing (%u)",
|
|
p->firstSN);
|
|
tap_info->missingSNs = 1;
|
|
}
|
|
break;
|
|
|
|
case SN_Repeated:
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
|
|
tvb, 0, 0, FALSE);
|
|
proto_item_set_generated(ti);
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated,
|
|
tvb, 0, 0, TRUE);
|
|
proto_item_set_generated(ti);
|
|
expert_add_info_format(pinfo, ti, &ei_rlc_lte_um_sn_repeated,
|
|
"UM SN (%u) repeated for %s for UE %u (%s-%u)",
|
|
p->firstSN,
|
|
val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
|
|
p_rlc_lte_info->ueid,
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
|
|
p_rlc_lte_info->channelId);
|
|
proto_item_append_text(seqnum_ti, "- SN %u Repeated",
|
|
p->firstSN);
|
|
break;
|
|
|
|
case SN_MAC_Retx:
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
|
|
tvb, 0, 0, FALSE);
|
|
proto_item_set_generated(ti);
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_mac_retx,
|
|
tvb, 0, 0, TRUE);
|
|
proto_item_set_generated(ti);
|
|
expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_mac_retx,
|
|
"UM Frame retransmitted for %s on UE %u - due to MAC retx! (%s-%u)",
|
|
val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
|
|
p_rlc_lte_info->ueid,
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
|
|
p_rlc_lte_info->channelId);
|
|
break;
|
|
|
|
default:
|
|
/* Incorrect sequence number */
|
|
expert_add_info_format(pinfo, ti, &ei_rlc_lte_wrong_sequence_number,
|
|
"Wrong Sequence Number for %s on UE %u - got %u, expected %u (%s-%u)",
|
|
val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
|
|
p_rlc_lte_info->ueid, sequenceNumber, p->sequenceExpected,
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
|
|
p_rlc_lte_info->channelId);
|
|
|
|
break;
|
|
}
|
|
|
|
}
|
|
else {
|
|
/* Correct sequence number, so check frame indication bits consistent */
|
|
checkFIconsistency(p, p_rlc_lte_info, newSegmentStarted, seqnum_tree, pinfo, tvb);
|
|
|
|
/* Set OK here! */
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
|
|
tvb, 0, 0, TRUE);
|
|
proto_item_set_generated(ti);
|
|
proto_item_append_text(seqnum_ti, " - OK");
|
|
}
|
|
|
|
/* Next channel frame */
|
|
if (p->nextFrameNum != 0) {
|
|
ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_next_frame,
|
|
tvb, 0, 0, p->nextFrameNum);
|
|
proto_item_set_generated(ti);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Update the channel status and set report for this frame */
|
|
static sequence_analysis_state checkChannelSequenceInfo(packet_info *pinfo, tvbuff_t *tvb,
|
|
rlc_lte_info *p_rlc_lte_info,
|
|
gboolean isControlFrame,
|
|
guint8 number_of_segments,
|
|
guint16 firstSegmentOffset,
|
|
guint16 firstSegmentLength,
|
|
guint16 lastSegmentOffset,
|
|
guint16 sequenceNumber,
|
|
gboolean first_includes_start, gboolean last_includes_end,
|
|
gboolean is_resegmented _U_,
|
|
rlc_lte_tap_info *tap_info,
|
|
proto_tree *tree)
|
|
{
|
|
channel_hash_key channel_key;
|
|
channel_hash_key *p_channel_key;
|
|
channel_sequence_analysis_status *p_channel_status;
|
|
sequence_analysis_report *p_report_in_frame = NULL;
|
|
gboolean createdChannel = FALSE;
|
|
guint16 expectedSequenceNumber = 0;
|
|
guint32 snLimit = 0;
|
|
|
|
/* If find stat_report_in_frame already, use that and get out */
|
|
if (pinfo->fd->visited) {
|
|
p_report_in_frame = (sequence_analysis_report*)wmem_map_lookup(sequence_analysis_report_hash,
|
|
get_report_hash_key(sequenceNumber,
|
|
pinfo->num,
|
|
p_rlc_lte_info,
|
|
FALSE));
|
|
if (p_report_in_frame != NULL) {
|
|
addChannelSequenceInfo(p_report_in_frame, isControlFrame, p_rlc_lte_info,
|
|
sequenceNumber, first_includes_start,
|
|
tap_info, pinfo, tree, tvb);
|
|
return p_report_in_frame->state;
|
|
}
|
|
|
|
/* Don't just give up here... */
|
|
}
|
|
|
|
|
|
/**************************************************/
|
|
/* Create or find an entry for this channel state */
|
|
channel_key.ueId = p_rlc_lte_info->ueid;
|
|
channel_key.channelType = p_rlc_lte_info->channelType;
|
|
channel_key.channelId = p_rlc_lte_info->channelId;
|
|
channel_key.direction = p_rlc_lte_info->direction;
|
|
|
|
/* Do the table lookup */
|
|
p_channel_status = (channel_sequence_analysis_status*)wmem_map_lookup(sequence_analysis_channel_hash, &channel_key);
|
|
|
|
/* Create table entry if necessary */
|
|
if (p_channel_status == NULL) {
|
|
createdChannel = TRUE;
|
|
|
|
/* Allocate a new value and duplicate key contents */
|
|
p_channel_status = wmem_new0(wmem_file_scope(), channel_sequence_analysis_status);
|
|
p_channel_key = (channel_hash_key *)wmem_memdup(wmem_file_scope(), &channel_key, sizeof(channel_hash_key));
|
|
|
|
/* Set mode */
|
|
p_channel_status->rlcMode = p_rlc_lte_info->rlcMode;
|
|
|
|
/* Add entry */
|
|
wmem_map_insert(sequence_analysis_channel_hash, p_channel_key, p_channel_status);
|
|
}
|
|
|
|
/* Create space for frame state_report */
|
|
p_report_in_frame = wmem_new0(wmem_file_scope(), sequence_analysis_report);
|
|
|
|
|
|
/* Deal with according to channel mode */
|
|
switch (p_channel_status->rlcMode) {
|
|
case RLC_UM_MODE:
|
|
|
|
if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_5_BITS) {
|
|
snLimit = 32;
|
|
}
|
|
else {
|
|
snLimit = 1024;
|
|
}
|
|
|
|
/* Work out expected sequence number */
|
|
if (!createdChannel) {
|
|
expectedSequenceNumber = (p_channel_status->previousSequenceNumber + 1) % snLimit;
|
|
}
|
|
else {
|
|
/* Whatever we got is fine.. */
|
|
expectedSequenceNumber = sequenceNumber;
|
|
}
|
|
|
|
if ((sequenceNumber == 0) &&
|
|
((p_rlc_lte_info->channelType == CHANNEL_TYPE_MCCH) || (p_rlc_lte_info->channelType == CHANNEL_TYPE_MTCH))) {
|
|
/* With eMBMS, SN restarts to 0 at each MCH Scheduling Period so we cannot deduce easily whether
|
|
there was a PDU loss or not without analysing the Frame Indicator; assume no loss when seeing 0 */
|
|
expectedSequenceNumber = 0;
|
|
}
|
|
|
|
/* Set report for this frame */
|
|
/* For UM, sequence number is always expectedSequence number */
|
|
p_report_in_frame->sequenceExpectedCorrect = (sequenceNumber == expectedSequenceNumber);
|
|
|
|
/* For wrong sequence number... */
|
|
if (!p_report_in_frame->sequenceExpectedCorrect) {
|
|
|
|
/* Don't get confused by MAC (HARQ) retx */
|
|
if (is_mac_lte_frame_retx(pinfo, p_rlc_lte_info->direction)) {
|
|
p_report_in_frame->state = SN_MAC_Retx;
|
|
p_report_in_frame->firstSN = sequenceNumber;
|
|
|
|
/* No channel state to update */
|
|
break;
|
|
}
|
|
|
|
/* Frames are not missing if we get an earlier sequence number again */
|
|
/* TODO: taking time into account would give better idea of whether missing or repeated... */
|
|
else if ((p_rlc_lte_info->channelType == CHANNEL_TYPE_MCCH) || (p_rlc_lte_info->channelType == CHANNEL_TYPE_MTCH) ||
|
|
(((snLimit + sequenceNumber - expectedSequenceNumber) % snLimit) < 10)) {
|
|
reassembly_destroy(p_channel_status);
|
|
|
|
p_report_in_frame->state = SN_Missing;
|
|
tap_info->missingSNs = (snLimit + sequenceNumber - expectedSequenceNumber) % snLimit;
|
|
p_report_in_frame->firstSN = expectedSequenceNumber;
|
|
p_report_in_frame->lastSN = (snLimit + sequenceNumber - 1) % snLimit;
|
|
|
|
p_report_in_frame->sequenceExpected = expectedSequenceNumber;
|
|
p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum;
|
|
p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete;
|
|
|
|
/* Update channel status to remember *this* frame */
|
|
p_channel_status->previousFrameNum = pinfo->num;
|
|
p_channel_status->previousSequenceNumber = sequenceNumber;
|
|
p_channel_status->previousSegmentIncomplete = !last_includes_end;
|
|
}
|
|
else {
|
|
/* An SN has been repeated */
|
|
p_report_in_frame->state = SN_Repeated;
|
|
p_report_in_frame->firstSN = sequenceNumber;
|
|
|
|
p_report_in_frame->sequenceExpected = expectedSequenceNumber;
|
|
p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum;
|
|
}
|
|
}
|
|
else {
|
|
/* SN was OK */
|
|
p_report_in_frame->sequenceExpected = expectedSequenceNumber;
|
|
p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum;
|
|
p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete;
|
|
|
|
/* Update channel status to remember *this* frame */
|
|
p_channel_status->previousFrameNum = pinfo->num;
|
|
p_channel_status->previousSequenceNumber = sequenceNumber;
|
|
p_channel_status->previousSegmentIncomplete = !last_includes_end;
|
|
|
|
if (p_channel_status->reassembly_info) {
|
|
/* Add next segment to reassembly info */
|
|
reassembly_add_segment(p_channel_status, sequenceNumber, pinfo->num,
|
|
tvb, firstSegmentOffset, firstSegmentLength);
|
|
|
|
/* The end of existing reassembly? */
|
|
if (!first_includes_start &&
|
|
((number_of_segments > 1) || last_includes_end)) {
|
|
|
|
reassembly_record(p_channel_status, pinfo, sequenceNumber, p_rlc_lte_info);
|
|
reassembly_destroy(p_channel_status);
|
|
}
|
|
}
|
|
|
|
/* The start of a new reassembly? */
|
|
if (!last_includes_end &&
|
|
((number_of_segments > 1) || first_includes_start)) {
|
|
|
|
guint16 lastSegmentLength = tvb_reported_length(tvb)-lastSegmentOffset;
|
|
|
|
if (global_rlc_lte_reassembly) {
|
|
reassembly_reset(p_channel_status);
|
|
reassembly_add_segment(p_channel_status, sequenceNumber,
|
|
pinfo->num,
|
|
tvb, lastSegmentOffset, lastSegmentLength);
|
|
}
|
|
}
|
|
|
|
if (p_report_in_frame->previousFrameNum != 0) {
|
|
/* Get report for previous frame */
|
|
sequence_analysis_report *p_previous_report;
|
|
if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_5_BITS) {
|
|
snLimit = 32;
|
|
}
|
|
else {
|
|
snLimit = 1024;
|
|
}
|
|
|
|
/* Look up report for previous SN */
|
|
p_previous_report = (sequence_analysis_report*)wmem_map_lookup(sequence_analysis_report_hash,
|
|
get_report_hash_key((sequenceNumber+snLimit-1) % snLimit,
|
|
p_report_in_frame->previousFrameNum,
|
|
p_rlc_lte_info,
|
|
FALSE));
|
|
/* It really shouldn't be NULL... */
|
|
if (p_previous_report != NULL) {
|
|
/* Point it forward to this one */
|
|
p_previous_report->nextFrameNum = pinfo->num;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case RLC_AM_MODE:
|
|
|
|
if (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) {
|
|
snLimit = 65536;
|
|
} else {
|
|
snLimit = 1024;
|
|
}
|
|
|
|
/* Work out expected sequence number */
|
|
if (!createdChannel) {
|
|
expectedSequenceNumber = (p_channel_status->previousSequenceNumber + 1) % snLimit;
|
|
}
|
|
else {
|
|
/* Whatever we got is fine.. */
|
|
expectedSequenceNumber = sequenceNumber;
|
|
}
|
|
|
|
/* For AM, may be:
|
|
- expected Sequence number OR
|
|
- previous frame repeated
|
|
- old SN being sent (in response to NACK)
|
|
- new SN, but with frames missed out
|
|
Assume window whose front is at expectedSequenceNumber */
|
|
|
|
/* First of all, check to see whether frame is judged to be MAC Retx */
|
|
if (is_mac_lte_frame_retx(pinfo, p_rlc_lte_info->direction)) {
|
|
/* Just report that this is a MAC Retx */
|
|
p_report_in_frame->state = SN_MAC_Retx;
|
|
p_report_in_frame->firstSN = sequenceNumber;
|
|
|
|
/* No channel state to update */
|
|
break;
|
|
}
|
|
|
|
if (sequenceNumber != expectedSequenceNumber) {
|
|
/* Don't trash reassembly info if this looks like a close retx... */
|
|
if (((snLimit + sequenceNumber - expectedSequenceNumber) % snLimit) < 50) {
|
|
reassembly_destroy(p_channel_status);
|
|
}
|
|
}
|
|
|
|
/* Expected? */
|
|
if (sequenceNumber == expectedSequenceNumber) {
|
|
/* Set report for this frame */
|
|
p_report_in_frame->sequenceExpectedCorrect = TRUE;
|
|
p_report_in_frame->sequenceExpected = expectedSequenceNumber;
|
|
p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum;
|
|
p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete;
|
|
p_report_in_frame->state = SN_OK;
|
|
|
|
/* Update channel status */
|
|
p_channel_status->previousSequenceNumber = sequenceNumber;
|
|
p_channel_status->previousFrameNum = pinfo->num;
|
|
p_channel_status->previousSegmentIncomplete = !last_includes_end;
|
|
|
|
|
|
if (p_channel_status->reassembly_info) {
|
|
|
|
/* Add next segment to reassembly info */
|
|
reassembly_add_segment(p_channel_status, sequenceNumber, pinfo->num,
|
|
tvb, firstSegmentOffset, firstSegmentLength);
|
|
|
|
/* The end of existing reassembly? */
|
|
if (!first_includes_start &&
|
|
((number_of_segments > 1) || last_includes_end)) {
|
|
|
|
reassembly_record(p_channel_status, pinfo,
|
|
sequenceNumber, p_rlc_lte_info);
|
|
reassembly_destroy(p_channel_status);
|
|
}
|
|
}
|
|
|
|
/* The start of a new reassembly? */
|
|
if (!last_includes_end &&
|
|
((number_of_segments > 1) || first_includes_start)) {
|
|
|
|
guint16 lastSegmentLength = tvb_reported_length(tvb)-lastSegmentOffset;
|
|
if (global_rlc_lte_reassembly) {
|
|
reassembly_reset(p_channel_status);
|
|
reassembly_add_segment(p_channel_status, sequenceNumber,
|
|
pinfo->num,
|
|
tvb, lastSegmentOffset, lastSegmentLength);
|
|
}
|
|
}
|
|
|
|
if (p_report_in_frame->previousFrameNum != 0) {
|
|
/* Get report for previous frame */
|
|
sequence_analysis_report *p_previous_report;
|
|
p_previous_report = (sequence_analysis_report*)wmem_map_lookup(sequence_analysis_report_hash,
|
|
get_report_hash_key((sequenceNumber+snLimit-1) % snLimit,
|
|
p_report_in_frame->previousFrameNum,
|
|
p_rlc_lte_info,
|
|
FALSE));
|
|
/* It really shouldn't be NULL... */
|
|
if (p_previous_report != NULL) {
|
|
/* Point it forward to this one */
|
|
p_previous_report->nextFrameNum = pinfo->num;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* Previous subframe repeated? */
|
|
else if (((sequenceNumber+1) % snLimit) == expectedSequenceNumber) {
|
|
p_report_in_frame->state = SN_Repeated;
|
|
|
|
/* Set report for this frame */
|
|
p_report_in_frame->sequenceExpectedCorrect = FALSE;
|
|
p_report_in_frame->sequenceExpected = expectedSequenceNumber;
|
|
p_report_in_frame->firstSN = sequenceNumber;
|
|
p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum;
|
|
p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete;
|
|
|
|
|
|
/* Really should be nothing to update... */
|
|
p_channel_status->previousSequenceNumber = sequenceNumber;
|
|
p_channel_status->previousFrameNum = pinfo->num;
|
|
p_channel_status->previousSegmentIncomplete = !last_includes_end;
|
|
}
|
|
|
|
else {
|
|
/* Need to work out if new (with skips, or likely a retx (due to NACK)) */
|
|
gint delta = (snLimit + expectedSequenceNumber - sequenceNumber) % snLimit;
|
|
|
|
/* Rx window is 512/32768, so check to see if this is a retx */
|
|
if (delta < (gint)(snLimit>>1)) {
|
|
/* Probably a retx due to receiving NACK */
|
|
p_report_in_frame->state = SN_Retx;
|
|
|
|
p_report_in_frame->firstSN = sequenceNumber;
|
|
/* Don't update anything in channel state */
|
|
}
|
|
|
|
else {
|
|
/* Ahead of expected SN. Assume frames have been missed */
|
|
p_report_in_frame->state = SN_Missing;
|
|
|
|
p_report_in_frame->firstSN = expectedSequenceNumber;
|
|
p_report_in_frame->lastSN = (snLimit + sequenceNumber-1) % snLimit;
|
|
|
|
/* Update channel state - forget about missed SNs */
|
|
p_report_in_frame->sequenceExpected = expectedSequenceNumber;
|
|
p_channel_status->previousSequenceNumber = sequenceNumber;
|
|
p_channel_status->previousFrameNum = pinfo->num;
|
|
p_channel_status->previousSegmentIncomplete = !last_includes_end;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* Shouldn't get here! */
|
|
return SN_Error;
|
|
}
|
|
|
|
/* Associate with this frame number */
|
|
wmem_map_insert(sequence_analysis_report_hash,
|
|
get_report_hash_key(sequenceNumber, pinfo->num, p_rlc_lte_info, TRUE),
|
|
p_report_in_frame);
|
|
|
|
/* Add state report for this frame into tree */
|
|
addChannelSequenceInfo(p_report_in_frame, isControlFrame, p_rlc_lte_info, sequenceNumber,
|
|
first_includes_start, tap_info, pinfo, tree, tvb);
|
|
|
|
return p_report_in_frame->state;
|
|
}
|
|
|
|
|
|
/* Add to the tree values associated with sequence analysis for this frame */
|
|
static void addChannelRepeatedNACKInfo(channel_repeated_nack_report *p,
|
|
rlc_lte_info *p_rlc_lte_info,
|
|
packet_info *pinfo, proto_tree *tree,
|
|
tvbuff_t *tvb)
|
|
{
|
|
proto_tree *seqnum_tree;
|
|
proto_item *seqnum_ti;
|
|
proto_item *ti;
|
|
gint n;
|
|
|
|
/* Create subtree */
|
|
seqnum_ti = proto_tree_add_string_format(tree,
|
|
hf_rlc_lte_sequence_analysis,
|
|
tvb, 0, 0,
|
|
"", "Sequence Analysis");
|
|
seqnum_tree = proto_item_add_subtree(seqnum_ti,
|
|
ett_rlc_lte_sequence_analysis);
|
|
proto_item_set_generated(seqnum_ti);
|
|
|
|
/* OK = FALSE */
|
|
ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
|
|
tvb, 0, 0, FALSE);
|
|
proto_item_set_generated(ti);
|
|
|
|
/* Add each repeated NACK as item & expert info */
|
|
for (n=0; n < p->noOfNACKsRepeated; n++) {
|
|
|
|
ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated_nack,
|
|
tvb, 0, 0, p->repeatedNACKs[n]);
|
|
proto_item_set_generated(ti);
|
|
|
|
expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_repeated_nack,
|
|
"Same SN (%u) NACKd for %s on UE %u in successive Status PDUs",
|
|
p->repeatedNACKs[n],
|
|
val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
|
|
p_rlc_lte_info->ueid);
|
|
}
|
|
|
|
/* Link back to previous status report */
|
|
ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated_nack_original_frame,
|
|
tvb, 0, 0, p->previousFrameNum);
|
|
proto_item_set_generated(ti);
|
|
|
|
/* Append count to sequence analysis root */
|
|
proto_item_append_text(seqnum_ti, " - %u SNs repeated from previous Status PDU",
|
|
p->noOfNACKsRepeated);
|
|
}
|
|
|
|
|
|
/* Update the channel repeated NACK status and set report for this frame */
|
|
static void checkChannelRepeatedNACKInfo(packet_info *pinfo,
|
|
rlc_lte_info *p_rlc_lte_info,
|
|
rlc_lte_tap_info *tap_info,
|
|
proto_tree *tree,
|
|
tvbuff_t *tvb)
|
|
{
|
|
channel_hash_key channel_key;
|
|
channel_hash_key *p_channel_key;
|
|
channel_repeated_nack_status *p_channel_status;
|
|
channel_repeated_nack_report *p_report_in_frame = NULL;
|
|
|
|
guint16 noOfNACKsRepeated = 0;
|
|
guint16 repeatedNACKs[MAX_NACKs];
|
|
gint n, i, j;
|
|
|
|
/* If find state_report_in_frame already, use that and get out */
|
|
if (pinfo->fd->visited) {
|
|
p_report_in_frame = (channel_repeated_nack_report*)wmem_map_lookup(repeated_nack_report_hash,
|
|
get_report_hash_key(0, pinfo->num,
|
|
p_rlc_lte_info, FALSE));
|
|
if (p_report_in_frame != NULL) {
|
|
addChannelRepeatedNACKInfo(p_report_in_frame, p_rlc_lte_info,
|
|
pinfo, tree, tvb);
|
|
return;
|
|
}
|
|
else {
|
|
/* Give up - we must have tried already... */
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/**************************************************/
|
|
/* Create or find an entry for this channel state */
|
|
channel_key.ueId = p_rlc_lte_info->ueid;
|
|
channel_key.channelType = p_rlc_lte_info->channelType;
|
|
channel_key.channelId = p_rlc_lte_info->channelId;
|
|
channel_key.direction = p_rlc_lte_info->direction;
|
|
memset(repeatedNACKs, 0, sizeof(repeatedNACKs));
|
|
|
|
/* Do the table lookup */
|
|
p_channel_status = (channel_repeated_nack_status*)wmem_map_lookup(repeated_nack_channel_hash, &channel_key);
|
|
|
|
/* Create table entry if necessary */
|
|
if (p_channel_status == NULL) {
|
|
|
|
/* Allocate a new key and value */
|
|
p_channel_key = wmem_new(wmem_file_scope(), channel_hash_key);
|
|
p_channel_status = wmem_new0(wmem_file_scope(), channel_repeated_nack_status);
|
|
|
|
/* Copy key contents */
|
|
memcpy(p_channel_key, &channel_key, sizeof(channel_hash_key));
|
|
|
|
/* Add entry to table */
|
|
wmem_map_insert(repeated_nack_channel_hash, p_channel_key, p_channel_status);
|
|
}
|
|
|
|
/* Compare NACKs in channel status with NACKs in tap_info.
|
|
Note any that are repeated */
|
|
for (i=0; i < p_channel_status->noOfNACKs; i++) {
|
|
for (j=0; j < MIN(tap_info->noOfNACKs, MAX_NACKs); j++) {
|
|
if (tap_info->NACKs[j] == p_channel_status->NACKs[i]) {
|
|
/* Don't add the same repeated NACK twice! */
|
|
if ((noOfNACKsRepeated == 0) ||
|
|
(repeatedNACKs[noOfNACKsRepeated-1] != p_channel_status->NACKs[i])) {
|
|
|
|
repeatedNACKs[noOfNACKsRepeated++] = p_channel_status->NACKs[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Copy NACKs from tap_info into channel status for next time! */
|
|
p_channel_status->noOfNACKs = 0;
|
|
for (n=0; n < MIN(tap_info->noOfNACKs, MAX_NACKs); n++) {
|
|
p_channel_status->NACKs[p_channel_status->noOfNACKs++] = tap_info->NACKs[n];
|
|
}
|
|
|
|
if (noOfNACKsRepeated >= 1) {
|
|
/* Create space for frame state_report */
|
|
p_report_in_frame = wmem_new(wmem_file_scope(), channel_repeated_nack_report);
|
|
|
|
/* Copy in found duplicates */
|
|
for (n=0; n < MIN(tap_info->noOfNACKs, MAX_NACKs); n++) {
|
|
p_report_in_frame->repeatedNACKs[n] = repeatedNACKs[n];
|
|
}
|
|
p_report_in_frame->noOfNACKsRepeated = noOfNACKsRepeated;
|
|
|
|
p_report_in_frame->previousFrameNum = p_channel_status->frameNum;
|
|
|
|
/* Associate with this frame number */
|
|
wmem_map_insert(repeated_nack_report_hash,
|
|
get_report_hash_key(0, pinfo->num,
|
|
p_rlc_lte_info, TRUE),
|
|
p_report_in_frame);
|
|
|
|
/* Add state report for this frame into tree */
|
|
addChannelRepeatedNACKInfo(p_report_in_frame, p_rlc_lte_info,
|
|
pinfo, tree, tvb);
|
|
}
|
|
|
|
/* Save frame number for next comparison */
|
|
p_channel_status->frameNum = pinfo->num;
|
|
}
|
|
|
|
/* Check that the ACK is consistent with data the expected sequence number
|
|
in the other direction */
|
|
static void checkChannelACKWindow(guint16 ack_sn,
|
|
packet_info *pinfo,
|
|
rlc_lte_info *p_rlc_lte_info,
|
|
rlc_lte_tap_info *tap_info,
|
|
proto_tree *tree,
|
|
tvbuff_t *tvb)
|
|
{
|
|
channel_hash_key channel_key;
|
|
channel_sequence_analysis_status *p_channel_status;
|
|
sequence_analysis_report *p_report_in_frame = NULL;
|
|
guint32 snLimit;
|
|
|
|
/* If find stat_report_in_frame already, use that and get out */
|
|
if (pinfo->fd->visited) {
|
|
p_report_in_frame = (sequence_analysis_report*)wmem_map_lookup(sequence_analysis_report_hash,
|
|
get_report_hash_key(0, pinfo->num,
|
|
p_rlc_lte_info,
|
|
FALSE));
|
|
if (p_report_in_frame != NULL) {
|
|
/* Add any info to tree */
|
|
addChannelSequenceInfo(p_report_in_frame, TRUE, p_rlc_lte_info,
|
|
0, FALSE,
|
|
tap_info, pinfo, tree, tvb);
|
|
return;
|
|
}
|
|
else {
|
|
/* Give up - we must have tried already... */
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*******************************************************************/
|
|
/* Find an entry for this channel state (in the opposite direction */
|
|
channel_key.ueId = p_rlc_lte_info->ueid;
|
|
channel_key.channelType = p_rlc_lte_info->channelType;
|
|
channel_key.channelId = p_rlc_lte_info->channelId;
|
|
channel_key.direction =
|
|
(p_rlc_lte_info->direction == DIRECTION_UPLINK) ? DIRECTION_DOWNLINK : DIRECTION_UPLINK;
|
|
|
|
/* Do the table lookup */
|
|
p_channel_status = (channel_sequence_analysis_status*)wmem_map_lookup(sequence_analysis_channel_hash, &channel_key);
|
|
|
|
/* Create table entry if necessary */
|
|
if (p_channel_status == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Is it in the rx window? This test will catch if it's ahead, but we don't
|
|
really know what the back of the tx window is... */
|
|
snLimit = (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) ? 65536 : 1024;
|
|
if (((snLimit + (guint32)p_channel_status->previousSequenceNumber+1 - ack_sn) % snLimit) > (snLimit>>1)) {
|
|
|
|
/* Set result */
|
|
p_report_in_frame = wmem_new0(wmem_file_scope(), sequence_analysis_report);
|
|
p_report_in_frame->state = ACK_Out_of_Window;
|
|
p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum;
|
|
p_report_in_frame->sequenceExpected = p_channel_status->previousSequenceNumber;
|
|
p_report_in_frame->firstSN = ack_sn;
|
|
|
|
/* Associate with this frame number */
|
|
wmem_map_insert(sequence_analysis_report_hash,
|
|
get_report_hash_key(0, pinfo->num,
|
|
p_rlc_lte_info, TRUE),
|
|
p_report_in_frame);
|
|
|
|
/* Add state report for this frame into tree */
|
|
addChannelSequenceInfo(p_report_in_frame, TRUE, p_rlc_lte_info, 0,
|
|
FALSE, tap_info, pinfo, tree, tvb);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/***************************************************/
|
|
/* Transparent mode PDU. Call RRC if configured to */
|
|
static void dissect_rlc_lte_tm(tvbuff_t *tvb, packet_info *pinfo,
|
|
proto_tree *tree,
|
|
int offset,
|
|
rlc_lte_info *p_rlc_lte_info,
|
|
proto_item *top_ti)
|
|
{
|
|
proto_item *raw_tm_ti;
|
|
proto_item *tm_ti;
|
|
|
|
/* Create hidden TM root */
|
|
tm_ti = proto_tree_add_string_format(tree, hf_rlc_lte_tm,
|
|
tvb, offset, 0, "", "TM");
|
|
proto_item_set_hidden(tm_ti);
|
|
|
|
/* Remaining bytes are all data */
|
|
raw_tm_ti = proto_tree_add_item(tree, hf_rlc_lte_tm_data, tvb, offset, -1, ENC_NA);
|
|
if (!global_rlc_lte_call_rrc_for_ccch) {
|
|
write_pdu_label_and_info(top_ti, NULL, pinfo,
|
|
" [%u-bytes]", tvb_reported_length_remaining(tvb, offset));
|
|
}
|
|
|
|
if (global_rlc_lte_call_rrc_for_ccch) {
|
|
tvbuff_t *rrc_tvb = tvb_new_subset_remaining(tvb, offset);
|
|
volatile dissector_handle_t protocol_handle;
|
|
|
|
switch (p_rlc_lte_info->channelType) {
|
|
case CHANNEL_TYPE_CCCH:
|
|
if (p_rlc_lte_info->direction == DIRECTION_UPLINK) {
|
|
protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ?
|
|
lte_rrc_ul_ccch_nb : lte_rrc_ul_ccch;
|
|
}
|
|
else {
|
|
protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ?
|
|
lte_rrc_dl_ccch_nb : lte_rrc_dl_ccch;
|
|
}
|
|
break;
|
|
|
|
case CHANNEL_TYPE_BCCH_BCH:
|
|
protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ?
|
|
lte_rrc_bcch_bch_nb : lte_rrc_bcch_bch;
|
|
break;
|
|
case CHANNEL_TYPE_BCCH_DL_SCH:
|
|
protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ?
|
|
lte_rrc_bcch_dl_sch_nb : lte_rrc_bcch_dl_sch;
|
|
break;
|
|
case CHANNEL_TYPE_PCCH:
|
|
protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ?
|
|
lte_rrc_pcch_nb : lte_rrc_pcch;
|
|
break;
|
|
|
|
case CHANNEL_TYPE_SRB:
|
|
case CHANNEL_TYPE_DRB:
|
|
case CHANNEL_TYPE_MCCH:
|
|
case CHANNEL_TYPE_MTCH:
|
|
|
|
default:
|
|
/* Shouldn't happen, just return... */
|
|
return;
|
|
}
|
|
|
|
/* Hide raw view of bytes */
|
|
proto_item_set_hidden(raw_tm_ti);
|
|
|
|
/* Call it (catch exceptions) */
|
|
TRY {
|
|
call_dissector_only(protocol_handle, rrc_tvb, pinfo, tree, NULL);
|
|
}
|
|
CATCH_ALL {
|
|
}
|
|
ENDTRY
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/***************************************************/
|
|
/* Unacknowledged mode PDU */
|
|
static void dissect_rlc_lte_um(tvbuff_t *tvb, packet_info *pinfo,
|
|
proto_tree *tree,
|
|
int offset,
|
|
rlc_lte_info *p_rlc_lte_info,
|
|
proto_item *top_ti,
|
|
rlc_lte_tap_info *tap_info)
|
|
{
|
|
guint64 framing_info;
|
|
gboolean first_includes_start;
|
|
gboolean last_includes_end;
|
|
guint64 fixed_extension;
|
|
guint64 sn;
|
|
gint start_offset = offset;
|
|
proto_item *um_ti;
|
|
proto_tree *um_header_tree;
|
|
proto_item *um_header_ti;
|
|
gboolean is_truncated = FALSE;
|
|
proto_item *truncated_ti;
|
|
rlc_channel_reassembly_info *reassembly_info = NULL;
|
|
sequence_analysis_state seq_anal_state = SN_OK;
|
|
|
|
/* Hidden UM root */
|
|
um_ti = proto_tree_add_string_format(tree, hf_rlc_lte_um,
|
|
tvb, offset, 0, "", "UM");
|
|
proto_item_set_hidden(um_ti);
|
|
|
|
/* Add UM header subtree */
|
|
um_header_ti = proto_tree_add_string_format(tree, hf_rlc_lte_um_header,
|
|
tvb, offset, 0,
|
|
"", "UM header");
|
|
um_header_tree = proto_item_add_subtree(um_header_ti,
|
|
ett_rlc_lte_um_header);
|
|
|
|
|
|
/*******************************/
|
|
/* Fixed UM header */
|
|
if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_5_BITS) {
|
|
/* Framing info (2 bits) */
|
|
proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_fi,
|
|
tvb, offset*8, 2,
|
|
&framing_info, ENC_BIG_ENDIAN);
|
|
|
|
/* Extension (1 bit) */
|
|
proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_fixed_e, tvb,
|
|
(offset*8) + 2, 1,
|
|
&fixed_extension, ENC_BIG_ENDIAN);
|
|
|
|
/* Sequence Number (5 bit) */
|
|
proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_sn, tvb,
|
|
(offset*8) + 3, 5,
|
|
&sn, ENC_BIG_ENDIAN);
|
|
offset++;
|
|
}
|
|
else if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_10_BITS) {
|
|
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 (%" PRIu64 ")",
|
|
ack_sn);
|
|
}
|
|
|
|
/* NACK should always be 'behind' the ACK */
|
|
if ((sn_limit + ack_sn - nack_sn) % sn_limit > (sn_limit>>1)) {
|
|
expert_add_info(pinfo, nack_ti, &ei_rlc_lte_am_nack_sn_ahead_ack);
|
|
}
|
|
|
|
/* Copy into struct, but don't exceed buffer */
|
|
if (nack_count < MAX_NACKs) {
|
|
tap_info->NACKs[nack_count++] = (guint16)nack_sn;
|
|
}
|
|
else {
|
|
/* Let it get bigger than the array for accurate stats... */
|
|
nack_count++;
|
|
}
|
|
|
|
/* E1 */
|
|
proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e1, tvb,
|
|
bit_offset, 1, &e1, ENC_BIG_ENDIAN);
|
|
bit_offset++;
|
|
|
|
/* E2 */
|
|
proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e2, tvb,
|
|
bit_offset, 1, &e2, ENC_BIG_ENDIAN);
|
|
|
|
/* Report as expert info */
|
|
if (e2) {
|
|
expert_add_info_format(pinfo, nack_ti, &ei_rlc_lte_am_nack_sn_partial,
|
|
"Status PDU reports NACK (partial) on %s for UE %u",
|
|
val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
|
|
p_rlc_lte_info->ueid);
|
|
}
|
|
else {
|
|
expert_add_info_format(pinfo, nack_ti, &ei_rlc_lte_am_nack_sn,
|
|
"Status PDU reports NACK on %s for UE %u",
|
|
val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
|
|
p_rlc_lte_info->ueid);
|
|
}
|
|
|
|
bit_offset++;
|
|
}
|
|
|
|
if (e2) {
|
|
/* Read SOstart, SOend */
|
|
proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_so_start, tvb,
|
|
bit_offset, so_size, &so_start, ENC_BIG_ENDIAN);
|
|
bit_offset += so_size;
|
|
|
|
proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_so_end, tvb,
|
|
bit_offset, so_size, &so_end, ENC_BIG_ENDIAN);
|
|
bit_offset += so_size;
|
|
|
|
|
|
if ((guint16)so_end == so_end_of_pdu) {
|
|
write_pdu_label_and_info(top_ti, NULL, pinfo,
|
|
" (SOstart=%u SOend=<END-OF_PDU>)",
|
|
(guint16)so_start);
|
|
}
|
|
else {
|
|
write_pdu_label_and_info(top_ti, NULL, pinfo,
|
|
" (SOstart=%u SOend=%u)",
|
|
(guint16)so_start, (guint16)so_end);
|
|
}
|
|
|
|
/* Reset this flag here */
|
|
e2 = 0;
|
|
}
|
|
} while (e1);
|
|
|
|
if (nack_count > 0) {
|
|
proto_item *count_ti = proto_tree_add_uint(tree, hf_rlc_lte_am_nacks, tvb, 0, 1, nack_count);
|
|
proto_item_set_generated(count_ti);
|
|
proto_item_append_text(status_ti, " (%u NACKs)", nack_count);
|
|
tap_info->noOfNACKs = nack_count;
|
|
}
|
|
|
|
/* Check that we've reached the end of the PDU. If not, show malformed */
|
|
offset = (bit_offset+7) / 8;
|
|
if (tvb_reported_length_remaining(tvb, offset) > 0) {
|
|
expert_add_info_format(pinfo, status_ti, &ei_rlc_lte_bytes_after_status_pdu_complete,
|
|
"%cL %u bytes remaining after Status PDU complete",
|
|
(p_rlc_lte_info->direction == DIRECTION_UPLINK) ? 'U' : 'D',
|
|
tvb_reported_length_remaining(tvb, offset));
|
|
}
|
|
|
|
/* Set selected length of control tree */
|
|
proto_item_set_len(status_ti, offset);
|
|
|
|
/* Repeated NACK analysis & check ACK-SN is in range */
|
|
if (((global_rlc_lte_am_sequence_analysis == SEQUENCE_ANALYSIS_MAC_ONLY) &&
|
|
(p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) != NULL)) ||
|
|
((global_rlc_lte_am_sequence_analysis == SEQUENCE_ANALYSIS_RLC_ONLY) &&
|
|
(p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) == NULL))) {
|
|
|
|
if (!is_mac_lte_frame_retx(pinfo, p_rlc_lte_info->direction)) {
|
|
checkChannelRepeatedNACKInfo(pinfo, p_rlc_lte_info, tap_info, tree, tvb);
|
|
checkChannelACKWindow((guint16)ack_sn, pinfo, p_rlc_lte_info, tap_info, tree, tvb);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************/
|
|
/* Acknowledged mode PDU */
|
|
static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
|
|
proto_tree *tree,
|
|
int offset,
|
|
rlc_lte_info *p_rlc_lte_info,
|
|
proto_item *top_ti,
|
|
rlc_lte_tap_info *tap_info)
|
|
{
|
|
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(pinfo->pool, rlc_lte_tap_info);
|
|
|
|
/* Set protocol name */
|
|
col_set_str(pinfo->cinfo, COL_PROTOCOL, "RLC-LTE");
|
|
|
|
/* Create protocol tree. */
|
|
top_ti = proto_tree_add_item(tree, proto_rlc_lte, tvb, offset, -1, ENC_NA);
|
|
rlc_lte_tree = proto_item_add_subtree(top_ti, ett_rlc_lte);
|
|
|
|
|
|
/* Look for packet info! */
|
|
p_rlc_lte_info = (rlc_lte_info *)p_get_proto_data(wmem_file_scope(), pinfo, proto_rlc_lte, 0);
|
|
|
|
/* Can't dissect anything without it... */
|
|
if (p_rlc_lte_info == NULL) {
|
|
proto_tree_add_expert(rlc_lte_tree, pinfo, &ei_rlc_lte_no_per_frame_info, tvb, offset, -1);
|
|
return;
|
|
}
|
|
|
|
/* Clear info column when using UDP framing */
|
|
if (is_udp_framing) {
|
|
col_clear(pinfo->cinfo, COL_INFO);
|
|
}
|
|
|
|
/*****************************************/
|
|
/* Show context information */
|
|
|
|
/* Create context root */
|
|
context_ti = proto_tree_add_string_format(rlc_lte_tree, hf_rlc_lte_context,
|
|
tvb, offset, 0, "", "Context");
|
|
context_tree = proto_item_add_subtree(context_ti, ett_rlc_lte_context);
|
|
proto_item_set_generated(context_ti);
|
|
|
|
ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_direction,
|
|
tvb, 0, 0, p_rlc_lte_info->direction);
|
|
proto_item_set_generated(ti);
|
|
|
|
mode_ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_mode,
|
|
tvb, 0, 0, p_rlc_lte_info->rlcMode);
|
|
proto_item_set_generated(mode_ti);
|
|
|
|
if (p_rlc_lte_info->ueid != 0) {
|
|
ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_ueid,
|
|
tvb, 0, 0, p_rlc_lte_info->ueid);
|
|
proto_item_set_generated(ti);
|
|
}
|
|
|
|
if ((p_rlc_lte_info->priority >= 1) && (p_rlc_lte_info->priority <=16)) {
|
|
ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_priority,
|
|
tvb, 0, 0, p_rlc_lte_info->priority);
|
|
proto_item_set_generated(ti);
|
|
}
|
|
|
|
ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_channel_type,
|
|
tvb, 0, 0, p_rlc_lte_info->channelType);
|
|
proto_item_set_generated(ti);
|
|
|
|
if ((p_rlc_lte_info->channelType == CHANNEL_TYPE_SRB) ||
|
|
(p_rlc_lte_info->channelType == CHANNEL_TYPE_DRB) ||
|
|
(p_rlc_lte_info->channelType == CHANNEL_TYPE_MTCH)) {
|
|
ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_channel_id,
|
|
tvb, 0, 0, p_rlc_lte_info->channelId);
|
|
proto_item_set_generated(ti);
|
|
}
|
|
|
|
ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_pdu_length,
|
|
tvb, 0, 0, p_rlc_lte_info->pduLength);
|
|
proto_item_set_generated(ti);
|
|
|
|
if (p_rlc_lte_info->rlcMode == RLC_UM_MODE) {
|
|
ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_um_sn_length,
|
|
tvb, 0, 0, p_rlc_lte_info->sequenceNumberLength);
|
|
proto_item_set_generated(ti);
|
|
}
|
|
|
|
if (p_rlc_lte_info->rlcMode == RLC_AM_MODE) {
|
|
ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_am_sn_length,
|
|
tvb, 0, 0, p_rlc_lte_info->sequenceNumberLength ?
|
|
p_rlc_lte_info->sequenceNumberLength : 10);
|
|
proto_item_set_generated(ti);
|
|
}
|
|
|
|
/* Append highlights to top-level item */
|
|
if (p_rlc_lte_info->ueid != 0) {
|
|
proto_item_append_text(top_ti, " UEId=%u", p_rlc_lte_info->ueid);
|
|
col_append_fstr(pinfo->cinfo, COL_INFO, "UEId=%-4u ", p_rlc_lte_info->ueid);
|
|
}
|
|
|
|
/* Append context highlights to info column */
|
|
write_pdu_label_and_info(top_ti, NULL, pinfo,
|
|
" [%s] [%s] ",
|
|
(p_rlc_lte_info->direction == 0) ? "UL" : "DL",
|
|
val_to_str_const(p_rlc_lte_info->rlcMode, rlc_mode_short_vals, "Unknown"));
|
|
|
|
if (p_rlc_lte_info->channelId == 0) {
|
|
write_pdu_label_and_info(top_ti, NULL, pinfo, "%s ",
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"));
|
|
}
|
|
else {
|
|
write_pdu_label_and_info(top_ti, NULL, pinfo, "%s:%-2u",
|
|
val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
|
|
p_rlc_lte_info->channelId);
|
|
}
|
|
|
|
/* Set context-info parts of tap struct */
|
|
tap_info->rlcMode = p_rlc_lte_info->rlcMode;
|
|
tap_info->direction = p_rlc_lte_info->direction;
|
|
tap_info->priority = p_rlc_lte_info->priority;
|
|
tap_info->ueid = p_rlc_lte_info->ueid;
|
|
tap_info->channelType = p_rlc_lte_info->channelType;
|
|
tap_info->channelId = p_rlc_lte_info->channelId;
|
|
tap_info->pduLength = p_rlc_lte_info->pduLength;
|
|
tap_info->sequenceNumberLength = p_rlc_lte_info->sequenceNumberLength;
|
|
tap_info->loggedInMACFrame = (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) != NULL);
|
|
|
|
tap_info->rlc_lte_time = pinfo->abs_ts;
|
|
|
|
/* Reset this count */
|
|
s_number_of_extensions = 0;
|
|
|
|
/* Dissect the RLC PDU itself. Format depends upon mode... */
|
|
switch (p_rlc_lte_info->rlcMode) {
|
|
|
|
case RLC_TM_MODE:
|
|
dissect_rlc_lte_tm(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info, top_ti);
|
|
break;
|
|
|
|
case RLC_UM_MODE:
|
|
dissect_rlc_lte_um(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info, top_ti,
|
|
tap_info);
|
|
break;
|
|
|
|
case RLC_AM_MODE:
|
|
dissect_rlc_lte_am(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info, top_ti,
|
|
tap_info);
|
|
break;
|
|
|
|
case RLC_PREDEF:
|
|
/* Predefined data (i.e. not containing a valid RLC header */
|
|
proto_tree_add_item(rlc_lte_tree, hf_rlc_lte_predefined_pdu, tvb, offset, -1, ENC_NA);
|
|
write_pdu_label_and_info(top_ti, NULL, pinfo, " [%u-bytes]",
|
|
tvb_reported_length_remaining(tvb, offset));
|
|
break;
|
|
|
|
default:
|
|
/* Error - unrecognised mode */
|
|
expert_add_info_format(pinfo, mode_ti, &ei_rlc_lte_context_mode,
|
|
"Unrecognised RLC Mode set (%u)", p_rlc_lte_info->rlcMode);
|
|
break;
|
|
}
|
|
|
|
/* Queue tap info */
|
|
tap_queue_packet(rlc_lte_tap, pinfo, tap_info);
|
|
}
|
|
|
|
|
|
|
|
/* Configure number of PDCP SN bits to use for DRB channels */
|
|
void set_rlc_lte_drb_pdcp_seqnum_length(packet_info *pinfo, guint16 ueid, guint8 drbid,
|
|
guint8 userplane_seqnum_length)
|
|
{
|
|
wmem_tree_key_t key[3];
|
|
guint32 id;
|
|
rlc_ue_parameters *params;
|
|
|
|
if (PINFO_FD_VISITED(pinfo)) {
|
|
return;
|
|
}
|
|
|
|
id = (drbid << 16) | ueid;
|
|
key[0].length = 1;
|
|
key[0].key = &id;
|
|
key[1].length = 1;
|
|
key[1].key = &pinfo->num;
|
|
key[2].length = 0;
|
|
key[2].key = NULL;
|
|
|
|
params = (rlc_ue_parameters *)wmem_tree_lookup32_array_le(ue_parameters_tree, key);
|
|
if (params && (params->id != id)) {
|
|
params = NULL;
|
|
}
|
|
if (params == NULL) {
|
|
params = (rlc_ue_parameters *)wmem_new(wmem_file_scope(), rlc_ue_parameters);
|
|
params->id = id;
|
|
params->ext_li_field = NO_EXT_LI;
|
|
wmem_tree_insert32_array(ue_parameters_tree, key, (void *)params);
|
|
}
|
|
params->pdcp_sn_bits = userplane_seqnum_length;
|
|
}
|
|
|
|
/*Configure LI field for AM DRB channels */
|
|
void set_rlc_lte_drb_li_field(packet_info *pinfo, guint16 ueid, guint8 drbid,
|
|
gboolean ul_ext_li_field, gboolean dl_ext_li_field)
|
|
{
|
|
wmem_tree_key_t key[3];
|
|
guint32 id;
|
|
rlc_ue_parameters *params;
|
|
|
|
if (PINFO_FD_VISITED(pinfo)) {
|
|
return;
|
|
}
|
|
|
|
id = (drbid << 16) | ueid;
|
|
key[0].length = 1;
|
|
key[0].key = &id;
|
|
key[1].length = 1;
|
|
key[1].key = &pinfo->num;
|
|
key[2].length = 0;
|
|
key[2].key = NULL;
|
|
|
|
params = (rlc_ue_parameters *)wmem_tree_lookup32_array_le(ue_parameters_tree, key);
|
|
if (params && (params->id != id)) {
|
|
params = NULL;
|
|
}
|
|
if (params == NULL) {
|
|
params = (rlc_ue_parameters *)wmem_new(wmem_file_scope(), rlc_ue_parameters);
|
|
params->id = id;
|
|
params->pdcp_sn_bits = 12;
|
|
wmem_tree_insert32_array(ue_parameters_tree, key, (void *)params);
|
|
}
|
|
params->ext_li_field = ul_ext_li_field ? UL_EXT_LI : NO_EXT_LI;
|
|
params->ext_li_field |= dl_ext_li_field ? DL_EXT_LI : NO_EXT_LI;
|
|
}
|
|
|
|
void proto_register_rlc_lte(void)
|
|
{
|
|
static hf_register_info hf[] =
|
|
{
|
|
/**********************************/
|
|
/* Items for decoding context */
|
|
{ &hf_rlc_lte_context,
|
|
{ "Context",
|
|
"rlc-lte.context", FT_STRING, BASE_NONE, NULL, 0x0,
|
|
NULL, HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_context_mode,
|
|
{ "RLC Mode",
|
|
"rlc-lte.mode", FT_UINT8, BASE_DEC, VALS(rlc_mode_vals), 0x0,
|
|
NULL, HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_context_direction,
|
|
{ "Direction",
|
|
"rlc-lte.direction", FT_UINT8, BASE_DEC, VALS(direction_vals), 0x0,
|
|
"Direction of message", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_context_priority,
|
|
{ "Priority",
|
|
"rlc-lte.priority", FT_UINT8, BASE_DEC, 0, 0x0,
|
|
NULL, HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_context_ueid,
|
|
{ "UEId",
|
|
"rlc-lte.ueid", FT_UINT16, BASE_DEC, 0, 0x0,
|
|
"User Equipment Identifier associated with message", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_context_channel_type,
|
|
{ "Channel Type",
|
|
"rlc-lte.channel-type", FT_UINT16, BASE_DEC, VALS(rlc_channel_type_vals), 0x0,
|
|
"Channel Type associated with message", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_context_channel_id,
|
|
{ "Channel ID",
|
|
"rlc-lte.channel-id", FT_UINT16, BASE_DEC, 0, 0x0,
|
|
"Channel ID associated with message", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_context_pdu_length,
|
|
{ "PDU Length",
|
|
"rlc-lte.pdu-length", FT_UINT16, BASE_DEC, 0, 0x0,
|
|
"Length of PDU (in bytes)", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_context_um_sn_length,
|
|
{ "UM Sequence number length",
|
|
"rlc-lte.um-seqnum-length", FT_UINT8, BASE_DEC, 0, 0x0,
|
|
"Length of UM sequence number in bits", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_context_am_sn_length,
|
|
{ "AM Sequence number length",
|
|
"rlc-lte.am-seqnum-length", FT_UINT8, BASE_DEC, 0, 0x0,
|
|
"Length of AM sequence number in bits", HFILL
|
|
}
|
|
},
|
|
|
|
/* Transparent mode fields */
|
|
{ &hf_rlc_lte_tm,
|
|
{ "TM",
|
|
"rlc-lte.tm", FT_STRING, BASE_NONE, NULL, 0x0,
|
|
"Transparent Mode", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_tm_data,
|
|
{ "TM Data",
|
|
"rlc-lte.tm.data", FT_BYTES, BASE_NONE, 0, 0x0,
|
|
"Transparent Mode Data", HFILL
|
|
}
|
|
},
|
|
|
|
/* Unacknowledged mode fields */
|
|
{ &hf_rlc_lte_um,
|
|
{ "UM",
|
|
"rlc-lte.um", FT_STRING, BASE_NONE, NULL, 0x0,
|
|
"Unacknowledged Mode", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_um_header,
|
|
{ "UM Header",
|
|
"rlc-lte.um.header", FT_STRING, BASE_NONE, NULL, 0x0,
|
|
"Unacknowledged Mode Header", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_um_fi,
|
|
{ "Framing Info",
|
|
"rlc-lte.um.fi", FT_UINT8, BASE_HEX, VALS(framing_info_vals), 0x0,
|
|
NULL, HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_um_fixed_e,
|
|
{ "Extension",
|
|
"rlc-lte.um.fixed.e", FT_UINT8, BASE_HEX, VALS(fixed_extension_vals), 0x0,
|
|
"Extension in fixed part of UM header", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_um_sn,
|
|
{ "Sequence number",
|
|
"rlc-lte.um.sn", FT_UINT8, BASE_DEC, 0, 0x0,
|
|
"Unacknowledged Mode Sequence Number", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_um_fixed_reserved,
|
|
{ "Reserved",
|
|
"rlc-lte.um.reserved", FT_UINT8, BASE_DEC, 0, 0xe0,
|
|
"Unacknowledged Mode Fixed header reserved bits", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_um_data,
|
|
{ "UM Data",
|
|
"rlc-lte.um.data", FT_BYTES, BASE_NONE, 0, 0x0,
|
|
"Unacknowledged Mode Data", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_extension_part,
|
|
{ "Extension Part",
|
|
"rlc-lte.extension-part", FT_STRING, BASE_NONE, 0, 0x0,
|
|
NULL, HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_extension_e,
|
|
{ "Extension",
|
|
"rlc-lte.extension.e", FT_UINT8, BASE_HEX, VALS(extension_extension_vals), 0x0,
|
|
"Extension in extended part of the header", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_extension_li,
|
|
{ "Length Indicator",
|
|
"rlc-lte.extension.li", FT_UINT16, BASE_DEC, 0, 0x0,
|
|
NULL, HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_extension_padding,
|
|
{ "Padding",
|
|
"rlc-lte.extension.padding", FT_UINT8, BASE_HEX, 0, 0x0f,
|
|
"Extension header padding", HFILL
|
|
}
|
|
},
|
|
|
|
/* Acknowledged mode fields */
|
|
{ &hf_rlc_lte_am,
|
|
{ "AM",
|
|
"rlc-lte.am", FT_STRING, BASE_NONE, NULL, 0x0,
|
|
"Acknowledged Mode", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_am_header,
|
|
{ "AM Header",
|
|
"rlc-lte.am.header", FT_STRING, BASE_NONE, NULL, 0x0,
|
|
"Acknowledged Mode Header", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_am_data_control,
|
|
{ "Frame type",
|
|
"rlc-lte.am.frame-type", FT_UINT8, BASE_HEX, VALS(data_or_control_vals), 0x80,
|
|
"AM Frame Type (Control or Data)", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_am_rf,
|
|
{ "Re-segmentation Flag",
|
|
"rlc-lte.am.rf", FT_UINT8, BASE_HEX, VALS(resegmentation_flag_vals), 0x40,
|
|
"AM Re-segmentation Flag", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_am_p,
|
|
{ "Polling Bit",
|
|
"rlc-lte.am.p", FT_UINT8, BASE_HEX, VALS(polling_bit_vals), 0x20,
|
|
NULL, HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_am_fi,
|
|
{ "Framing Info",
|
|
"rlc-lte.am.fi", FT_UINT8, BASE_HEX, VALS(framing_info_vals), 0x18,
|
|
"AM Framing Info", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_am_fixed_e,
|
|
{ "Extension",
|
|
"rlc-lte.am.fixed.e", FT_UINT8, BASE_HEX, VALS(fixed_extension_vals), 0x04,
|
|
"Fixed Extension Bit", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_am_fixed_sn,
|
|
{ "Sequence Number",
|
|
"rlc-lte.am.fixed.sn", FT_UINT16, BASE_DEC, 0, 0x03ff,
|
|
"AM Fixed Sequence Number", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_am_fixed_reserved,
|
|
{ "Reserved",
|
|
"rlc-lte.am.reserved", FT_UINT8, BASE_DEC, 0, 0x03,
|
|
"Acknowledged Mode Fixed header reserved bits", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_am_segment_lsf16,
|
|
{ "Last Segment Flag",
|
|
"rlc-lte.am.segment.lsf", FT_UINT8, BASE_HEX, VALS(lsf_vals), 0x02,
|
|
NULL, HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_am_fixed_reserved2,
|
|
{ "Reserved",
|
|
"rlc-lte.am.reserved", FT_UINT8, BASE_DEC, 0, 0x01,
|
|
"Acknowledged Mode Fixed header reserved bit", HFILL
|
|
}
|
|
},
|
|
{ &hf_rlc_lte_am_fixed_sn16,
|
|
{ "Sequence Number",
|
|
"rlc-lte.am.fixed.sn", FT_UINT16, BASE_DEC, 0, 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:
|
|
*/
|