1419 lines
43 KiB
C
1419 lines
43 KiB
C
/* Routines for UMTS RLC disassembly
|
|
*
|
|
* $Id$
|
|
*
|
|
* Wireshark - Network traffic analyzer
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
* 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 <string.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include <epan/packet.h>
|
|
#include <epan/asn1.h>
|
|
#include <epan/expert.h>
|
|
#include "packet-umts_fp.h"
|
|
#include "packet-umts_mac.h"
|
|
#include "packet-rlc.h"
|
|
#include "packet-rrc.h"
|
|
|
|
/* TODO:
|
|
* - 15 bit Length Identifiers
|
|
* - AM SEQ wrap case
|
|
* - UM/AM 'real' reordering (final packet must appear in-order right now)
|
|
* - decode CW values in RLIST SUFI
|
|
* - decode RESET & RESET ACK
|
|
* - use sub_num in fragment identification?
|
|
*/
|
|
|
|
#define DEBUG_FRAME(number, msg) {if (pinfo->fd->num == number) printf("%u: %s\n", number, msg);}
|
|
|
|
int proto_rlc = -1;
|
|
|
|
extern int proto_fp;
|
|
extern int proto_malformed;
|
|
|
|
/* fields */
|
|
static int hf_rlc_seq = -1;
|
|
static int hf_rlc_ext = -1;
|
|
static int hf_rlc_pad = -1;
|
|
static int hf_rlc_frags = -1;
|
|
static int hf_rlc_frag = -1;
|
|
static int hf_rlc_duplicate_of = -1;
|
|
static int hf_rlc_reassembled_in = -1;
|
|
static int hf_rlc_he = -1;
|
|
static int hf_rlc_dc = -1;
|
|
static int hf_rlc_p = -1;
|
|
static int hf_rlc_li = -1;
|
|
static int hf_rlc_li_value = -1;
|
|
static int hf_rlc_li_ext = -1;
|
|
static int hf_rlc_li_data = -1;
|
|
static int hf_rlc_data = -1;
|
|
static int hf_rlc_ctrl_type = -1;
|
|
static int hf_rlc_sufi = -1;
|
|
static int hf_rlc_sufi_type = -1;
|
|
static int hf_rlc_sufi_lsn = -1;
|
|
static int hf_rlc_sufi_wsn = -1;
|
|
static int hf_rlc_sufi_sn = -1;
|
|
static int hf_rlc_sufi_l = -1;
|
|
static int hf_rlc_sufi_fsn = -1;
|
|
static int hf_rlc_sufi_len = -1;
|
|
static int hf_rlc_sufi_bitmap = -1;
|
|
static int hf_rlc_sufi_cw = -1;
|
|
static int hf_rlc_sufi_n = -1;
|
|
static int hf_rlc_sufi_sn_ack = -1;
|
|
static int hf_rlc_sufi_sn_mrw = -1;
|
|
|
|
/* subtrees */
|
|
static int ett_rlc = -1;
|
|
static int ett_rlc_frag = -1;
|
|
static int ett_rlc_fragments = -1;
|
|
static int ett_rlc_sdu = -1;
|
|
static int ett_rlc_sufi = -1;
|
|
|
|
static dissector_handle_t ip_handle;
|
|
static dissector_handle_t rrc_handle;
|
|
|
|
enum channel_type {
|
|
PCCH,
|
|
UL_CCCH,
|
|
DL_CCCH,
|
|
UL_DCCH,
|
|
DL_DCCH,
|
|
PS_DTCH,
|
|
};
|
|
|
|
static const true_false_string rlc_ext_val = {
|
|
"Next field is Length Indicator and E Bit", "Next field is data, piggybacked STATUS PDU or padding"
|
|
};
|
|
|
|
static const true_false_string rlc_dc_val = {
|
|
"Data", "Control"
|
|
};
|
|
|
|
static const true_false_string rlc_p_val = {
|
|
"Request a status report", "Status report not requested"
|
|
};
|
|
|
|
static const value_string rlc_he_vals[] = {
|
|
{ 0, "The succeeding octet contains data" },
|
|
{ 1, "The succeeding octet contains a length indicator and E bit" },
|
|
{ 2, "The succeeding octet contains data and the last octet of the PDU is the last octet of an SDU" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
#define RLC_STATUS 0x0
|
|
#define RLC_RESET 0x1
|
|
#define RLC_RESET_ACK 0x2
|
|
static const value_string rlc_ctrl_vals[] = {
|
|
{ RLC_STATUS, "STATUS" },
|
|
{ RLC_RESET, "RESET" },
|
|
{ RLC_RESET_ACK, "RESET ACK" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
#define RLC_SUFI_NOMORE 0x0
|
|
#define RLC_SUFI_WINDOW 0x1
|
|
#define RLC_SUFI_ACK 0x2
|
|
#define RLC_SUFI_LIST 0x3
|
|
#define RLC_SUFI_BITMAP 0x4
|
|
#define RLC_SUFI_RLIST 0x5
|
|
#define RLC_SUFI_MRW 0x6
|
|
#define RLC_SUFI_MRW_ACK 0x7
|
|
static const value_string rlc_sufi_vals[] = {
|
|
{ RLC_SUFI_NOMORE, "No more data" },
|
|
{ RLC_SUFI_WINDOW, "Window size" },
|
|
{ RLC_SUFI_ACK, "Acknowledgement" },
|
|
{ RLC_SUFI_LIST, "List" },
|
|
{ RLC_SUFI_BITMAP, "Bitmap" },
|
|
{ RLC_SUFI_RLIST, "Relative list" },
|
|
{ RLC_SUFI_MRW, "Move receiving window" },
|
|
{ RLC_SUFI_MRW_ACK, "Move receiving window acknowledgement" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/* reassembly related data */
|
|
static GHashTable *fragment_table = NULL; /* maps rlc_channel -> fragmented sdu */
|
|
static GHashTable *reassembled_table = NULL; /* maps fragment -> complete sdu */
|
|
static GHashTable *sequence_table = NULL; /* channel -> seq */
|
|
|
|
/* identify an RLC channel, using one of two options:
|
|
* - via Radio Bearer ID and U-RNTI
|
|
* - via Radio Bearer ID and (VPI/VCI/CID) + Link ID
|
|
*/
|
|
struct rlc_channel {
|
|
guint32 urnti;
|
|
guint16 vpi;
|
|
guint16 vci;
|
|
guint8 cid;
|
|
guint16 link; /* link number */
|
|
guint8 rbid; /* radio bearer ID */
|
|
guint8 dir; /* direction */
|
|
|
|
enum rlc_mode mode;
|
|
};
|
|
|
|
/* used for duplicate detection */
|
|
struct rlc_seq {
|
|
guint32 frame_num;
|
|
nstime_t arrival;
|
|
guint16 seq;
|
|
guint16 oc; /* overflow counter */
|
|
};
|
|
|
|
struct rlc_seqlist {
|
|
struct rlc_channel ch;
|
|
|
|
GList *list;
|
|
};
|
|
|
|
/* fragment representation */
|
|
struct rlc_frag {
|
|
guint32 frame_num;
|
|
struct rlc_channel ch;
|
|
guint16 seq; /* RLC sequence number */
|
|
guint16 li; /* LI within current RLC frame */
|
|
guint16 len; /* length of fragment data */
|
|
guint8 *data; /* store fragment data here */
|
|
|
|
struct rlc_frag *next; /* next fragment */
|
|
};
|
|
|
|
struct rlc_sdu {
|
|
tvbuff_t *tvb; /* contains reassembled tvb */
|
|
guint16 len; /* total length of reassembled SDU */
|
|
guint16 fragcnt; /* number of fragments within this SDU */
|
|
guint8 *data; /* reassembled data buffer */
|
|
|
|
struct rlc_frag *reassembled_in;
|
|
struct rlc_frag *frags; /* pointer to list of fragments */
|
|
struct rlc_frag *last; /* pointer to last fragment */
|
|
};
|
|
|
|
struct rlc_li {
|
|
guint16 li; /* original li */
|
|
guint16 len; /* length of this data fragment */
|
|
guint8 ext; /* extension bit value */
|
|
|
|
proto_tree *tree; /* subtree for this LI */
|
|
};
|
|
|
|
/* hashtable functions for fragment table
|
|
* rlc_channel -> SDU
|
|
*/
|
|
static guint rlc_channel_hash(gconstpointer key)
|
|
{
|
|
const struct rlc_channel *ch = key;
|
|
|
|
if (ch->urnti)
|
|
return ch->urnti | ch->rbid | ch->mode;
|
|
|
|
return (ch->vci << 16) | (ch->link << 16) | ch->vpi | ch->vci;
|
|
}
|
|
|
|
static gboolean rlc_channel_equal(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct rlc_channel *x = a, *y = b;
|
|
|
|
if (x->urnti || y->urnti)
|
|
return x->urnti == y->urnti &&
|
|
x->rbid == y->rbid &&
|
|
x->mode == y->mode &&
|
|
x->dir == y->dir ? TRUE : FALSE;
|
|
|
|
return x->vpi == y->vpi &&
|
|
x->vci == y->vci &&
|
|
x->cid == y->cid &&
|
|
x->rbid == y->rbid &&
|
|
x->mode == y->mode &&
|
|
x->dir == y->dir &&
|
|
x->link == y->link ? TRUE : FALSE;
|
|
}
|
|
|
|
static int rlc_channel_assign(struct rlc_channel *ch, enum rlc_mode mode, packet_info *pinfo)
|
|
{
|
|
struct atm_phdr *atm;
|
|
rlc_info *rlcinf;
|
|
fp_info *fpinf;
|
|
|
|
atm = &pinfo->pseudo_header->atm;
|
|
fpinf = p_get_proto_data(pinfo->fd, proto_fp);
|
|
rlcinf = p_get_proto_data(pinfo->fd, proto_rlc);
|
|
if (!fpinf || !rlcinf || !atm) return -1;
|
|
|
|
if (rlcinf->urnti[fpinf->cur_tb]) {
|
|
ch->urnti = rlcinf->urnti[fpinf->cur_tb];
|
|
ch->vpi = ch->vci = ch->link = ch->cid = 0;
|
|
} else {
|
|
ch->urnti = 0;
|
|
ch->vpi = atm->vpi;
|
|
ch->vci = atm->vci;
|
|
ch->cid = atm->aal2_cid;
|
|
ch->link = pinfo->link_number;
|
|
}
|
|
ch->rbid = rlcinf->rbid[fpinf->cur_tb];
|
|
ch->dir = pinfo->p2p_dir;
|
|
ch->mode = mode;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct rlc_channel *rlc_channel_create(enum rlc_mode mode, packet_info *pinfo)
|
|
{
|
|
struct rlc_channel *ch;
|
|
int rv;
|
|
|
|
ch = g_malloc0(sizeof(struct rlc_channel));
|
|
rv = rlc_channel_assign(ch, mode, pinfo);
|
|
|
|
if (rv != 0) {
|
|
/* channel assignment failed */
|
|
g_free(ch);
|
|
ch = NULL;
|
|
}
|
|
return ch;
|
|
}
|
|
|
|
static void rlc_channel_delete(gpointer data)
|
|
{
|
|
g_free(data);
|
|
}
|
|
|
|
/* hashtable functions for reassembled table
|
|
* fragment -> SDU
|
|
*/
|
|
static guint rlc_frag_hash(gconstpointer key)
|
|
{
|
|
const struct rlc_frag *frag = key;
|
|
return rlc_channel_hash(&frag->ch) | frag->li | frag->seq;
|
|
}
|
|
|
|
static gboolean rlc_frag_equal(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct rlc_frag *x = a, *y = b;
|
|
|
|
return rlc_channel_equal(&x->ch, &y->ch) &&
|
|
x->seq == y->seq &&
|
|
x->frame_num == y->frame_num &&
|
|
x->li == y->li ? TRUE : FALSE;
|
|
}
|
|
|
|
|
|
static struct rlc_sdu *rlc_sdu_create(void)
|
|
{
|
|
struct rlc_sdu *sdu;
|
|
sdu = se_alloc0(sizeof(struct rlc_sdu));
|
|
return sdu;
|
|
}
|
|
|
|
static void rlc_frag_delete(gpointer data)
|
|
{
|
|
struct rlc_frag *frag = data;
|
|
if (frag->data) {
|
|
g_free(frag->data);
|
|
frag->data = NULL;
|
|
}
|
|
}
|
|
|
|
static void rlc_sdu_frags_delete(gpointer data)
|
|
{
|
|
struct rlc_sdu *sdu = data;
|
|
struct rlc_frag *frag;
|
|
|
|
frag = sdu->frags;
|
|
while (frag) {
|
|
if (frag->data) {
|
|
g_free(frag->data);
|
|
}
|
|
frag->data = NULL;
|
|
frag = frag->next;
|
|
}
|
|
}
|
|
|
|
static int rlc_frag_assign(struct rlc_frag *frag, enum rlc_mode mode, packet_info *pinfo,
|
|
guint16 seq, guint16 li)
|
|
{
|
|
frag->frame_num = pinfo->fd->num;
|
|
frag->seq = seq;
|
|
frag->li = li;
|
|
frag->len = 0;
|
|
frag->data = NULL;
|
|
rlc_channel_assign(&frag->ch, mode, pinfo);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rlc_frag_assign_data(struct rlc_frag *frag, tvbuff_t *tvb,
|
|
guint16 offset, guint16 length)
|
|
{
|
|
frag->len = length;
|
|
frag->data = g_malloc(length);
|
|
tvb_memcpy(tvb, frag->data, offset, length);
|
|
return 0;
|
|
}
|
|
|
|
static struct rlc_frag *rlc_frag_create(tvbuff_t *tvb, enum rlc_mode mode, packet_info *pinfo,
|
|
guint16 offset, guint16 length, guint16 seq, guint16 li)
|
|
{
|
|
struct rlc_frag *frag;
|
|
frag = se_alloc0(sizeof(struct rlc_frag));
|
|
rlc_frag_assign(frag, mode, pinfo, seq, li);
|
|
rlc_frag_assign_data(frag, tvb, offset, length);
|
|
|
|
return frag;
|
|
}
|
|
|
|
static int rlc_cmp_seq(gconstpointer a, gconstpointer b)
|
|
{
|
|
const struct rlc_seq *_a = a, *_b = b;
|
|
|
|
return _a->seq < _b->seq ? -1 :
|
|
_a->seq > _b->seq ? 1 :
|
|
0;
|
|
}
|
|
|
|
/* callback function to use for g_hash_table_foreach_remove()
|
|
* always return TRUE (=always delete the entry)
|
|
* this is required for backwards compatibility
|
|
* with older versions of glib which do not have
|
|
* a g_hash_table_remove_all() (because of this,
|
|
* hashtables are emptied using g_hash_table_foreach_remove()
|
|
* in conjunction with this funcion)
|
|
*/
|
|
static gboolean free_table_entry(gpointer key _U_,
|
|
gpointer value _U_, gpointer user_data _U_)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
/* "Value destroy" function called each time an entry is removed
|
|
* from the sequence_table hash.
|
|
* It frees the GList pointed to by the entry.
|
|
*/
|
|
static void free_sequence_table_entry_data(gpointer data)
|
|
{
|
|
struct rlc_seqlist *list = data;
|
|
if (list->list != NULL) {
|
|
g_list_free(list->list);
|
|
list->list = NULL; /* for good measure */
|
|
}
|
|
}
|
|
|
|
static void fragment_table_init(void)
|
|
{
|
|
if (fragment_table) {
|
|
g_hash_table_foreach_remove(fragment_table, free_table_entry, NULL);
|
|
g_hash_table_destroy(fragment_table);
|
|
}
|
|
if (reassembled_table) {
|
|
g_hash_table_foreach_remove(reassembled_table, free_table_entry, NULL);
|
|
g_hash_table_destroy(reassembled_table);
|
|
}
|
|
if (sequence_table) {
|
|
/* Note: "value destroy" function wil be called for each removed hash table entry */
|
|
g_hash_table_foreach_remove(sequence_table, free_table_entry, NULL);
|
|
g_hash_table_destroy(sequence_table);
|
|
}
|
|
fragment_table = g_hash_table_new_full(rlc_channel_hash, rlc_channel_equal,
|
|
rlc_channel_delete, rlc_sdu_frags_delete);
|
|
reassembled_table = g_hash_table_new_full(rlc_frag_hash, rlc_frag_equal,
|
|
rlc_frag_delete, rlc_sdu_frags_delete);
|
|
sequence_table = g_hash_table_new_full(rlc_channel_hash, rlc_channel_equal,
|
|
NULL, free_sequence_table_entry_data);
|
|
}
|
|
|
|
/* add the list of fragments for this sdu to 'tree' */
|
|
static void tree_add_fragment_list(struct rlc_sdu *sdu, tvbuff_t *tvb, proto_tree *tree)
|
|
{
|
|
proto_item *ti;
|
|
proto_tree *frag_tree;
|
|
guint16 offset;
|
|
struct rlc_frag *sdufrag;
|
|
ti = proto_tree_add_item(tree, hf_rlc_frags, tvb, 0, -1, FALSE);
|
|
frag_tree = proto_item_add_subtree(ti, ett_rlc_fragments);
|
|
proto_item_append_text(ti, " (%u bytes, %u fragments): ",
|
|
sdu->len, sdu->fragcnt);
|
|
sdufrag = sdu->frags;
|
|
offset = 0;
|
|
while (sdufrag) {
|
|
proto_tree_add_uint_format(frag_tree, hf_rlc_frag, tvb, offset,
|
|
sdufrag->len, sdufrag->frame_num, "Frame: %u, payload %u-%u (%u bytes) (Seq: %u)",
|
|
sdufrag->frame_num, offset, offset + sdufrag->len - 1, sdufrag->len, sdufrag->seq);
|
|
offset += sdufrag->len;
|
|
sdufrag = sdufrag->next;
|
|
}
|
|
}
|
|
|
|
/* add the list of fragments for this sdu to 'tree' */
|
|
static void tree_add_fragment_list_incomplete(struct rlc_sdu *sdu, tvbuff_t *tvb, proto_tree *tree)
|
|
{
|
|
proto_item *ti;
|
|
proto_tree *frag_tree;
|
|
guint16 offset;
|
|
struct rlc_frag *sdufrag;
|
|
ti = proto_tree_add_item(tree, hf_rlc_frags, tvb, 0, 0, FALSE);
|
|
frag_tree = proto_item_add_subtree(ti, ett_rlc_fragments);
|
|
proto_item_append_text(ti, " (%u bytes, %u fragments): ",
|
|
sdu->len, sdu->fragcnt);
|
|
sdufrag = sdu->frags;
|
|
offset = 0;
|
|
while (sdufrag) {
|
|
proto_tree_add_uint_format(frag_tree, hf_rlc_frag, tvb, 0,
|
|
0, sdufrag->frame_num, "Frame: %u, payload %u-%u (%u bytes) (Seq: %u)",
|
|
sdufrag->frame_num, offset, offset + sdufrag->len - 1, sdufrag->len, sdufrag->seq);
|
|
offset += sdufrag->len;
|
|
sdufrag = sdufrag->next;
|
|
}
|
|
}
|
|
|
|
/* add information for an LI to 'tree' */
|
|
static proto_tree *tree_add_li(struct rlc_li *li, guint8 li_idx, guint8 hdr_offs, tvbuff_t *tvb, proto_tree *tree)
|
|
{
|
|
proto_item *ti;
|
|
proto_tree *li_tree;
|
|
guint8 li_offs;
|
|
|
|
if (!tree) return NULL;
|
|
|
|
li_offs = hdr_offs + li_idx;
|
|
|
|
ti = proto_tree_add_item(tree, hf_rlc_li, tvb, li_offs, 1, FALSE);
|
|
li_tree = proto_item_add_subtree(ti, ett_rlc_frag);
|
|
proto_tree_add_bits_item(li_tree, hf_rlc_li_value, tvb, li_offs*8, 7, FALSE);
|
|
proto_tree_add_item(li_tree, hf_rlc_li_ext, tvb, li_offs, 1, FALSE);
|
|
|
|
if (li->len > 0) {
|
|
if (li->li > tvb_length_remaining(tvb, hdr_offs)) return li_tree;
|
|
if (li->len > li->li) return li_tree;
|
|
proto_tree_add_item(li_tree, hf_rlc_li_data, tvb, hdr_offs + li->li - li->len, li->len, FALSE);
|
|
}
|
|
|
|
return li_tree;
|
|
}
|
|
|
|
/* add a fragment to an SDU */
|
|
static int rlc_sdu_add_fragment(enum rlc_mode mode, struct rlc_sdu *sdu,
|
|
struct rlc_frag *frag)
|
|
{
|
|
struct rlc_frag *tmp;
|
|
|
|
if (!sdu->frags) {
|
|
/* insert as first element */
|
|
sdu->frags = frag;
|
|
sdu->last = frag;
|
|
sdu->fragcnt++;
|
|
sdu->len += frag->len;
|
|
return 0;
|
|
}
|
|
switch (mode) {
|
|
case RLC_UM:
|
|
/* insert as last element */
|
|
sdu->last->next = frag;
|
|
frag->next = NULL;
|
|
sdu->last = frag;
|
|
sdu->len += frag->len;
|
|
break;
|
|
case RLC_AM:
|
|
/* insert ordered */
|
|
tmp = sdu->frags;
|
|
if (frag->seq < tmp->seq) {
|
|
/* insert as first element */
|
|
frag->next = tmp;
|
|
sdu->frags = frag;
|
|
} else {
|
|
while (tmp->next && tmp->next->seq < frag->seq)
|
|
tmp = tmp->next;
|
|
frag->next = tmp->next;
|
|
tmp->next = frag;
|
|
if (frag->next == NULL) sdu->last = frag;
|
|
}
|
|
sdu->len += frag->len;
|
|
break;
|
|
default:
|
|
return -2;
|
|
}
|
|
sdu->fragcnt++;
|
|
return 0;
|
|
}
|
|
|
|
static void reassemble_message(struct rlc_channel *ch, struct rlc_sdu *sdu, struct rlc_frag *frag)
|
|
{
|
|
struct rlc_frag *temp;
|
|
guint16 offs = 0;
|
|
|
|
if (!sdu || !ch || !sdu->frags) return;
|
|
|
|
if (sdu->data) return; /* already assembled */
|
|
|
|
if (frag)
|
|
sdu->reassembled_in = frag;
|
|
else
|
|
sdu->reassembled_in = sdu->last;
|
|
|
|
sdu->data = se_alloc(sdu->len);
|
|
|
|
temp = sdu->frags;
|
|
while (temp) {
|
|
memcpy(sdu->data + offs, temp->data, temp->len);
|
|
/* mark this fragment in reassembled table */
|
|
g_hash_table_insert(reassembled_table, temp, sdu);
|
|
|
|
offs += temp->len;
|
|
temp = temp->next;
|
|
}
|
|
g_hash_table_remove(fragment_table, ch);
|
|
}
|
|
|
|
/* add a new fragment to an SDU
|
|
* if length == 0, just finalize the specified SDU
|
|
*/
|
|
static struct rlc_frag *add_fragment(enum rlc_mode mode, tvbuff_t *tvb, packet_info *pinfo,
|
|
proto_tree *tree, guint16 offset, guint16 seq, guint16 num_li, guint16 len, gboolean final)
|
|
{
|
|
struct rlc_channel ch_lookup;
|
|
struct rlc_frag frag_lookup, *frag = NULL, *tmp;
|
|
gpointer orig_frag, orig_sdu;
|
|
struct rlc_sdu *sdu;
|
|
|
|
rlc_channel_assign(&ch_lookup, mode, pinfo);
|
|
rlc_frag_assign(&frag_lookup, mode, pinfo, seq, num_li);
|
|
|
|
/* look for an already assembled SDU */
|
|
if (g_hash_table_lookup_extended(reassembled_table, &frag_lookup,
|
|
&orig_frag, &orig_sdu)) {
|
|
/* this fragment is already reassembled somewhere */
|
|
frag = orig_frag;
|
|
sdu = orig_sdu;
|
|
if (tree) {
|
|
/* mark the fragment, if reassembly happened somewhere else */
|
|
if (frag->seq != sdu->reassembled_in->seq ||
|
|
frag->li != sdu->reassembled_in->li)
|
|
proto_tree_add_uint(tree, hf_rlc_reassembled_in, tvb, 0, 0,
|
|
sdu->reassembled_in->frame_num);
|
|
}
|
|
return frag;
|
|
}
|
|
|
|
/* if not already reassembled, search for a fragment entry */
|
|
sdu = g_hash_table_lookup(fragment_table, &ch_lookup);
|
|
|
|
if (final && len == 0) {
|
|
/* just finish this SDU */
|
|
if (sdu) {
|
|
frag = rlc_frag_create(tvb, mode, pinfo, offset, len, seq, num_li);
|
|
rlc_sdu_add_fragment(mode, sdu, frag);
|
|
reassemble_message(&ch_lookup, sdu, frag);
|
|
}
|
|
return NULL;
|
|
}
|
|
/* create the SDU entry, if it does not already exist */
|
|
if (!sdu) {
|
|
/* this the first observed fragment of an SDU */
|
|
struct rlc_channel *ch;
|
|
ch = rlc_channel_create(mode, pinfo);
|
|
sdu = rlc_sdu_create();
|
|
g_hash_table_insert(fragment_table, ch, sdu);
|
|
}
|
|
|
|
/* check whether we have seen this fragment already */
|
|
tmp = sdu->frags;
|
|
while (tmp) {
|
|
if (rlc_frag_equal(&frag_lookup, tmp) == TRUE)
|
|
return tmp;
|
|
tmp = tmp->next;
|
|
}
|
|
frag = rlc_frag_create(tvb, mode, pinfo, offset, len, seq, num_li);
|
|
rlc_sdu_add_fragment(mode, sdu, frag);
|
|
if (final) {
|
|
reassemble_message(&ch_lookup, sdu, frag);
|
|
}
|
|
return frag;
|
|
}
|
|
|
|
/* is_data is used to identify rlc data parts that are not identified by an LI, but are at the end of
|
|
* the RLC frame
|
|
* these can be valid reassembly points, but only if the LI of the *next* relevant RLC frame is
|
|
* set to '0' (this is indicated in the reassembled SDU
|
|
*/
|
|
static tvbuff_t *get_reassembled_data(enum rlc_mode mode, tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
|
|
guint16 seq, guint16 num_li)
|
|
{
|
|
gpointer orig_frag, orig_sdu;
|
|
struct rlc_sdu *sdu;
|
|
struct rlc_frag lookup, *frag;
|
|
|
|
rlc_frag_assign(&lookup, mode, pinfo, seq, num_li);
|
|
|
|
if (!g_hash_table_lookup_extended(reassembled_table, &lookup,
|
|
&orig_frag, &orig_sdu))
|
|
return NULL;
|
|
|
|
frag = orig_frag;
|
|
sdu = orig_sdu;
|
|
if (!sdu || !sdu->data)
|
|
return NULL;
|
|
|
|
/* TODO */
|
|
#if 0
|
|
if (!rlc_frag_equal(&lookup, sdu->reassembled_in)) return NULL;
|
|
#endif
|
|
|
|
if (tree) {
|
|
frag = sdu->frags;
|
|
while (frag->next) {
|
|
if (frag->next->seq - frag->seq > 1) {
|
|
proto_item *pi = proto_tree_add_text(tree, tvb, 0, 0,
|
|
"Error: Incomplete sequence");
|
|
PROTO_ITEM_SET_GENERATED(pi);
|
|
tree_add_fragment_list_incomplete(sdu, tvb, tree);
|
|
return NULL;
|
|
}
|
|
frag = frag->next;
|
|
}
|
|
}
|
|
sdu->tvb = tvb_new_real_data(sdu->data, sdu->len, sdu->len);
|
|
tvb_set_child_real_data_tvbuff(tvb, sdu->tvb);
|
|
add_new_data_source(pinfo, sdu->tvb, "Reassembled RLC Message");
|
|
|
|
/* reassembly happened here, so create the fragment list */
|
|
if (tree)
|
|
tree_add_fragment_list(sdu, sdu->tvb, tree);
|
|
|
|
return sdu->tvb;
|
|
}
|
|
|
|
#define RLC_RETRANSMISSION_TIMEOUT 5 /* in seconds */
|
|
static gboolean rlc_is_duplicate(enum rlc_mode mode, packet_info *pinfo, guint16 seq, guint32 *original)
|
|
{
|
|
GList *element;
|
|
struct rlc_seqlist lookup, *list;
|
|
struct rlc_seq seq_item, *seq_new;
|
|
|
|
rlc_channel_assign(&lookup.ch, mode, pinfo);
|
|
list = g_hash_table_lookup(sequence_table, &lookup.ch);
|
|
if (!list) {
|
|
/* we see this channel for the first time */
|
|
list = se_alloc0(sizeof(*list));
|
|
rlc_channel_assign(&list->ch, mode, pinfo);
|
|
g_hash_table_insert(sequence_table, &list->ch, list);
|
|
}
|
|
seq_item.seq = seq;
|
|
seq_item.frame_num = pinfo->fd->num;
|
|
|
|
element = g_list_find_custom(list->list, &seq_item, rlc_cmp_seq);
|
|
if (element) {
|
|
seq_new = element->data;
|
|
if (seq_new->frame_num != seq_item.frame_num) {
|
|
nstime_t delta;
|
|
nstime_delta(&delta, &pinfo->fd->abs_ts, &seq_new->arrival);
|
|
if (delta.secs < RLC_RETRANSMISSION_TIMEOUT) {
|
|
if (original)
|
|
*original = seq_new->frame_num;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
return FALSE; /* we revisit the seq that was already seen */
|
|
}
|
|
seq_new = se_alloc0(sizeof(struct rlc_seq));
|
|
*seq_new = seq_item;
|
|
seq_new->arrival = pinfo->fd->abs_ts;
|
|
list->list = g_list_insert_sorted(list->list, seq_new, rlc_cmp_seq);
|
|
return FALSE;
|
|
}
|
|
|
|
static void rlc_call_subdissector(enum channel_type channel, tvbuff_t *tvb,
|
|
packet_info *pinfo, proto_tree *tree)
|
|
{
|
|
enum rrc_message_type msgtype;
|
|
|
|
switch (channel) {
|
|
case UL_CCCH:
|
|
msgtype = RRC_MESSAGE_TYPE_UL_CCCH;
|
|
break;
|
|
case DL_CCCH:
|
|
msgtype = RRC_MESSAGE_TYPE_DL_CCCH;
|
|
break;
|
|
case UL_DCCH:
|
|
msgtype = RRC_MESSAGE_TYPE_UL_DCCH;
|
|
break;
|
|
case DL_DCCH:
|
|
msgtype = RRC_MESSAGE_TYPE_DL_DCCH;
|
|
break;
|
|
case PCCH:
|
|
msgtype = RRC_MESSAGE_TYPE_PCCH;
|
|
break;
|
|
case PS_DTCH:
|
|
msgtype = RRC_MESSAGE_TYPE_INVALID;
|
|
/* assume transparent PDCP for now */
|
|
call_dissector(ip_handle, tvb, pinfo, tree);
|
|
break;
|
|
default:
|
|
return; /* abort */
|
|
}
|
|
if (msgtype != RRC_MESSAGE_TYPE_INVALID) {
|
|
struct rrc_info *rrcinf;
|
|
fp_info *fpinf;
|
|
fpinf = p_get_proto_data(pinfo->fd, proto_fp);
|
|
rrcinf = p_get_proto_data(pinfo->fd, proto_rrc);
|
|
if (!rrcinf) {
|
|
rrcinf = se_alloc0(sizeof(struct rrc_info));
|
|
p_add_proto_data(pinfo->fd, proto_rrc, rrcinf);
|
|
}
|
|
rrcinf->msgtype[fpinf->cur_tb] = msgtype;
|
|
call_dissector(rrc_handle, tvb, pinfo, tree);
|
|
/* once the packet has been dissected, protect it from further changes */
|
|
col_set_writable(pinfo->cinfo, FALSE);
|
|
}
|
|
}
|
|
|
|
static void dissect_rlc_tm(enum channel_type channel, tvbuff_t *tvb, packet_info *pinfo,
|
|
proto_tree *top_level, proto_tree *tree _U_)
|
|
{
|
|
rlc_call_subdissector(channel, tvb, pinfo, top_level);
|
|
}
|
|
|
|
|
|
static void rlc_um_reassemble(tvbuff_t *tvb, guint8 offs, packet_info *pinfo, proto_tree *tree,
|
|
proto_tree *top_level, enum channel_type channel, guint16 seq, struct rlc_li *li, guint16 num_li)
|
|
{
|
|
guint8 i;
|
|
gboolean dissected = FALSE;
|
|
tvbuff_t *next_tvb = NULL;
|
|
/* perform reassembly now */
|
|
for (i = 0; i < num_li; i++) {
|
|
switch (li[i].li) {
|
|
case 0x7f: /* padding, must be last LI */
|
|
if (tree)
|
|
proto_tree_add_item(tree, hf_rlc_pad, tvb, offs, -1, FALSE);
|
|
offs += tvb_length_remaining(tvb, offs);
|
|
break;
|
|
case 0x7c: /* a new SDU starts here */
|
|
break; /* nothing to do, really */
|
|
case 0x00: /* previous segment was the last of an SDU */
|
|
default:
|
|
add_fragment(RLC_UM, tvb, pinfo, li[i].tree, offs, seq, i, li[i].len, TRUE);
|
|
next_tvb = get_reassembled_data(RLC_UM, tvb, pinfo, li[i].tree, seq, i);
|
|
}
|
|
if (next_tvb) {
|
|
dissected = TRUE;
|
|
rlc_call_subdissector(channel, next_tvb, pinfo, top_level);
|
|
next_tvb = NULL;
|
|
}
|
|
offs += li[i].len;
|
|
}
|
|
|
|
/* is there data left? */
|
|
if (tvb_length_remaining(tvb, offs) > 0) {
|
|
if (tree) {
|
|
proto_tree_add_item(tree, hf_rlc_data, tvb, offs, -1, FALSE);
|
|
}
|
|
/* add remaining data as fragment */
|
|
add_fragment(RLC_UM, tvb, pinfo, tree, offs, seq, i, tvb_length_remaining(tvb, offs), FALSE);
|
|
if (dissected == FALSE && check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "[RLC UM Fragment]");
|
|
}
|
|
}
|
|
|
|
static gint16 rlc_decode_li(enum rlc_mode mode, tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
|
|
struct rlc_li *li, guint8 max_li)
|
|
{
|
|
guint8 ext, next_byte, hdr_len, offs = 0, num_li = 0, li_offs;
|
|
guint16 prev_li = 0;
|
|
proto_item *malformed;
|
|
guint16 total_len;
|
|
|
|
switch (mode) {
|
|
case RLC_AM: offs = 1; break;
|
|
case RLC_UM: offs = 0; break;
|
|
case RLC_TM: return -1;
|
|
}
|
|
hdr_len = offs;
|
|
/* calculate header length */
|
|
ext = tvb_get_guint8(tvb, offs) & 0x01;
|
|
while (ext) {
|
|
next_byte = tvb_get_guint8(tvb, hdr_len);
|
|
ext = next_byte & 0x01;
|
|
hdr_len++;
|
|
}
|
|
total_len = tvb_length_remaining(tvb, hdr_len);
|
|
|
|
/* do actual evaluation of LIs */
|
|
ext = tvb_get_guint8(tvb, offs++) & 0x01;
|
|
li_offs = offs;
|
|
while (ext) {
|
|
next_byte = tvb_get_guint8(tvb, offs++);
|
|
ext = next_byte & 0x01;
|
|
li[num_li].ext = ext;
|
|
li[num_li].li = next_byte >> 1;
|
|
|
|
switch (li[num_li].li) {
|
|
case 0x00: /* previous segment was the last one */
|
|
case 0x7e: /* contains piggybacked STATUS */
|
|
case 0x7f: /* padding */
|
|
li[num_li].len = 0;
|
|
break;
|
|
case 0x7c: /* start of a new PDU, UM only */
|
|
case 0x7d: /* contains exactly one PDU, UM only */
|
|
if (mode == RLC_UM) {
|
|
/* valid for UM */
|
|
li[num_li].len = 0;
|
|
break;
|
|
}
|
|
/*invalid for AM */
|
|
/* add malformed LI for investigation */
|
|
tree_add_li(&li[num_li], num_li, li_offs, tvb, tree);
|
|
malformed = proto_tree_add_protocol_format(tree,
|
|
proto_malformed, tvb, 0, 0, "[Malformed Packet: %s]", pinfo->current_proto);
|
|
expert_add_info_format(pinfo, malformed, PI_MALFORMED, PI_ERROR,
|
|
"Malformed Packet (Uses reserved LI)");
|
|
if (check_col(pinfo->cinfo, COL_INFO))
|
|
col_append_str(pinfo->cinfo, COL_INFO, "[Malformed Packet]");
|
|
return -1; /* just give up on this */
|
|
default:
|
|
/* since the LI is an offset (from the end of the header), it
|
|
* may not be larger than the total remaining length and no
|
|
* LI may be smaller than its preceding one
|
|
*/
|
|
if (li[num_li].li > total_len || li[num_li].li < prev_li) {
|
|
/* add malformed LI for investigation */
|
|
tree_add_li(&li[num_li], num_li, li_offs, tvb, tree);
|
|
malformed = proto_tree_add_protocol_format(tree,
|
|
proto_malformed, tvb, 0, 0, "[Malformed Packet: %s]", pinfo->current_proto);
|
|
expert_add_info_format(pinfo, malformed, PI_MALFORMED, PI_ERROR,
|
|
"Malformed Packet (incorrect LI value)");
|
|
if (check_col(pinfo->cinfo, COL_INFO))
|
|
col_append_str(pinfo->cinfo, COL_INFO, "[Malformed Packet]");
|
|
return -1; /* just give up on this */
|
|
}
|
|
li[num_li].len = li[num_li].li - prev_li;
|
|
prev_li = li[num_li].li;
|
|
}
|
|
li[num_li].tree = tree_add_li(&li[num_li], num_li, li_offs, tvb, tree);
|
|
num_li++;
|
|
|
|
if (num_li > max_li) {
|
|
/* OK, so this is not really a malformed packet, but for now,
|
|
* we will treat it as such, so that it is marked in some way */
|
|
malformed = proto_tree_add_protocol_format(tree,
|
|
proto_malformed, tvb, 0, 0, "[Dissector Problem: %s]", pinfo->current_proto);
|
|
expert_add_info_format(pinfo, malformed, PI_MALFORMED, PI_ERROR,
|
|
"Too many LI entries");
|
|
return -1;
|
|
}
|
|
}
|
|
return num_li;
|
|
}
|
|
|
|
static void dissect_rlc_um(enum channel_type channel, tvbuff_t *tvb, packet_info *pinfo,
|
|
proto_tree *top_level, proto_tree *tree)
|
|
{
|
|
#define MAX_LI 16
|
|
struct rlc_li li[MAX_LI];
|
|
fp_info *fpinf;
|
|
rlc_info *rlcinf;
|
|
guint32 orig_num;
|
|
guint8 seq, ext;
|
|
guint8 next_byte, offs = 0;
|
|
gint16 pos, num_li = 0;
|
|
|
|
next_byte = tvb_get_guint8(tvb, offs++);
|
|
seq = next_byte >> 1;
|
|
ext = next_byte & 0x01;
|
|
|
|
/* show sequence number and extension bit */
|
|
if (tree) {
|
|
proto_tree_add_bits_item(tree, hf_rlc_seq, tvb, 0, 7, FALSE);
|
|
proto_tree_add_item(tree, hf_rlc_ext, tvb, 0, 1, FALSE);
|
|
}
|
|
|
|
fpinf = p_get_proto_data(pinfo->fd, proto_fp);
|
|
rlcinf = p_get_proto_data(pinfo->fd, proto_rlc);
|
|
if (!fpinf || !rlcinf) {
|
|
proto_tree_add_text(tree, tvb, 0, -1,
|
|
"Cannot dissect RLC frame because per-frame info is missing");
|
|
return;
|
|
}
|
|
pos = fpinf->cur_tb;
|
|
if (rlcinf->ciphered[pos] == TRUE && rlcinf->deciphered[pos] == FALSE) {
|
|
proto_tree_add_text(tree, tvb, 0, -1,
|
|
"Cannot dissect RLC frame because it is ciphered");
|
|
return;
|
|
}
|
|
|
|
num_li = rlc_decode_li(RLC_UM, tvb, pinfo, tree, li, MAX_LI);
|
|
if (num_li == -1) return; /* something went wrong */
|
|
offs += num_li;
|
|
|
|
|
|
/* do not detect duplicates or reassemble, if prefiltering is done */
|
|
if (pinfo->fd->num == 0) return;
|
|
/* check for duplicates */
|
|
if (rlc_is_duplicate(RLC_UM, pinfo, seq, &orig_num) == TRUE) {
|
|
if (check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "[RLC UM Fragment] [Duplicate]");
|
|
proto_tree_add_uint(tree, hf_rlc_duplicate_of, tvb, 0, 0, orig_num);
|
|
return;
|
|
}
|
|
rlc_um_reassemble(tvb, offs, pinfo, tree, top_level, channel, seq, li, num_li);
|
|
}
|
|
|
|
static void dissect_rlc_status(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, guint8 offset)
|
|
{
|
|
guint8 sufi_type, len;
|
|
gint bit_offset;
|
|
proto_tree *sufi_tree;
|
|
proto_item *sufi_item, *malformed;
|
|
|
|
bit_offset = offset*8 + 4; /* first SUFI type is always 4 bit shifted */
|
|
|
|
while (tvb_length_remaining(tvb, bit_offset/8) > 0) {
|
|
sufi_type = tvb_get_bits8(tvb, bit_offset, 4);
|
|
sufi_item = proto_tree_add_item(tree, hf_rlc_sufi, tvb, 0, 0, FALSE);
|
|
sufi_tree = proto_item_add_subtree(sufi_item, ett_rlc_sufi);
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_type, tvb, bit_offset, 4, FALSE);
|
|
bit_offset += 4;
|
|
switch (sufi_type) {
|
|
case RLC_SUFI_NOMORE:
|
|
return; /* must be last SUFI */
|
|
case RLC_SUFI_ACK:
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_lsn, tvb, bit_offset, 12, FALSE);
|
|
return; /* must be last SUFI */
|
|
case RLC_SUFI_WINDOW:
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_wsn, tvb, bit_offset, 12, FALSE);
|
|
bit_offset += 12;
|
|
break;
|
|
case RLC_SUFI_LIST:
|
|
len = tvb_get_bits8(tvb, bit_offset, 4);
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_len, tvb, bit_offset, 4, FALSE);
|
|
bit_offset += 4;
|
|
while (len) {
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_sn, tvb, bit_offset, 12, FALSE);
|
|
bit_offset += 12;
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_l, tvb, bit_offset, 4, FALSE);
|
|
bit_offset += 4;
|
|
len--;
|
|
}
|
|
break;
|
|
case RLC_SUFI_BITMAP:
|
|
len = tvb_get_bits8(tvb, bit_offset, 4);
|
|
len++; /* bitmap is len + 1 */
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_len, tvb, bit_offset, 4, FALSE);
|
|
bit_offset += 4;
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_fsn, tvb, bit_offset, 12, FALSE);
|
|
bit_offset += 12;
|
|
proto_tree_add_item(sufi_tree, hf_rlc_sufi_bitmap, tvb, bit_offset/8, len, FALSE);
|
|
bit_offset += len*8;
|
|
break;
|
|
case RLC_SUFI_RLIST:
|
|
len = tvb_get_bits8(tvb, bit_offset, 4);
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_len, tvb, bit_offset, 4, FALSE);
|
|
bit_offset += 4;
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_fsn, tvb, bit_offset, 12, FALSE);
|
|
bit_offset += 12;
|
|
while (len) {
|
|
/* TODO: decode CW values */
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_cw, tvb, bit_offset, 4, FALSE);
|
|
bit_offset += 4;
|
|
len--;
|
|
}
|
|
break;
|
|
case RLC_SUFI_MRW_ACK:
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_n, tvb, bit_offset, 4, FALSE);
|
|
bit_offset += 4;
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_sn_ack, tvb, bit_offset, 12, FALSE);
|
|
bit_offset += 12;
|
|
break;
|
|
case RLC_SUFI_MRW:
|
|
len = tvb_get_bits8(tvb, bit_offset, 4);
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_len, tvb, bit_offset, 4, FALSE);
|
|
bit_offset += 4;
|
|
|
|
while (len) {
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_sn_mrw, tvb, bit_offset, 12, FALSE);
|
|
bit_offset += 12;
|
|
len--;
|
|
}
|
|
proto_tree_add_bits_item(sufi_tree, hf_rlc_sufi_n, tvb, bit_offset, 4, FALSE);
|
|
bit_offset += 4;
|
|
break;
|
|
default:
|
|
malformed = proto_tree_add_protocol_format(tree,
|
|
proto_malformed, tvb, 0, 0, "[Malformed Packet: %s]", pinfo->current_proto);
|
|
expert_add_info_format(pinfo, malformed, PI_MALFORMED, PI_ERROR,
|
|
"Malformed Packet (invalid SUFI type)");
|
|
if (check_col(pinfo->cinfo, COL_INFO))
|
|
col_append_str(pinfo->cinfo, COL_INFO, " [Malformed Packet]");
|
|
return; /* invalid value, ignore the rest */
|
|
}
|
|
}
|
|
}
|
|
|
|
static void dissect_rlc_control(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
|
|
{
|
|
guint8 type, next_byte;
|
|
proto_item *malformed;
|
|
|
|
next_byte = tvb_get_guint8(tvb, 0);
|
|
type = (next_byte >> 4) & 0x07;
|
|
|
|
proto_tree_add_uint(tree, hf_rlc_ctrl_type, tvb, 0, 1, next_byte);
|
|
switch (type) {
|
|
case RLC_STATUS:
|
|
dissect_rlc_status(tvb, pinfo, tree, 0);
|
|
break;
|
|
case RLC_RESET:
|
|
case RLC_RESET_ACK:
|
|
/* TODO */
|
|
break;
|
|
default:
|
|
malformed = proto_tree_add_protocol_format(tree,
|
|
proto_malformed, tvb, 0, 0, "[Malformed Packet: %s]", pinfo->current_proto);
|
|
expert_add_info_format(pinfo, malformed, PI_MALFORMED, PI_ERROR,
|
|
"Malformed Packet (invalid RLC AM control type %u)", type);
|
|
if (check_col(pinfo->cinfo, COL_INFO))
|
|
col_append_str(pinfo->cinfo, COL_INFO, " [Malformed Packet]");
|
|
return; /* invalid */
|
|
}
|
|
}
|
|
|
|
static void rlc_am_reassemble(tvbuff_t *tvb, guint8 offs, packet_info *pinfo, proto_tree *tree,
|
|
proto_tree *top_level, enum channel_type channel, guint16 seq, struct rlc_li *li, guint16 num_li,
|
|
gboolean final)
|
|
{
|
|
guint8 i;
|
|
gboolean piggyback = FALSE, dissected = FALSE;
|
|
tvbuff_t *next_tvb = NULL;
|
|
/* perform reassembly now */
|
|
for (i = 0; i < num_li; i++) {
|
|
switch (li[i].li) {
|
|
case 0x7e: /* piggybacked status */
|
|
piggyback = TRUE;
|
|
break;
|
|
case 0x7f: /* padding, must be last LI */
|
|
if (tree && tvb_length_remaining(tvb, offs) > 0)
|
|
proto_tree_add_item(tree, hf_rlc_pad, tvb, offs, -1, FALSE);
|
|
offs += tvb_length_remaining(tvb, offs);
|
|
break;
|
|
case 0x0: /* previous segment was the last one */
|
|
default:
|
|
add_fragment(RLC_AM, tvb, pinfo, li[i].tree, offs, seq, i, li[i].len, TRUE);
|
|
next_tvb = get_reassembled_data(RLC_AM, tvb, pinfo, li[i].tree, seq, i);
|
|
}
|
|
if (next_tvb) {
|
|
dissected = TRUE;
|
|
rlc_call_subdissector(channel, next_tvb, pinfo, top_level);
|
|
next_tvb = NULL;
|
|
}
|
|
offs += li[i].len;
|
|
}
|
|
|
|
if (piggyback) {
|
|
dissect_rlc_status(tvb, pinfo, tree, offs);
|
|
} else {
|
|
if (tvb_length_remaining(tvb, offs) > 0) {
|
|
/* we have remaining data, which we need to mark in the tree */
|
|
if (tree) {
|
|
proto_tree_add_item(tree, hf_rlc_data, tvb, offs, -1, FALSE);
|
|
}
|
|
add_fragment(RLC_AM, tvb, pinfo, tree, offs, seq, i,
|
|
tvb_length_remaining(tvb,offs), final);
|
|
if (final) {
|
|
next_tvb = get_reassembled_data(RLC_AM, tvb, pinfo, NULL, seq, i);
|
|
}
|
|
}
|
|
if (next_tvb) {
|
|
dissected = TRUE;
|
|
rlc_call_subdissector(channel, next_tvb, pinfo, top_level);
|
|
next_tvb = NULL;
|
|
}
|
|
}
|
|
if (dissected == FALSE && check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "[RLC AM Fragment]");
|
|
}
|
|
|
|
static void dissect_rlc_am(enum channel_type channel, tvbuff_t *tvb, packet_info *pinfo,
|
|
proto_tree *top_level, proto_tree *tree)
|
|
{
|
|
#define MAX_LI 16
|
|
struct rlc_li li[MAX_LI];
|
|
fp_info *fpinf;
|
|
rlc_info *rlcinf;
|
|
guint8 ext, dc;
|
|
guint8 next_byte, offs = 0;
|
|
guint32 orig_num = 0;
|
|
gint16 num_li = 0, seq, pos;
|
|
|
|
next_byte = tvb_get_guint8(tvb, offs++);
|
|
dc = next_byte >> 7;
|
|
if (tree)
|
|
proto_tree_add_item(tree, hf_rlc_dc, tvb, 0, 1, FALSE);
|
|
if (dc == 0) {
|
|
if (check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "RLC Control Frame");
|
|
dissect_rlc_control(tvb, pinfo, tree);
|
|
return;
|
|
}
|
|
|
|
seq = next_byte & 0x7f;
|
|
seq <<= 5;
|
|
next_byte = tvb_get_guint8(tvb, offs++);
|
|
seq |= (next_byte >> 3);
|
|
|
|
ext = next_byte & 0x03;
|
|
/* show header fields */
|
|
if (tree) {
|
|
proto_tree_add_bits_item(tree, hf_rlc_seq, tvb, 1, 12, FALSE);
|
|
proto_tree_add_item(tree, hf_rlc_p, tvb, 1, 1, FALSE);
|
|
proto_tree_add_bits_item(tree, hf_rlc_he, tvb, 14, 2, FALSE);
|
|
}
|
|
|
|
/* header extension may only be 00 or 01 */
|
|
if (ext > 2) {
|
|
proto_item *malformed;
|
|
malformed = proto_tree_add_protocol_format(tree,
|
|
proto_malformed, tvb, 0, 0, "[Malformed Packet: %s]", pinfo->current_proto);
|
|
expert_add_info_format(pinfo, malformed, PI_MALFORMED, PI_ERROR,
|
|
"Malformed Packet (incorrect HE value)");
|
|
if (check_col(pinfo->cinfo, COL_INFO))
|
|
col_append_str(pinfo->cinfo, COL_INFO, "[Malformed Packet]");
|
|
return;
|
|
}
|
|
|
|
fpinf = p_get_proto_data(pinfo->fd, proto_fp);
|
|
rlcinf = p_get_proto_data(pinfo->fd, proto_rlc);
|
|
if (!fpinf || !rlcinf) {
|
|
proto_tree_add_text(tree, tvb, 0, -1,
|
|
"Cannot dissect RLC frame because per-frame info is missing");
|
|
return;
|
|
}
|
|
pos = fpinf->cur_tb;
|
|
if (rlcinf->ciphered[pos] == TRUE && rlcinf->deciphered[pos] == FALSE) {
|
|
proto_tree_add_text(tree, tvb, 0, -1,
|
|
"Cannot dissect RLC frame because it is ciphered");
|
|
return;
|
|
}
|
|
|
|
num_li = rlc_decode_li(RLC_AM, tvb, pinfo, tree, li, MAX_LI);
|
|
if (num_li == -1) return; /* something went wrong */
|
|
offs += num_li;
|
|
|
|
/* do not detect duplicates or reassemble, if prefiltering is done */
|
|
if (pinfo->fd->num == 0) return;
|
|
/* check for duplicates */
|
|
if (rlc_is_duplicate(RLC_AM, pinfo, seq, &orig_num) == TRUE) {
|
|
if (check_col(pinfo->cinfo, COL_INFO))
|
|
col_set_str(pinfo->cinfo, COL_INFO, "[RLC AM Fragment] [Duplicate]");
|
|
proto_tree_add_uint(tree, hf_rlc_duplicate_of, tvb, 0, 0, orig_num);
|
|
return;
|
|
}
|
|
rlc_am_reassemble(tvb, offs, pinfo, tree, top_level, channel, seq, li, num_li,
|
|
ext == 2);
|
|
}
|
|
|
|
/* dissect entry functions */
|
|
static void dissect_rlc_pcch(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
|
|
{
|
|
proto_tree *subtree = NULL;
|
|
if (check_col(pinfo->cinfo, COL_PROTOCOL))
|
|
col_set_str(pinfo->cinfo, COL_PROTOCOL, "RLC");
|
|
if (check_col(pinfo->cinfo, COL_INFO))
|
|
col_clear(pinfo->cinfo, COL_INFO);
|
|
|
|
/* PCCH is always RLC UM */
|
|
if (tree) {
|
|
proto_item *ti;
|
|
ti = proto_tree_add_item(tree, proto_rlc, tvb, 0, -1, FALSE);
|
|
subtree = proto_item_add_subtree(ti, ett_rlc);
|
|
proto_item_append_text(ti, " TM (PCCH)");
|
|
}
|
|
dissect_rlc_tm(PCCH, tvb, pinfo, tree, subtree);
|
|
}
|
|
|
|
static void dissect_rlc_ccch(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
|
|
{
|
|
fp_info *fpi;
|
|
proto_item *ti = NULL;
|
|
proto_tree *subtree = NULL;
|
|
|
|
if (check_col(pinfo->cinfo, COL_PROTOCOL))
|
|
col_set_str(pinfo->cinfo, COL_PROTOCOL, "RLC");
|
|
if (check_col(pinfo->cinfo, COL_INFO))
|
|
col_clear(pinfo->cinfo, COL_INFO);
|
|
|
|
fpi = p_get_proto_data(pinfo->fd, proto_fp);
|
|
if (!fpi) return; /* dissection failure */
|
|
|
|
if (tree) {
|
|
ti = proto_tree_add_item(tree, proto_rlc, tvb, 0, -1, FALSE);
|
|
subtree = proto_item_add_subtree(ti, ett_rlc);
|
|
}
|
|
|
|
if (fpi->is_uplink) {
|
|
/* UL CCCH is always RLC TM */
|
|
proto_item_append_text(ti, " TM (CCCH)");
|
|
dissect_rlc_tm(UL_CCCH, tvb, pinfo, tree, subtree);
|
|
} else {
|
|
/* DL CCCH is always UM */
|
|
proto_item_append_text(ti, " UM (CCCH)");
|
|
dissect_rlc_um(DL_CCCH, tvb, pinfo, tree, subtree);
|
|
}
|
|
}
|
|
|
|
static void dissect_rlc_dcch(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
|
|
{
|
|
proto_item *ti = NULL;
|
|
proto_tree *subtree = NULL;
|
|
fp_info *fpi;
|
|
rlc_info *rlci;
|
|
enum channel_type channel;
|
|
|
|
if (check_col(pinfo->cinfo, COL_PROTOCOL))
|
|
col_set_str(pinfo->cinfo, COL_PROTOCOL, "RLC");
|
|
if (check_col(pinfo->cinfo, COL_INFO))
|
|
col_clear(pinfo->cinfo, COL_INFO);
|
|
|
|
fpi = p_get_proto_data(pinfo->fd, proto_fp);
|
|
rlci = p_get_proto_data(pinfo->fd, proto_rlc);
|
|
|
|
if (!fpi || !rlci) return;
|
|
|
|
if (tree) {
|
|
ti = proto_tree_add_item(tree, proto_rlc, tvb, 0, -1, FALSE);
|
|
subtree = proto_item_add_subtree(ti, ett_rlc);
|
|
}
|
|
|
|
channel = fpi->is_uplink ? UL_DCCH : DL_DCCH;
|
|
|
|
switch (rlci->mode[fpi->cur_tb]) {
|
|
case RLC_UM:
|
|
proto_item_append_text(ti, " UM (DCCH)");
|
|
dissect_rlc_um(channel, tvb, pinfo, tree, subtree);
|
|
break;
|
|
case RLC_AM:
|
|
proto_item_append_text(ti, " AM (DCCH)");
|
|
dissect_rlc_am(channel, tvb, pinfo, tree, subtree);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void dissect_rlc_ps_dtch(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
|
|
{
|
|
proto_item *ti = NULL;
|
|
proto_tree *subtree = NULL;
|
|
fp_info *fpi;
|
|
rlc_info *rlci;
|
|
|
|
if (check_col(pinfo->cinfo, COL_PROTOCOL))
|
|
col_set_str(pinfo->cinfo, COL_PROTOCOL, "RLC");
|
|
if (check_col(pinfo->cinfo, COL_INFO))
|
|
col_clear(pinfo->cinfo, COL_INFO);
|
|
|
|
fpi = p_get_proto_data(pinfo->fd, proto_fp);
|
|
rlci = p_get_proto_data(pinfo->fd, proto_rlc);
|
|
|
|
if (!fpi || !rlci) return;
|
|
|
|
if (tree) {
|
|
ti = proto_tree_add_item(tree, proto_rlc, tvb, 0, -1, FALSE);
|
|
subtree = proto_item_add_subtree(ti, ett_rlc);
|
|
}
|
|
|
|
switch (rlci->mode[fpi->cur_tb]) {
|
|
case RLC_UM:
|
|
proto_item_append_text(ti, " UM (PS DTCH)");
|
|
dissect_rlc_um(PS_DTCH, tvb, pinfo, tree, subtree);
|
|
break;
|
|
case RLC_AM:
|
|
proto_item_append_text(ti, " AM (PS DTCH)");
|
|
dissect_rlc_am(PS_DTCH, tvb, pinfo, tree, subtree);
|
|
break;
|
|
case RLC_TM:
|
|
proto_item_append_text(ti, " TM (PS DTCH)");
|
|
dissect_rlc_tm(PS_DTCH, tvb, pinfo, tree, subtree);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
proto_register_rlc(void)
|
|
{
|
|
static hf_register_info hf[] = {
|
|
{ &hf_rlc_dc, { "D/C Bit", "rlc.dc", FT_BOOLEAN, 8, TFS(&rlc_dc_val), 0x80, NULL, HFILL } },
|
|
{ &hf_rlc_ctrl_type, { "Control PDU Type", "rlc.ctrl_pdu_type", FT_UINT8, BASE_DEC, VALS(rlc_ctrl_vals), 0x70, "PDU Type", HFILL } },
|
|
{ &hf_rlc_seq, { "Sequence Number", "rlc.seq", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_ext, { "Extension Bit", "rlc.ext", FT_BOOLEAN, BASE_DEC, TFS(&rlc_ext_val), 0x01, NULL, HFILL } },
|
|
{ &hf_rlc_he, { "Header Extension Type", "rlc.he", FT_UINT8, BASE_DEC, VALS(rlc_he_vals), 0, NULL, HFILL } },
|
|
{ &hf_rlc_p, { "Polling Bit", "rlc.p", FT_BOOLEAN, 8, TFS(&rlc_p_val), 0x04, NULL, HFILL } },
|
|
{ &hf_rlc_pad, { "Padding", "rlc.padding", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_rlc_frags, { "Reassembled Fragments", "rlc.fragments", FT_NONE, BASE_NONE, NULL, 0, "Fragments", HFILL } },
|
|
{ &hf_rlc_frag, { "RLC Fragment", "rlc.fragment", FT_FRAMENUM, BASE_NONE, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_duplicate_of, { "Duplicate of", "rlc.duplicate_of", FT_FRAMENUM, BASE_NONE, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_reassembled_in, { "Reassembled Message in frame", "rlc.reassembled_in", FT_FRAMENUM, BASE_NONE, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_data, { "Data", "rlc.data", FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } },
|
|
/* LI information */
|
|
{ &hf_rlc_li, { "LI", "rlc.li", FT_NONE, BASE_NONE, NULL, 0, "Length Indicator", HFILL } },
|
|
{ &hf_rlc_li_value, { "LI value", "rlc.li.value", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_li_ext, { "LI extension bit", "rlc.li.ext", FT_BOOLEAN, BASE_DEC, TFS(&rlc_ext_val), 0x01, NULL, HFILL } },
|
|
{ &hf_rlc_li_data, { "LI Data", "rlc.li.data", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
/* SUFI information */
|
|
{ &hf_rlc_sufi, { "SUFI", "rlc.sufi", FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_sufi_type, { "SUFI Type", "rlc.sufi.type", FT_UINT8, BASE_DEC, VALS(rlc_sufi_vals), 0, NULL, HFILL } },
|
|
{ &hf_rlc_sufi_lsn, { "LSN", "rlc.sufi.lsn", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_sufi_wsn, { "WSN", "rlc.sufi.wsn", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_sufi_sn, { "SN", "rlc.sufi.sn", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_sufi_l, { "L", "rlc.sufi.l", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_sufi_len, { "Length", "rlc.sufi.len", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_sufi_fsn, { "FSN", "rlc.sufi.fsn", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_sufi_bitmap, { "Bitmap", "rlc.sufi.bitmap", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } },
|
|
{ &hf_rlc_sufi_cw, { "CW", "rlc.sufi.cw", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_sufi_n, { "N", "rlc.sufi.n", FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_sufi_sn_ack, { "SN ACK", "rlc.sufi.sn_ack", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
|
|
{ &hf_rlc_sufi_sn_mrw, { "SN MRW", "rlc.sufi.sn_mrw", FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
|
|
};
|
|
static gint *ett[] = {
|
|
&ett_rlc,
|
|
&ett_rlc_frag,
|
|
&ett_rlc_fragments,
|
|
&ett_rlc_sdu,
|
|
&ett_rlc_sufi,
|
|
};
|
|
proto_rlc = proto_register_protocol("RLC", "RLC", "rlc");
|
|
register_dissector("rlc.pcch", dissect_rlc_pcch, proto_rlc);
|
|
register_dissector("rlc.ccch", dissect_rlc_ccch, proto_rlc);
|
|
register_dissector("rlc.dcch", dissect_rlc_dcch, proto_rlc);
|
|
register_dissector("rlc.ps_dtch", dissect_rlc_ps_dtch, proto_rlc);
|
|
|
|
proto_register_field_array(proto_rlc, hf, array_length(hf));
|
|
proto_register_subtree_array(ett, array_length(ett));
|
|
|
|
register_init_routine(fragment_table_init);
|
|
}
|
|
|
|
void
|
|
proto_reg_handoff_rlc(void)
|
|
{
|
|
rrc_handle = find_dissector("rrc");
|
|
ip_handle = find_dissector("ip");
|
|
}
|