/* Routines for LTE RLC disassembly * * Martin Mathieson * * $Id$ * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "packet-rlc-lte.h" /* Described in: * 3GPP TS 36.322 Evolved Universal Terrestial Radio Access (E-UTRA) * Radio Link Control (RLC) Protocol specification */ /* TODO: Working towards re-assembly: - track frames within same channel, so can show: - frame link back to start of current segment - show length (and possibly contents) of re-assembled segment - report inconsistent framing info values, i.e. grumble if see for consecutive SNs: - "... [" OR "] ..." */ /* Initialize the protocol and registered fields. */ int proto_rlc_lte = -1; /* Decoding context */ 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; /* Transparent mode fields */ static int hf_rlc_lte_tm_data = -1; /* Unacknowledged mode fields */ 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_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_segment_lsf = -1; static int hf_rlc_lte_am_segment_so = -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_so_start = -1; static int hf_rlc_lte_am_so_end = -1; static int hf_rlc_lte_predefined_pdu = -1; /* Subtrees. */ static int ett_rlc_lte = -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 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"}, { 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, "BCCH"}, { CHANNEL_TYPE_PCCH, "PCCH"}, { CHANNEL_TYPE_SRB, "SRB"}, { CHANNEL_TYPE_DRB, "DRB"}, { 0, NULL } }; static const value_string framing_info_vals[] = { { 0, "First byte begins an RLC SDU and last byte ends an RLC SDU"}, { 1, "First byte begins an RLC SDU and last byte does not end an RLC SDU"}, { 2, "First byte does not begin an RLC SDU and last byte ends an RLC SDU"}, { 3, "First byte does not begin an RLC SDU and last byte does not end an 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, "AND 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 AND 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 } }; /* These are for keeping track of UM/AM extension headers, and the lengths found in them */ guint8 s_number_of_extensions = 0; #define MAX_RLC_SDUS 64 guint16 s_lengths[MAX_RLC_SDUS]; /* Dissect extension headers (common to both UM and AM) */ static int dissect_rlc_lte_extension_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset) { 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; isOdd = (s_number_of_extensions % 2); /* 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); /* 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, FALSE); /* 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, FALSE); proto_item_append_text(extension_part_tree, " (length=%u)", (guint16)length); /* Move on to byte of next extension */ if (isOdd) { offset += 2; } else { offset++; } 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) { guint8 padding; proto_item *ti; padding = tvb_get_guint8(tvb, offset) & 0x0f; ti = proto_tree_add_item(tree, hf_rlc_lte_extension_padding, tvb, offset, 1, FALSE); if (padding != 0) { expert_add_info_format(pinfo, ti, PI_MALFORMED, PI_ERROR, "Extension Header padding not zero (found 0x%x)", padding); } offset++; } 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, guint16 length, gboolean first_includes_start, gboolean last_includes_end) { /* Reflect this PDU in the info column */ if (check_col(pinfo->cinfo, COL_INFO)) { col_append_fstr(pinfo->cinfo, COL_INFO, " %s%u-byte%s%s", (first_includes_start) ? "[" : "..", length, (length > 1) ? "s" : "", (last_includes_end) ? "]" : ".."); } } /***************************************************/ /* 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) { guint64 framing_info; gboolean first_includes_start; gboolean last_includes_end; guint64 fixed_extension; guint64 sn; gint start_offset = offset; proto_tree *um_header_tree; proto_item *um_header_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->UMSequenceNumberLength == 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, FALSE); /* 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, FALSE); /* Sequence Number (5 bit) */ proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_sn, tvb, (offset*8) + 3, 5, &sn, FALSE); offset++; } else if (p_rlc_lte_info->UMSequenceNumberLength == 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, FALSE); if (reserved != 0) { expert_add_info_format(pinfo, ti, PI_MALFORMED, PI_ERROR, "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, FALSE); /* 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, FALSE); /* Sequence Number (10 bits) */ proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_sn, tvb, (offset*8) + 6, 10, &sn, FALSE); offset += 2; } else { /* Invalid length of sequence number */ proto_item *ti; ti = proto_tree_add_text(um_header_tree, tvb, 0, 0, "Invalid sequence number length (%u bits)", p_rlc_lte_info->UMSequenceNumberLength); expert_add_info_format(pinfo, ti, PI_MALFORMED, PI_ERROR, "Invalid sequence number length (%u bits)", p_rlc_lte_info->UMSequenceNumberLength); return; } /* Show SN in info column */ if (check_col(pinfo->cinfo, COL_INFO)) { col_append_fstr(pinfo->cinfo, COL_INFO, " SN=%04u", (guint16)sn); } /* Show SN in UM header root */ proto_item_append_text(um_header_ti, " (SN=%u)", (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, tree, offset); } /* Extract these 2 flags from framing_info */ first_includes_start = ((guint8)framing_info & 0x02) == 0; last_includes_end = ((guint8)framing_info & 0x01) == 0; /*************************************/ /* Data */ if (s_number_of_extensions > 0) { /* Show each data segment separately */ int n; for (n=0; n < s_number_of_extensions; n++) { proto_tree_add_item(tree, hf_rlc_lte_um_data, tvb, offset, s_lengths[n], FALSE); show_PDU_in_info(pinfo, s_lengths[n], (n==0) ? first_includes_start : TRUE, TRUE); tvb_ensure_bytes_exist(tvb, offset, s_lengths[n]); offset += s_lengths[n]; } } /* Final data element */ proto_tree_add_item(tree, hf_rlc_lte_um_data, tvb, offset, -1, FALSE); show_PDU_in_info(pinfo, (guint16)tvb_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, int offset) { guint8 cpt; guint64 ack_sn, nack_sn; 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, FALSE); if (cpt != 0) { /* Protest and stop - only know about STATUS PDUs */ expert_add_info_format(pinfo, ti, PI_MALFORMED, PI_ERROR, "RLC Control frame type %u not handled", cpt); return; } /*****************************************************************/ /* STATUS PDU */ /* The 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, 10, &ack_sn, FALSE); bit_offset += 10; if (check_col(pinfo->cinfo, COL_INFO)) { col_append_fstr(pinfo->cinfo, COL_INFO, " ACK_SN=%u", (guint16)ack_sn); } /* E1 */ proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e1, tvb, bit_offset, 1, &e1, FALSE); /* 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, 10, &nack_sn, FALSE); bit_offset += 10; if (check_col(pinfo->cinfo, COL_INFO)) { col_append_fstr(pinfo->cinfo, COL_INFO, " NACK_SN=%u", (guint16)nack_sn); } expert_add_info_format(pinfo, nack_ti, PI_SEQUENCE, PI_WARN, "Status PDU reports NACK for SN=%u", (guint16)nack_sn); /* E1 */ proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e1, tvb, bit_offset, 1, &e1, FALSE); bit_offset++; /* E2 */ proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e2, tvb, bit_offset, 1, &e2, FALSE); bit_offset++; } if (e2) { /* Read SOstart, SOend */ proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_so_start, tvb, bit_offset, 15, &so_start, FALSE); bit_offset += 15; proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_so_end, tvb, bit_offset, 15, &so_end, FALSE); bit_offset += 15; if (check_col(pinfo->cinfo, COL_INFO)) { col_append_fstr(pinfo->cinfo, COL_INFO, " (SOstart=%u SOend=%u)", (guint16)so_start, (guint16)so_end); if ((guint16)so_end == 0x7fff) { col_append_str(pinfo->cinfo, COL_INFO, " (missing portion reaches end of AMD PDU)"); } } /* Reset this flag here */ e2 = 0; } } while (e1 || e2); } /***************************************************/ /* Acknowledged mode PDU */ static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset) { guint8 is_data; guint8 is_segment; guint8 polling; guint8 fixed_extension; guint8 framing_info; gboolean first_includes_start; gboolean last_includes_end; proto_tree *am_header_tree; proto_item *am_header_ti; gint start_offset = offset; guint16 sn; /* Add UM 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, FALSE); if (check_col(pinfo->cinfo, COL_INFO)) { col_append_str(pinfo->cinfo, COL_INFO, (is_data) ? " [DATA]" : " [CONTROL]"); } /**************************************************/ /* Control PDUs are a completely separate format */ if (!is_data) { dissect_rlc_lte_am_status_pdu(tvb, pinfo, am_header_tree, offset); return; } /******************************/ /* Data PDU fixed header */ /* Re-segmentation Flag (RF) field */ is_segment = (tvb_get_guint8(tvb, offset) & 0x40) >> 6; proto_tree_add_item(am_header_tree, hf_rlc_lte_am_rf, tvb, offset, 1, FALSE); /* 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, FALSE); if (check_col(pinfo->cinfo, COL_INFO)) { col_append_str(pinfo->cinfo, COL_INFO, (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, FALSE); /* 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, FALSE); /* Sequence Number */ sn = tvb_get_ntohs(tvb, offset) & 0x03ff; proto_tree_add_item(am_header_tree, hf_rlc_lte_am_fixed_sn, tvb, offset, 2, FALSE); offset += 2; if (check_col(pinfo->cinfo, COL_INFO)) { col_append_fstr(pinfo->cinfo, COL_INFO, "sn=%u", sn); } /* Show SN in AM header root */ proto_item_append_text(am_header_ti, " (SN=%u)", sn); proto_item_set_len(am_header_ti, offset-start_offset); /***************************************/ /* Dissect extra segment header fields */ if (is_segment) { /* Last Segment Field (LSF) */ proto_tree_add_item(am_header_tree, hf_rlc_lte_am_segment_lsf, tvb, offset, 1, FALSE); /* SO */ proto_tree_add_item(am_header_tree, hf_rlc_lte_am_segment_so, tvb, offset, 2, FALSE); offset += 2; } /*************************************/ /* AM header extension */ if (fixed_extension) { offset = dissect_rlc_lte_extension_header(tvb, pinfo, tree, offset); } /* Extract these 2 flags from framing_info */ first_includes_start = (framing_info & 0x02) == 0; last_includes_end = (framing_info & 0x01) == 0; /*************************************/ /* Data */ if (s_number_of_extensions > 0) { /* Show each data segment separately */ int n; for (n=0; n < s_number_of_extensions; n++) { proto_tree_add_item(tree, hf_rlc_lte_am_data, tvb, offset, s_lengths[n], FALSE); show_PDU_in_info(pinfo, s_lengths[n], (n==0) ? first_includes_start : TRUE, TRUE); tvb_ensure_bytes_exist(tvb, offset, s_lengths[n]); offset += s_lengths[n]; } } /* Final data element */ proto_tree_add_item(tree, hf_rlc_lte_am_data, tvb, offset, -1, FALSE); show_PDU_in_info(pinfo, (guint16)tvb_length_remaining(tvb, offset), (s_number_of_extensions == 0) ? first_includes_start : TRUE, last_includes_end); } /*****************************/ /* Main dissection function. */ /*****************************/ void dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { proto_tree *rlc_lte_tree; proto_item *ti; proto_item *mode_ti; gint offset = 0; struct rlc_lte_info *p_rlc_lte_info = NULL; /* Set protocol name */ if (check_col(pinfo->cinfo, COL_PROTOCOL)) { col_set_str(pinfo->cinfo, COL_PROTOCOL, "RLC-LTE"); } /* Create protocol tree. */ ti = proto_tree_add_item(tree, proto_rlc_lte, tvb, offset, -1, FALSE); rlc_lte_tree = proto_item_add_subtree(ti, ett_rlc_lte); /* Look for packet info! */ p_rlc_lte_info = p_get_proto_data(pinfo->fd, proto_rlc_lte); /* Can't dissect anything without it... */ if (p_rlc_lte_info == NULL) { proto_item *ti = proto_tree_add_text(rlc_lte_tree, tvb, offset, -1, "Can't dissect LTE RLC frame because no per-frame info was attached!"); PROTO_ITEM_SET_GENERATED(ti); return; } /*****************************************/ /* Show context information */ /* TODO: hide inside own tree? */ ti = proto_tree_add_uint(rlc_lte_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(rlc_lte_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(rlc_lte_tree, hf_rlc_lte_context_ueid, tvb, 0, 0, p_rlc_lte_info->ueid); PROTO_ITEM_SET_GENERATED(ti); } ti = proto_tree_add_uint(rlc_lte_tree, hf_rlc_lte_context_priority, tvb, 0, 0, p_rlc_lte_info->priority); PROTO_ITEM_SET_GENERATED(ti); ti = proto_tree_add_uint(rlc_lte_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)) { ti = proto_tree_add_uint(rlc_lte_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(rlc_lte_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(rlc_lte_tree, hf_rlc_lte_context_um_sn_length, tvb, 0, 0, p_rlc_lte_info->UMSequenceNumberLength); PROTO_ITEM_SET_GENERATED(ti); } /* Append context highlights to info column */ if (check_col(pinfo->cinfo, COL_INFO)) { col_add_fstr(pinfo->cinfo, COL_INFO, "[%s] [%s] ", (p_rlc_lte_info->direction == 0) ? "UL" : "DL", val_to_str(p_rlc_lte_info->rlcMode, rlc_mode_short_vals, "Unknown")); if (p_rlc_lte_info->ueid != 0) { col_append_fstr(pinfo->cinfo, COL_INFO, "UEId=%u ", p_rlc_lte_info->ueid); } if (p_rlc_lte_info->channelId == 0) { col_append_fstr(pinfo->cinfo, COL_INFO, "%s", val_to_str(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown")); } else { col_append_fstr(pinfo->cinfo, COL_INFO, "%s:%u", val_to_str(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); } } /* 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: /* Remaining bytes are all data */ proto_tree_add_item(rlc_lte_tree, hf_rlc_lte_tm_data, tvb, offset, -1, FALSE); if (check_col(pinfo->cinfo, COL_INFO)) { col_append_fstr(pinfo->cinfo, COL_INFO, " [%u-bytes]", tvb_length_remaining(tvb, offset)); } break; case RLC_UM_MODE: dissect_rlc_lte_um(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info); break; case RLC_AM_MODE: dissect_rlc_lte_am(tvb, pinfo, rlc_lte_tree, offset); 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, FALSE); break; default: /* Error - unrecognised mode */ expert_add_info_format(pinfo, mode_ti, PI_MALFORMED, PI_ERROR, "Unrecognised RLC Mode set (%u)", p_rlc_lte_info->rlcMode); break; } } void proto_register_rlc_lte(void) { static hf_register_info hf[] = { /**********************************/ /* Items for decoding context */ { &hf_rlc_lte_context_mode, { "RLC Mode", "rlc-lte.mode", FT_UINT8, BASE_DEC, VALS(rlc_mode_vals), 0x0, "RLC Mode", 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, "Priority", 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 } }, /* Transparent mode fields */ { &hf_rlc_lte_tm_data, { "TM Data", "rlc-lte.tm.data", FT_BYTES, BASE_HEX, 0, 0x0, "Transparent Mode Data", HFILL } }, /* Unacknowledged mode fields */ { &hf_rlc_lte_um_header, { "UM Header", "rlc-lte.um.header", FT_STRING, BASE_NONE, NULL, 0x0, "Unackowledged Mode Header", HFILL } }, { &hf_rlc_lte_um_fi, { "Framing Info", "rlc-lte.um.fi", FT_UINT8, BASE_HEX, VALS(framing_info_vals), 0x0, "Framing Info", 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_HEX, 0, 0x0, "Unacknowledged Mode Data", HFILL } }, { &hf_rlc_lte_extension_part, { "Extension Part", "rlc-lte.extension-part", FT_STRING, BASE_NONE, 0, 0x0, "Extension Part", 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, "Length Indicator", HFILL } }, { &hf_rlc_lte_extension_padding, { "Padding", "rlc-lte.extension.padding", FT_UINT8, BASE_HEX, 0, 0x0f, "Extension header padding", HFILL } }, { &hf_rlc_lte_am_header, { "UM Header", "rlc-lte.am.header", FT_STRING, BASE_NONE, NULL, 0x0, "Ackowledged 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, "Polling Bit", 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_HEX, 0, 0x03ff, "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, "Last Segment Flag", HFILL } }, { &hf_rlc_lte_am_segment_so, { "Segment Offset", "rlc-lte.am.segment.offset", FT_UINT16, BASE_DEC, 0, 0x7fff, "Segment Offset", HFILL } }, { &hf_rlc_lte_am_data, { "AM Data", "rlc-lte.am.data", FT_BYTES, BASE_HEX, 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're next expecting to receive", HFILL } }, { &hf_rlc_lte_am_e1, { "Extension bit 1", "rlc-lte.am.e1", FT_UINT8, BASE_HEX, VALS(am_e1_vals), 0x0, "Extension bit 1", HFILL } }, { &hf_rlc_lte_am_e2, { "Extension bit 2", "rlc-lte.am.e2", FT_UINT8, BASE_HEX, VALS(am_e2_vals), 0x0, "Extension bit 2", 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, "SO Start", HFILL } }, { &hf_rlc_lte_am_so_end, { "SO End", "rlc-lte.am.so-end", FT_UINT16, BASE_DEC, 0, 0x0, "SO End", HFILL } }, { &hf_rlc_lte_predefined_pdu, { "Predefined data", "rlc-lte.predefined-data", FT_BYTES, BASE_HEX, 0, 0x0, "Predefined test data", HFILL } }, }; static gint *ett[] = { &ett_rlc_lte, &ett_rlc_lte_um_header, &ett_rlc_lte_am_header, &ett_rlc_lte_extension_part, }; /* 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)); /* Allow other dissectors to find this one by name. */ register_dissector("rlc-lte", dissect_rlc_lte, proto_rlc_lte); }