/* Routines for UMTS RLC disassembly * * $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 #include #include #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" }, { 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 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 */ 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_item *ti; ti = 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) { 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_item *ti; ti = 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), FALSE); } } 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 > 1) { 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 (!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); } /* 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"); }