Don't attach sequence analysis results to control/data PDUs when not appropriate...
[obnox/wireshark/wip.git] / epan / dissectors / packet-rlc-lte.c
index 6b25dc06c537ae7f7525e2072fff36f99d7da752..d7ceb111c2a75cb9866539c38b14e6ef5a5528ec 100644 (file)
 #include <epan/packet.h>
 #include <epan/expert.h>
 #include <epan/prefs.h>
+#include <epan/tap.h>
 
+#include "packet-mac-lte.h"
 #include "packet-rlc-lte.h"
+#include "packet-pdcp-lte.h"
 
 
 /* Described in:
  */
 
 /* TODO:
-   - AM sequence analysis/re-assembly?
+   - UM & AM re-assembly?
 */
 
+/********************************/
+/* Preference settings          */
 
-/* By default try to analyse the sequence of messages for UM channels */
-static gboolean global_rlc_lte_sequence_analysis = TRUE;
+#define SEQUENCE_ANALYSIS_MAC_ONLY 1
+#define SEQUENCE_ANALYSIS_RLC_ONLY 2
 
+/* By default don't try to analyse the sequence of messages for AM/UM channels */
+static gint global_rlc_lte_am_sequence_analysis = FALSE;
+static gint global_rlc_lte_um_sequence_analysis = FALSE;
 
+/* By default don't call PDCP/RRC dissectors for SDU data */
+static gboolean global_rlc_lte_call_pdcp_for_srb = FALSE;
 
+enum pdcp_for_drb { PDCP_drb_off, PDCP_drb_SN_7, PDCP_drb_SN_12, PDCP_drb_SN_signalled};
+static enum_val_t pdcp_drb_col_vals[] = {
+    {"pdcp-drb-off",   "Off",       PDCP_drb_off},
+    {"pdcp-drb-sn-7",  "7-bit SN",  PDCP_drb_SN_7},
+    {"pdcp-drb-sn-12", "12-bit SN", PDCP_drb_SN_12},
+    {"pdcp-drb-sn-signalling", "Use signalled value", PDCP_drb_SN_signalled},
+    {NULL, NULL, -1}
+};
+static gint global_rlc_lte_call_pdcp_for_drb = (gint)PDCP_drb_off;
+static gint signalled_pdcp_sn_bits = 12;
+
+static gboolean global_rlc_lte_call_rrc = FALSE;
+
+/* Preference to expect RLC headers without payloads */
+static gboolean global_rlc_lte_headers_expected = FALSE;
+
+/* Heuristic dissection */
+static gboolean global_rlc_lte_heur = FALSE;
+
+
+/**************************************************/
 /* Initialize the protocol and registered fields. */
 int proto_rlc_lte = -1;
 
+extern int proto_mac_lte;
+extern int proto_pdcp_lte;
+
+static int rlc_lte_tap = -1;
+
 /* Decoding context */
+static int hf_rlc_lte_context = -1;
 static int hf_rlc_lte_context_mode = -1;
 static int hf_rlc_lte_context_direction = -1;
 static int hf_rlc_lte_context_priority = -1;
@@ -65,9 +102,11 @@ 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 = -1;
 static int hf_rlc_lte_tm_data = -1;
 
 /* Unacknowledged mode fields */
+static int hf_rlc_lte_um = -1;
 static int hf_rlc_lte_um_header = -1;
 static int hf_rlc_lte_um_fi = -1;
 static int hf_rlc_lte_um_fixed_e = -1;
@@ -83,6 +122,7 @@ static int hf_rlc_lte_extension_padding = -1;
 
 
 /* Acknowledged mode fields */
+static int hf_rlc_lte_am = -1;
 static int hf_rlc_lte_am_header = -1;
 static int hf_rlc_lte_am_data_control = -1;
 static int hf_rlc_lte_am_rf = -1;
@@ -100,26 +140,40 @@ static int hf_rlc_lte_am_ack_sn = -1;
 static int hf_rlc_lte_am_e1 = -1;
 static int hf_rlc_lte_am_e2 = -1;
 static int hf_rlc_lte_am_nack_sn = -1;
+static int hf_rlc_lte_am_nacks = -1;
 static int hf_rlc_lte_am_so_start = -1;
 static int hf_rlc_lte_am_so_end = -1;
 
 static int hf_rlc_lte_predefined_pdu = -1;
+static int hf_rlc_lte_header_only = -1;
 
 /* Sequence Analysis */
 static int hf_rlc_lte_sequence_analysis = -1;
+static int hf_rlc_lte_sequence_analysis_ok = -1;
 static int hf_rlc_lte_sequence_analysis_previous_frame = -1;
 static int hf_rlc_lte_sequence_analysis_expected_sn = -1;
 static int hf_rlc_lte_sequence_analysis_framing_info_correct = -1;
 
+static int hf_rlc_lte_sequence_analysis_mac_retx = -1;
+static int hf_rlc_lte_sequence_analysis_retx = -1;
+static int hf_rlc_lte_sequence_analysis_repeated = -1;
+static int hf_rlc_lte_sequence_analysis_skipped = -1;
+
+static int hf_rlc_lte_sequence_analysis_repeated_nack = -1;
+static int hf_rlc_lte_sequence_analysis_repeated_nack_original_frame = -1;
+
+static int hf_rlc_lte_sequence_analysis_ack_out_of_range = -1;
+static int hf_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame = -1;
 
 /* Subtrees. */
 static int ett_rlc_lte = -1;
+static int ett_rlc_lte_context = -1;
 static int ett_rlc_lte_um_header = -1;
 static int ett_rlc_lte_am_header = -1;
 static int ett_rlc_lte_extension_part = -1;
 static int ett_rlc_lte_sequence_analysis = -1;
 
-
+/* Value-strings */
 static const value_string direction_vals[] =
 {
     { DIRECTION_UPLINK,      "Uplink"},
@@ -144,24 +198,23 @@ static const value_string rlc_mode_vals[] =
     { 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"},
+    { CHANNEL_TYPE_CCCH,         "CCCH"},
+    { CHANNEL_TYPE_BCCH_BCH,     "BCCH_BCH"},
+    { CHANNEL_TYPE_PCCH,         "PCCH"},
+    { CHANNEL_TYPE_SRB,          "SRB"},
+    { CHANNEL_TYPE_DRB,          "DRB"},
+    { CHANNEL_TYPE_BCCH_DL_SCH,  "BCCH_DL_SCH"},
     { 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,      "First byte begins a RLC SDU and last byte ends a RLC SDU"},
+    { 1,      "First byte begins a RLC SDU and last byte does not end a RLC SDU"},
+    { 2,      "First byte does not begin a RLC SDU and last byte ends a RLC SDU"},
+    { 3,      "First byte does not begin a RLC SDU and last byte does not end a RLC SDU"},
     { 0, NULL }
 };
 
@@ -189,7 +242,7 @@ static const value_string data_or_control_vals[] =
 static const value_string resegmentation_flag_vals[] =
 {
     { 0,      "AMD PDU"},
-    { 1,      "AND PDU segment"},
+    { 1,      "AMD PDU segment"},
     { 0, NULL }
 };
 
@@ -200,15 +253,13 @@ static const value_string polling_bit_vals[] =
     { 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"},
+    { 1,      "Last byte of the AMD PDU segment corresponds to the last byte of an AMD PDU"},
     { 0, NULL }
 };
 
-
 static const value_string control_pdu_type_vals[] =
 {
     { 0,      "STATUS PDU"},
@@ -229,18 +280,135 @@ static const value_string am_e2_vals[] =
     { 0, NULL }
 };
 
+static const value_string header_only_vals[] =
+{
+    { 0,      "RLC PDU Headers and body present"},
+    { 1,      "RLC PDU Headers only"},
+    { 0, NULL }
+};
+
 
 
 /**********************************************************************************/
 /* These are for keeping track of UM/AM extension headers, and the lengths found  */
 /* in them                                                                        */
-guint8  s_number_of_extensions = 0;
+static guint8  s_number_of_extensions = 0;
 #define MAX_RLC_SDUS 64
-guint16 s_lengths[MAX_RLC_SDUS];
+static guint16 s_lengths[MAX_RLC_SDUS];
+
+
+/*********************************************************************/
+/* UM/AM sequence analysis                                           */
+
+/* Types for RLC channel hash table                                   */
+/* This table is maintained during initial dissection of RLC          */
+/* frames, mapping from rlc_channel_hash_key -> rlc_channel_status    */
+
+/* Channel key */
+typedef struct
+{
+    guint16  ueId;
+    guint16  channelType;
+    guint16  channelId;
+    guint8   direction;
+} rlc_channel_hash_key;
+
+/* Conversation-type status for sequence analysis on channel */
+typedef struct
+{
+    guint8   rlcMode;
+
+    /* For UM, we always expect the SN to keep advancing, and these fields
+       keep track of this.
+       For AM, these correspond to new data */
+    guint16  previousSequenceNumber;
+    guint32  previousFrameNum;
+    gboolean previousSegmentIncomplete;
+} rlc_channel_sequence_analysis_status;
+
+/* The sequence analysis channel hash table */
+static GHashTable *rlc_lte_sequence_analysis_channel_hash = NULL;
+
+
+/* Types for sequence analysis frame report hash table                  */
+/* This is a table from framenum -> state_report_in_frame               */
+/* This is necessary because the per-packet info is already being used  */
+/* for context information before the dissector is called               */
+
+/* Info to attach to frame when first read, recording what to show about sequence */
+typedef struct
+{
+    gboolean  sequenceExpectedCorrect;
+    guint16   sequenceExpected;
+    guint32   previousFrameNum;
+    gboolean  previousSegmentIncomplete;
+
+    guint16   firstSN;
+    guint16   lastSN;
+
+    /* AM/UM */
+    enum { SN_OK, SN_Repeated, SN_MAC_Retx, SN_Retx, SN_Missing, ACK_Out_of_Window} state;
+} state_sequence_analysis_report_in_frame;
+
+
+/* The sequence analysis frame report hash table instance itself   */
+static GHashTable *rlc_lte_frame_sequence_analysis_report_hash = NULL;
+
+
+/******************************************************************/
+/* Conversation-type status for repeated NACK checking on channel */
+typedef struct
+{
+    guint16         noOfNACKs;
+    guint16         NACKs[MAX_NACKs];
+    guint32         frameNum;
+} rlc_channel_repeated_nack_status;
+
+static GHashTable *rlc_lte_repeated_nack_channel_hash = NULL;
+
+typedef struct {
+    guint16         noOfNACKsRepeated;
+    guint16         repeatedNACKs[MAX_NACKs];
+    guint32         previousFrameNum;
+} rlc_channel_repeated_nack_report_in_frame;
+
+static GHashTable *rlc_lte_frame_repeated_nack_report_hash = NULL;
+
+
+
+/********************************************************/
+/* Forward declarations & functions                     */
+static void dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);
+
+
+/* Write the given formatted text to:
+   - the info column
+   - the top-level RLC PDU item
+   - another subtree item (if supplied) */
+static void write_pdu_label_and_info(proto_item *pdu_ti, proto_item *sub_ti,
+                                     packet_info *pinfo, const char *format, ...)
+{
+    #define MAX_INFO_BUFFER 256
+    static char info_buffer[MAX_INFO_BUFFER];
+
+    va_list ap;
+
+    va_start(ap, format);
+    g_vsnprintf(info_buffer, MAX_INFO_BUFFER, format, ap);
+    va_end(ap);
+
+    /* Add to indicated places */
+    col_append_str(pinfo->cinfo, COL_INFO, info_buffer);
+    proto_item_append_text(pdu_ti, "%s", info_buffer);
+    if (sub_ti != NULL) {
+        proto_item_append_text(sub_ti, "%s", info_buffer);
+    }
+}
+
 
 
 /* Dissect extension headers (common to both UM and AM) */
-static int dissect_rlc_lte_extension_header(tvbuff_t *tvb, packet_info *pinfo,
+static int dissect_rlc_lte_extension_header(tvbuff_t *tvb, packet_info *pinfo _U_,
                                             proto_tree *tree,
                                             int offset)
 {
@@ -268,13 +436,13 @@ static int dissect_rlc_lte_extension_header(tvbuff_t *tvb, packet_info *pinfo,
 
         /* Read next extension */
         proto_tree_add_bits_ret_val(extension_part_tree, hf_rlc_lte_extension_e, tvb,
-                                   (offset*8) + ((isOdd) ? 4 : 0),
+                                    (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),
+                                    (offset*8) + ((isOdd) ? 5 : 1),
                                     11,
                                     &length, FALSE);
 
@@ -293,17 +461,8 @@ static int dissect_rlc_lte_extension_header(tvbuff_t *tvb, packet_info *pinfo,
     /* 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++;
+        proto_tree_add_item(tree, hf_rlc_lte_extension_padding,
+                            tvb, offset++, 1, FALSE);
     }
 
     return offset;
@@ -313,46 +472,98 @@ static int dissect_rlc_lte_extension_header(tvbuff_t *tvb, packet_info *pinfo,
 /* Show in the info column how many bytes are in the UM/AM PDU, and indicate
    whether or not the beginning and end are included in this packet */
 static void show_PDU_in_info(packet_info *pinfo,
+                             proto_item *top_ti,
                              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) ? "]" : "..");
-    }
+    write_pdu_label_and_info(top_ti, NULL, pinfo,
+                             "  %s%u-byte%s%s",
+                             (first_includes_start) ? "[" : "..",
+                             length,
+                             (length > 1) ? "s" : "",
+                             (last_includes_end) ? "]" : "..");
 }
 
 
+/* Show an SDU.  If configured, pass to PDCP dissector */
+static void show_PDU_in_tree(packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, gint offset, gint length,
+                             rlc_lte_info *rlc_info, gboolean whole_pdu)
+{
+    /* Add raw data (according to mode) */
+    proto_item *data_ti = proto_tree_add_item(tree,
+                                              (rlc_info->rlcMode == RLC_AM_MODE) ?
+                                                    hf_rlc_lte_am_data :
+                                                    hf_rlc_lte_um_data,
+                                              tvb, offset, length, FALSE);
+
+    /* Decode signalling PDUs as PDCP */
+    if (whole_pdu &&
+        (((global_rlc_lte_call_pdcp_for_srb) && (rlc_info->channelType == CHANNEL_TYPE_SRB)) ||
+         ((global_rlc_lte_call_pdcp_for_drb != PDCP_drb_off) && (rlc_info->channelType == CHANNEL_TYPE_DRB)))) {
+
+        /* Attempt to decode payload using LTE PDCP dissector */
+        tvbuff_t *pdcp_tvb = tvb_new_subset(tvb, offset, length, length);
+        volatile dissector_handle_t protocol_handle;
+
+        struct pdcp_lte_info *p_pdcp_lte_info;
+
+        /* Reuse or allocate struct */
+        p_pdcp_lte_info = p_get_proto_data(pinfo->fd, proto_pdcp_lte);
+        if (p_pdcp_lte_info == NULL) {
+            p_pdcp_lte_info = se_alloc0(sizeof(struct pdcp_lte_info));
+            /* Store info in packet */
+            p_add_proto_data(pinfo->fd, proto_pdcp_lte, p_pdcp_lte_info);
+        }
 
-/*********************************************************************/
-/* UM/AM sequence analysis                                           */
+        p_pdcp_lte_info->ueid = rlc_info->ueid;
+        p_pdcp_lte_info->channelType = Channel_DCCH;
+        p_pdcp_lte_info->channelId = rlc_info->channelId;
+        p_pdcp_lte_info->direction = rlc_info->direction;
 
-/* Types for RLC channel hash table                                   */
-/* This table is maintained during initial dissection of RLC          */
-/* frames, mapping from rlc_channel_hash_key -> rlc_channel_status    */
+        /* Set plane and sequnce number length */
+        p_pdcp_lte_info->no_header_pdu = FALSE;
+        if (rlc_info->channelType == CHANNEL_TYPE_SRB) {
+            p_pdcp_lte_info->plane = SIGNALING_PLANE;
+            p_pdcp_lte_info->seqnum_length = 5;
+        }
+        else {
+            p_pdcp_lte_info->plane = USER_PLANE;
+            switch (global_rlc_lte_call_pdcp_for_drb) {
+                case PDCP_drb_SN_7:
+                    p_pdcp_lte_info->seqnum_length = 7;
+                    break;
+                case PDCP_drb_SN_12:
+                    p_pdcp_lte_info->seqnum_length = 12;
+                    break;
+                case PDCP_drb_SN_signalled:
+                    /* Use whatever was signalled (e.g. in RRC) */
+                    p_pdcp_lte_info->seqnum_length = signalled_pdcp_sn_bits;
+                    break;
+
+                default:
+                    DISSECTOR_ASSERT(FALSE);
+                    break;
+            }
+        }
 
-/* Channel key */
-typedef struct
-{
-    guint16  ueId;
-    guint16  channelType;
-    guint16  channelId;
-    guint8   direction;
-} rlc_channel_hash_key;
+        p_pdcp_lte_info->rohc_compression = FALSE;
 
-/* Conversation-type status for channel */
-typedef struct
-{
-    guint16  previousSequenceNumber;
-    guint32  previousFrameNum;
-    gboolean previousSegmentIncomplete;
-} rlc_channel_status;
 
+        /* Get dissector handle */
+        protocol_handle = find_dissector("pdcp-lte");
+
+        TRY {
+            call_dissector_only(protocol_handle, pdcp_tvb, pinfo, tree);
+        }
+        CATCH_ALL {
+        }
+        ENDTRY
+
+        PROTO_ITEM_SET_HIDDEN(data_ti);
+    }
+}
 
 /* Hash table functions for RLC channels */
 
@@ -378,25 +589,6 @@ static guint rlc_channel_hash_func(gconstpointer v)
     return ((val1->ueId * 1024) + (val1->channelType*64) + (val1->channelId*2) + val1->direction);
 }
 
-/* The channel hash table instance itself        */
-static GHashTable *rlc_lte_channel_hash = NULL;
-
-
-
-
-/* Types for frame report hash table                                    */
-/* This is a table from framenum -> state_report_in_frame               */
-/* This is necessary because the per-packet info is already being used  */
-/* for conext information before the dissector is called                */
-
-/* Info to attach to frame when first read, recording what to show about sequence */
-typedef struct
-{
-    guint8  sequenceExpectedCorrect;
-    guint16 sequenceExpected;
-    guint32 previousFrameNum;
-    guint8  previousSegmentIncomplete;
-} state_report_in_frame;
 
 
 /* Hash table functions for frame reports */
@@ -413,15 +605,15 @@ static guint rlc_frame_hash_func(gconstpointer v)
     return GPOINTER_TO_UINT(v);
 }
 
-/* The frame report hash table instance itself   */
-static GHashTable *rlc_lte_frame_report_hash = NULL;
-
 
 
 /* Add to the tree values associated with sequence analysis for this frame */
-static void addChannelSequenceInfo(state_report_in_frame *p,
-                                   guint16 sequenceNumber,
+static void addChannelSequenceInfo(state_sequence_analysis_report_in_frame *p,
+                                   gboolean isControlFrame,
+                                   rlc_lte_info *p_rlc_lte_info,
+                                   guint16   sequenceNumber,
                                    gboolean  newSegmentStarted,
+                                   rlc_lte_tap_info *tap_info,
                                    packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb)
 {
     proto_tree *seqnum_tree;
@@ -432,86 +624,320 @@ static void addChannelSequenceInfo(state_report_in_frame *p,
     seqnum_ti = proto_tree_add_string_format(tree,
                                              hf_rlc_lte_sequence_analysis,
                                              tvb, 0, 0,
-                                             "",
-                                             "Sequence Analysis");
+                                             "", "Sequence Analysis");
     seqnum_tree = proto_item_add_subtree(seqnum_ti,
                                          ett_rlc_lte_sequence_analysis);
     PROTO_ITEM_SET_GENERATED(seqnum_ti);
 
-    /* Previous channel frame */
-    if (p->previousFrameNum != 0) {
-        proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_previous_frame,
-                            tvb, 0, 0, p->previousFrameNum);
-    }
+    switch (p_rlc_lte_info->rlcMode) {
+        case RLC_AM_MODE:
 
-    /* Expected sequence number */
-    ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_expected_sn,
-                            tvb, 0, 0, p->sequenceExpected);
-    PROTO_ITEM_SET_GENERATED(ti);
-    if (!p->sequenceExpectedCorrect) {
-        /* Incorrect sequence number */
-        expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_WARN,
-                               "Wrong Sequence Number - got %u, expected %u",
-                               sequenceNumber, p->sequenceExpected);
-    }
-    else {
-        /* Correct sequence number, so check frame indication bits consistent */
-        if (p->previousSegmentIncomplete) {
-            /* Previous segment was incomplete, so this PDU should continue it */
-            if (newSegmentStarted) {
-                ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct,
-                                         tvb, 0, 0, FALSE);
-                if (!p->sequenceExpectedCorrect) {
+            /********************************************/
+            /* AM                                       */
+            /********************************************/
+
+            switch (p->state) {
+                case SN_OK:
+                    if (isControlFrame) {
+                        return;
+                    }
+
+                    ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
+                                                tvb, 0, 0, TRUE);
+                    PROTO_ITEM_SET_GENERATED(ti);
+                    proto_item_append_text(seqnum_ti, " - OK");
+                    break;
+
+                case SN_MAC_Retx:
+                    if (isControlFrame) {
+                        return;
+                    }
+
+                    ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
+                                                tvb, 0, 0, FALSE);
+                    PROTO_ITEM_SET_GENERATED(ti);
+                    ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_mac_retx,
+                                                tvb, 0, 0, TRUE);
+                    PROTO_ITEM_SET_GENERATED(ti);
                     expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_WARN,
-                                           "Last segment of previous PDU was not continued");
-                }
+                                           "AM Frame retransmitted for %s on UE %u (SN=%u) - due to MAC retx!",
+                                           val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
+                                           p_rlc_lte_info->ueid,
+                                           p->firstSN);
+                    break;
+
+                case SN_Retx:
+                    if (isControlFrame) {
+                        return;
+                    }
+
+                    ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
+                                                tvb, 0, 0, FALSE);
+                    PROTO_ITEM_SET_GENERATED(ti);
+                    ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_retx,
+                                                tvb, 0, 0, TRUE);
+                    PROTO_ITEM_SET_GENERATED(ti);
+                    expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_WARN,
+                                           "AM Frame retransmitted for %s on UE %u (SN=%u) - most likely in response to NACK",
+                                           val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
+                                           p_rlc_lte_info->ueid,
+                                           p->firstSN);
+                    proto_item_append_text(seqnum_ti, " - SN %u retransmitted", p->firstSN);
+                    break;
+
+                case SN_Repeated:
+                    if (isControlFrame) {
+                        return;
+                    }
+
+                    ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
+                                                tvb, 0, 0, FALSE);
+                    PROTO_ITEM_SET_GENERATED(ti);
+                    ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated,
+                                                tvb, 0, 0, TRUE);
+                    PROTO_ITEM_SET_GENERATED(ti);
+                    expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_WARN,
+                                           "AM SN %u Repeated for %s for UE %u - probably because didn't receive Status PDU?",
+                                           p->firstSN,
+                                           val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
+                                           p_rlc_lte_info->ueid);
+                    proto_item_append_text(seqnum_ti, "- SN %u Repeated",
+                                           p->firstSN);
+                    break;
+
+                case SN_Missing:
+                    if (isControlFrame) {
+                        return;
+                    }
+
+                    ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
+                                                tvb, 0, 0, FALSE);
+                    PROTO_ITEM_SET_GENERATED(ti);
+                    ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_skipped,
+                                                tvb, 0, 0, TRUE);
+                    PROTO_ITEM_SET_GENERATED(ti);
+                    if (p->lastSN != p->firstSN) {
+                        expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_WARN,
+                                               "AM SNs (%u to %u) missing for %s on UE %u",
+                                               p->firstSN, p->lastSN,
+                                               val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
+                                               p_rlc_lte_info->ueid);
+                        proto_item_append_text(seqnum_ti, " - SNs missing (%u to %u)",
+                                               p->firstSN, p->lastSN);
+                        tap_info->missingSNs = ((p->lastSN - p->firstSN) % 1024) + 1;
+                    }
+                    else {
+                        expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_WARN,
+                                               "AM SN (%u) missing for %s on UE %u",
+                                               p->firstSN,
+                                               val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
+                                               p_rlc_lte_info->ueid);
+                        proto_item_append_text(seqnum_ti, " - SN missing (%u)",
+                                               p->firstSN);
+                        tap_info->missingSNs = 1;
+                    }
+                    break;
+
+                case ACK_Out_of_Window:
+                    if (!isControlFrame) {
+                        return;
+                    }
+
+
+                    /* Not OK */
+                    ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
+                                                tvb, 0, 0, FALSE);
+                    /* Out of range */
+                    PROTO_ITEM_SET_GENERATED(ti);
+                    ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ack_out_of_range,
+                                                tvb, 0, 0, TRUE);
+                    PROTO_ITEM_SET_GENERATED(ti);
+
+                    /* Link back to last seen SN in other direction */
+                    ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame,
+                                             tvb, 0, 0, p->previousFrameNum);
+                    PROTO_ITEM_SET_GENERATED(ti);
+
+                    /* Expert error */
+                    expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_ERROR,
+                                           "AM ACK for SN %u - but last received SN in other direction is %u for UE %u",
+                                           p->firstSN, p->sequenceExpected,
+                                           p_rlc_lte_info->ueid);
+                    proto_item_append_text(seqnum_ti, "- ACK SN %u Outside Rx Window - last received SN is %u",
+                                           p->firstSN, p->sequenceExpected);
+
+                    break;
             }
-            else {
-               ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct,
-                                         tvb, 0, 0, TRUE);
+            break;
+
+        case RLC_UM_MODE:
+
+            /********************************************/
+            /* UM                                       */
+            /********************************************/
+
+            /* Previous channel frame */
+            if (p->previousFrameNum != 0) {
+                proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_previous_frame,
+                                    tvb, 0, 0, p->previousFrameNum);
             }
-        }
-        else {
-            /* Previous segment was complete, so this PDU should start a new one */
-            if (!newSegmentStarted) {
-                ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct,
-                                         tvb, 0, 0, FALSE);
-                if (!p->sequenceExpectedCorrect) {
-                    expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_WARN,
-                                           "Last segment of previous PDU was complete, but new segmeng was not started");
+
+            /* Expected sequence number */
+            ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_expected_sn,
+                                    tvb, 0, 0, p->sequenceExpected);
+            PROTO_ITEM_SET_GENERATED(ti);
+
+            if (!p->sequenceExpectedCorrect) {
+                /* Work out SN wrap (in case needed below) */
+                guint16 snLimit;
+                if (p_rlc_lte_info->UMSequenceNumberLength == 5) {
+                    snLimit = 32;
                 }
+                else {
+                    snLimit = 1024;
+                }
+
+                switch (p->state) {
+                    case SN_Missing:
+                        ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
+                                                    tvb, 0, 0, FALSE);
+                        PROTO_ITEM_SET_GENERATED(ti);
+                        ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_skipped,
+                                                    tvb, 0, 0, TRUE);
+                        PROTO_ITEM_SET_GENERATED(ti);
+                        if (p->lastSN != p->firstSN) {
+                            expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_WARN,
+                                                   "UM SNs (%u to %u) missing for %s on UE %u",
+                                                   p->firstSN, p->lastSN,
+                                                   val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
+                                                   p_rlc_lte_info->ueid);
+                            proto_item_append_text(seqnum_ti, " - SNs missing (%u to %u)",
+                                                   p->firstSN, p->lastSN);
+                            tap_info->missingSNs = ((p->lastSN - p->firstSN) % snLimit) + 1;
+                        }
+                        else {
+                            expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_WARN,
+                                                   "UM SN (%u) missing for %s on UE %u",
+                                                   p->firstSN,
+                                                   val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
+                                                   p_rlc_lte_info->ueid);
+                            proto_item_append_text(seqnum_ti, " - SN missing (%u)",
+                                                   p->firstSN);
+                            tap_info->missingSNs = 1;
+                        }
+                        break;
+
+                    case SN_Repeated:
+                        ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
+                                                    tvb, 0, 0, FALSE);
+                        PROTO_ITEM_SET_GENERATED(ti);
+                        ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated,
+                                                    tvb, 0, 0, TRUE);
+                        PROTO_ITEM_SET_GENERATED(ti);
+                        expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_WARN,
+                                               "UM SN (%u) repeated for %s for UE %u",
+                                               p->firstSN,
+                                               val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
+                                               p_rlc_lte_info->ueid);
+                        proto_item_append_text(seqnum_ti, "- SN %u Repeated",
+                                               p->firstSN);
+                        break;
+
+                    case SN_MAC_Retx:
+                        ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
+                                                    tvb, 0, 0, FALSE);
+                        PROTO_ITEM_SET_GENERATED(ti);
+                        ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_mac_retx,
+                                                    tvb, 0, 0, TRUE);
+                        PROTO_ITEM_SET_GENERATED(ti);
+                        expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_WARN,
+                                               "UM Frame retransmitted for %s on UE %u - due to MAC retx!",
+                                               val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
+                                               p_rlc_lte_info->ueid);
+                        break;
+
+                    case SN_OK:
+                        ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
+                                                    tvb, 0, 0, TRUE);
+                        PROTO_ITEM_SET_GENERATED(ti);
+                        proto_item_append_text(seqnum_ti, " - OK");
+                        break;
+
+                    default:
+                        /* Incorrect sequence number */
+                        expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_WARN,
+                                               "Wrong Sequence Number for %s on UE %u - got %u, expected %u",
+                                               val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
+                                               p_rlc_lte_info->ueid, sequenceNumber, p->sequenceExpected);
+                        break;
+                }
+
             }
             else {
-               ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct,
-                                         tvb, 0, 0, TRUE);
-            }
+                /* Correct sequence number, so check frame indication bits consistent */
+                if (p->previousSegmentIncomplete) {
+                    /* Previous segment was incomplete, so this PDU should continue it */
+                    if (newSegmentStarted) {
+                        ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct,
+                                                     tvb, 0, 0, FALSE);
+                        if (!p->sequenceExpectedCorrect) {
+                            expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_WARN,
+                                                   "Last segment of previous PDU was not continued for UE %u",
+                                                   p_rlc_lte_info->ueid);
+                        }
+                    }
+                    else {
+                       ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct,
+                                                   tvb, 0, 0, TRUE);
+                    }
+                }
+                else {
+                    /* Previous segment was complete, so this PDU should start a new one */
+                    if (!newSegmentStarted) {
+                        ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct,
+                                                    tvb, 0, 0, FALSE);
+                        if (!p->sequenceExpectedCorrect) {
+                            expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_WARN,
+                                                   "Last segment of previous PDU was complete, but new segment was not started");
+                        }
+                    }
+                    else {
+                       ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct,
+                                                   tvb, 0, 0, TRUE);
+                    }
 
-        }
-        PROTO_ITEM_SET_GENERATED(ti);
+                }
+                PROTO_ITEM_SET_GENERATED(ti);
+            }
     }
 }
 
 /* Update the channel status and set report for this frame */
 static void checkChannelSequenceInfo(packet_info *pinfo, tvbuff_t *tvb,
                                      rlc_lte_info *p_rlc_lte_info,
+                                     gboolean isControlFrame,
                                      guint16 sequenceNumber,
                                      gboolean first_includes_start, gboolean last_includes_end,
+                                     gboolean is_resegmented _U_,
+                                     rlc_lte_tap_info *tap_info,
                                      proto_tree *tree)
 {
     rlc_channel_hash_key   channel_key;
     rlc_channel_hash_key   *p_channel_key;
-    rlc_channel_status     *p_channel_status;
-    state_report_in_frame  *p_report_in_frame = NULL;
-    guint8                 createdChannel = FALSE;
-    guint16                expectedSequenceNumber;
+    rlc_channel_sequence_analysis_status     *p_channel_status;
+    state_sequence_analysis_report_in_frame  *p_report_in_frame = NULL;
+    gboolean               createdChannel = FALSE;
+    guint16                expectedSequenceNumber = 0;
+    guint16                snLimit = 0;
 
     /* If find stat_report_in_frame already, use that and get out */
     if (pinfo->fd->flags.visited) {
-        p_report_in_frame = (state_report_in_frame*)g_hash_table_lookup(rlc_lte_frame_report_hash,
-                                                                        &pinfo->fd->num);
+        p_report_in_frame = (state_sequence_analysis_report_in_frame*)g_hash_table_lookup(rlc_lte_frame_sequence_analysis_report_hash,
+                                                                                          &pinfo->fd->num);
         if (p_report_in_frame != NULL) {
-            addChannelSequenceInfo(p_report_in_frame, sequenceNumber, first_includes_start,
-                                   pinfo, tree, tvb);
+            addChannelSequenceInfo(p_report_in_frame, isControlFrame, p_rlc_lte_info,
+                                   sequenceNumber, first_includes_start,
+                                   tap_info, pinfo, tree, tvb);
             return;
         }
         else {
@@ -529,83 +955,506 @@ static void checkChannelSequenceInfo(packet_info *pinfo, tvbuff_t *tvb,
     channel_key.direction = p_rlc_lte_info->direction;
 
     /* Do the table lookup */
-    p_channel_status = (rlc_channel_status*)g_hash_table_lookup(rlc_lte_channel_hash, &channel_key);
+    p_channel_status = (rlc_channel_sequence_analysis_status*)g_hash_table_lookup(rlc_lte_sequence_analysis_channel_hash, &channel_key);
 
     /* Create table entry if necessary */
     if (p_channel_status == NULL) {
         createdChannel = TRUE;
 
-        /* Allocate a new key and value */
-        p_channel_key = se_alloc(sizeof(rlc_channel_hash_key));
-        p_channel_status = se_alloc0(sizeof(rlc_channel_status));
+        /* Allocate a new value and duplicate key contents */
+        p_channel_status = se_alloc0(sizeof(rlc_channel_sequence_analysis_status));
+        p_channel_key = se_memdup(&channel_key, sizeof(rlc_channel_hash_key));
 
-        /* Just give up if allocations failed */
-        if (!p_channel_key || !p_channel_status) {
-            return;
-        }
-
-        /* Copy key contents */
-        memcpy(p_channel_key, &channel_key, sizeof(rlc_channel_hash_key));
+        /* Set mode */
+        p_channel_status->rlcMode = p_rlc_lte_info->rlcMode;
 
         /* Add entry */
-        g_hash_table_insert(rlc_lte_channel_hash, p_channel_key, p_channel_status);
+        g_hash_table_insert(rlc_lte_sequence_analysis_channel_hash, p_channel_key, p_channel_status);
     }
 
     /* Create space for frame state_report */
-    p_report_in_frame = se_alloc(sizeof(state_report_in_frame));
+    p_report_in_frame = se_alloc(sizeof(state_sequence_analysis_report_in_frame));
+
+    switch (p_channel_status->rlcMode) {
+        case RLC_UM_MODE:
 
-    /* Set expected sequence number.
-       Wrap according to number of bits in SN */
-    if (!createdChannel) {
-        guint16 snLimit = 4096;  /* AM default */
-        if (p_rlc_lte_info->rlcMode == RLC_UM_MODE) {
             if (p_rlc_lte_info->UMSequenceNumberLength == 5) {
                 snLimit = 32;
             }
             else {
                 snLimit = 1024;
             }
+
+            /* Work out expected sequence number */
+            if (!createdChannel) {
+                expectedSequenceNumber = (p_channel_status->previousSequenceNumber + 1) % snLimit;
+            }
+            else {
+                /* Whatever we got is fine.. */
+                expectedSequenceNumber = sequenceNumber;
+            }
+
+            /* Set report for this frame */
+            /* For UM, sequence number is always expectedSequence number */
+            p_report_in_frame->sequenceExpectedCorrect = (sequenceNumber == expectedSequenceNumber);
+
+            /* For wrong sequence number... */
+            if (!p_report_in_frame->sequenceExpectedCorrect) {
+
+                p_report_in_frame->sequenceExpectedCorrect = FALSE;
+
+                /* Don't get confused by MAC (HARQ) retx */
+                if (is_mac_lte_frame_retx(pinfo, p_rlc_lte_info->direction)) {
+                    p_report_in_frame->state = SN_MAC_Retx;
+                    p_report_in_frame->firstSN = sequenceNumber;
+                }
+
+                /* Frames are not missing if we get an earlier sequence number again */
+                /* TODO: taking time into account would give better idea of whether missing or repeated... */
+                else if (((snLimit + sequenceNumber - expectedSequenceNumber) % snLimit) < 10) {
+                    p_report_in_frame->state = SN_Missing;
+                    tap_info->missingSNs = (snLimit + sequenceNumber - expectedSequenceNumber) % snLimit;
+                    p_report_in_frame->firstSN = expectedSequenceNumber;
+                    p_report_in_frame->lastSN = (snLimit + sequenceNumber - 1) % snLimit;
+
+                    p_report_in_frame->sequenceExpected = expectedSequenceNumber;
+                    p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum;
+                    p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete;
+
+                    /* Update channel status to remember *this* frame */
+                    p_channel_status->previousFrameNum = pinfo->fd->num;
+                    p_channel_status->previousSequenceNumber = sequenceNumber;
+                    p_channel_status->previousSegmentIncomplete = !last_includes_end;
+                }
+                else {
+                    /* An SN has been repeated */
+                    p_report_in_frame->state = SN_Repeated;
+                    p_report_in_frame->firstSN = sequenceNumber;
+
+                    p_report_in_frame->sequenceExpected = expectedSequenceNumber;
+                    p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum;
+                }
+            }
+            else {
+                /* SN was OK */
+                p_report_in_frame->sequenceExpected = expectedSequenceNumber;
+                p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum;
+                p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete;
+
+                /* Update channel status to remember *this* frame */
+                p_channel_status->previousFrameNum = pinfo->fd->num;
+                p_channel_status->previousSequenceNumber = sequenceNumber;
+                p_channel_status->previousSegmentIncomplete = !last_includes_end;
+            }
+
+            break;
+
+        case RLC_AM_MODE:
+
+            /* Work out expected sequence number */
+            if (!createdChannel) {
+                expectedSequenceNumber = (p_channel_status->previousSequenceNumber + 1) % 1024;
+            }
+
+            /* For AM, may be:
+               - expected Sequence number OR
+               - previous frame repeated
+               - old SN being sent (in response to NACK)
+               - new SN, but with frames missed out
+               Assume window whose front is at expectedSequenceNumber */
+
+            /* First of all, check to see whether frame is judged to be MAC Retx */
+            if (is_mac_lte_frame_retx(pinfo, p_rlc_lte_info->direction)) {
+                /* Just report that this is a MAC Retx */
+                p_report_in_frame->state = SN_MAC_Retx;
+                p_report_in_frame->firstSN = sequenceNumber;
+
+                /* No channel state to update */
+            }
+
+            /* Expected? */
+            else if (sequenceNumber == expectedSequenceNumber) {
+
+                /* Set report for this frame */
+                p_report_in_frame->sequenceExpectedCorrect = TRUE;
+                p_report_in_frame->sequenceExpected = expectedSequenceNumber;
+                p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum;
+                p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete;
+                p_report_in_frame->state = SN_OK;
+
+                /* Update channel status */
+                p_channel_status->previousSequenceNumber = sequenceNumber;
+                p_channel_status->previousFrameNum = pinfo->fd->num;
+                p_channel_status->previousSegmentIncomplete = !last_includes_end;
+            }
+
+            /* Previous subframe repeated? */
+            else if (((sequenceNumber+1) % 1024) == expectedSequenceNumber) {
+                p_report_in_frame->state = SN_Repeated;
+
+                /* Set report for this frame */
+                p_report_in_frame->sequenceExpectedCorrect = FALSE;
+                p_report_in_frame->sequenceExpected = expectedSequenceNumber;
+                p_report_in_frame->firstSN = sequenceNumber;
+                p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum;
+                p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete;
+
+
+                /* Really should be nothing to update... */
+                p_channel_status->previousSequenceNumber = sequenceNumber;
+                p_channel_status->previousFrameNum = pinfo->fd->num;
+                p_channel_status->previousSegmentIncomplete = !last_includes_end;
+            }
+
+            else {
+                /* Need to work out if new (with skips, or likely a retx (due to NACK)) */
+                int delta  = (1024 + expectedSequenceNumber - sequenceNumber) % 1024;
+
+                /* Rx window is 512, so check to see if this is a retx */
+                if (delta < 512) {
+                    /* Probably a retx due to receiving NACK */
+                    p_report_in_frame->state = SN_Retx;
+
+                    p_report_in_frame->firstSN = sequenceNumber;
+                    /* Don't update anything in channel state */
+                }
+
+                else {
+                    if (!createdChannel) {
+                        /* Ahead of expected SN. Assume frames have been missed */
+                        p_report_in_frame->state = SN_Missing;
+    
+                        p_report_in_frame->firstSN = expectedSequenceNumber;
+                        p_report_in_frame->lastSN = (1024 + sequenceNumber-1) % 1024;
+                    }
+                    else {
+                        /* The log may not contain the very first SNs for this channel, so be forgiving... */
+                        p_report_in_frame->state = SN_OK;
+                    }
+
+                    /* Update channel state - forget about missed SNs */
+                    p_report_in_frame->sequenceExpected = expectedSequenceNumber;
+                    p_channel_status->previousSequenceNumber = sequenceNumber;
+                    p_channel_status->previousFrameNum = pinfo->fd->num;
+                    p_channel_status->previousSegmentIncomplete = !last_includes_end;
+                }
+            }
+            break;
+
+        default:
+            /* Shouldn't get here! */
+            return;
+    }
+
+    /* Associate with this frame number */
+    g_hash_table_insert(rlc_lte_frame_sequence_analysis_report_hash, &pinfo->fd->num, p_report_in_frame);
+
+    /* Add state report for this frame into tree */
+    addChannelSequenceInfo(p_report_in_frame, isControlFrame, p_rlc_lte_info, sequenceNumber,
+                           first_includes_start, tap_info, pinfo, tree, tvb);
+}
+
+
+/* Add to the tree values associated with sequence analysis for this frame */
+static void addChannelRepeatedNACKInfo(rlc_channel_repeated_nack_report_in_frame *p,
+                                       rlc_lte_info *p_rlc_lte_info,
+                                       packet_info *pinfo, proto_tree *tree,
+                                       tvbuff_t *tvb)
+{
+    proto_tree *seqnum_tree;
+    proto_item *seqnum_ti;
+    proto_item *ti;
+    gint       n;
+
+    /* Create subtree */
+    seqnum_ti = proto_tree_add_string_format(tree,
+                                             hf_rlc_lte_sequence_analysis,
+                                             tvb, 0, 0,
+                                             "", "Sequence Analysis");
+    seqnum_tree = proto_item_add_subtree(seqnum_ti,
+                                         ett_rlc_lte_sequence_analysis);
+    PROTO_ITEM_SET_GENERATED(seqnum_ti);
+
+    /* OK = FALSE */
+    ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok,
+                                tvb, 0, 0, FALSE);
+    PROTO_ITEM_SET_GENERATED(ti);
+
+    /* Add each repeated NACK as item & expert info */
+    for (n=0; n < p->noOfNACKsRepeated; n++) {
+
+        ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated_nack,
+                                 tvb, 0, 0, p->repeatedNACKs[n]);
+        PROTO_ITEM_SET_GENERATED(ti);
+
+        expert_add_info_format(pinfo, ti, PI_SEQUENCE, PI_ERROR,
+                               "Same SN  (%u) NACKd for %s on UE %u in successive Status PDUs",
+                               p->repeatedNACKs[n],
+                               val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
+                               p_rlc_lte_info->ueid);
+    }
+
+    /* Link back to previous status report */
+    ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated_nack_original_frame,
+                             tvb, 0, 0, p->previousFrameNum);
+    PROTO_ITEM_SET_GENERATED(ti);
+
+    /* Append count to sequence analysis root */
+    proto_item_append_text(seqnum_ti, " - %u SNs repeated from previous Status PDU",
+                           p->noOfNACKsRepeated);
+}
+
+
+/* Update the channel repeated NACK status and set report for this frame */
+static void checkChannelRepeatedNACKInfo(packet_info *pinfo,
+                                         rlc_lte_info *p_rlc_lte_info,
+                                         rlc_lte_tap_info *tap_info,
+                                         proto_tree *tree,
+                                         tvbuff_t *tvb)
+{
+    rlc_channel_hash_key   channel_key;
+    rlc_channel_hash_key   *p_channel_key;
+    rlc_channel_repeated_nack_status     *p_channel_status;
+    rlc_channel_repeated_nack_report_in_frame  *p_report_in_frame = NULL;
+
+    guint16         noOfNACKsRepeated = 0;
+    guint16         repeatedNACKs[MAX_NACKs];
+    gint            n, i, j;
+
+    /* If find state_report_in_frame already, use that and get out */
+    if (pinfo->fd->flags.visited) {
+        p_report_in_frame = (rlc_channel_repeated_nack_report_in_frame*)g_hash_table_lookup(rlc_lte_frame_repeated_nack_report_hash,
+                                                                                            &pinfo->fd->num);
+        if (p_report_in_frame != NULL) {
+            addChannelRepeatedNACKInfo(p_report_in_frame, p_rlc_lte_info,
+                                       pinfo, tree, tvb);
+            return;
+        }
+        else {
+            /* Give up - we must have tried already... */
+            return;
         }
-        expectedSequenceNumber = (p_channel_status->previousSequenceNumber + 1) % snLimit;
     }
-    else {
-        expectedSequenceNumber = 0;
+
+
+    /**************************************************/
+    /* Create or find an entry for this channel state */
+    channel_key.ueId = p_rlc_lte_info->ueid;
+    channel_key.channelType = p_rlc_lte_info->channelType;
+    channel_key.channelId = p_rlc_lte_info->channelId;
+    channel_key.direction = p_rlc_lte_info->direction;
+    memset(repeatedNACKs, 0, sizeof(repeatedNACKs));
+
+    /* Do the table lookup */
+    p_channel_status = (rlc_channel_repeated_nack_status*)g_hash_table_lookup(rlc_lte_repeated_nack_channel_hash, &channel_key);
+
+    /* Create table entry if necessary */
+    if (p_channel_status == NULL) {
+
+        /* Allocate a new key and value */
+        p_channel_key = se_alloc(sizeof(rlc_channel_hash_key));
+        p_channel_status = se_alloc0(sizeof(rlc_channel_repeated_nack_status));
+
+        /* Copy key contents */
+        memcpy(p_channel_key, &channel_key, sizeof(rlc_channel_hash_key));
+
+        /* Add entry to table */
+        g_hash_table_insert(rlc_lte_repeated_nack_channel_hash, p_channel_key, p_channel_status);
     }
 
-    /* Set report info regarding sequence number */
-    if (sequenceNumber == expectedSequenceNumber) {
-        p_report_in_frame->sequenceExpectedCorrect = TRUE;
+    /* Compare NACKs in channel status with NACKs in tap_info.
+       Note any that are repeated */
+    for (i=0; i < p_channel_status->noOfNACKs; i++) {
+        for (j=0; j < MIN(tap_info->noOfNACKs, MAX_NACKs); j++) {
+            if (tap_info->NACKs[j] == p_channel_status->NACKs[i]) {
+                /* Don't add the same repeated NACK twice! */
+                if ((noOfNACKsRepeated == 0) ||
+                    (repeatedNACKs[noOfNACKsRepeated-1] != p_channel_status->NACKs[i])) {
+
+                    repeatedNACKs[noOfNACKsRepeated++] = p_channel_status->NACKs[i];
+                }
+            }
+        }
     }
-    else {
-        p_report_in_frame->sequenceExpectedCorrect = FALSE;
+
+    /* Copy NACKs from tap_info into channel status for next time! */
+    p_channel_status->noOfNACKs = 0;
+    for (n=0; n < MIN(tap_info->noOfNACKs, MAX_NACKs); n++) {
+        p_channel_status->NACKs[p_channel_status->noOfNACKs++] = tap_info->NACKs[n];
     }
-    p_report_in_frame->sequenceExpected = expectedSequenceNumber;
-    p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum;
-    p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete;
 
-    /* Associate with this frame number */
-    g_hash_table_insert(rlc_lte_frame_report_hash, &pinfo->fd->num, p_report_in_frame);
+    if (noOfNACKsRepeated >= 1) {
+        /* Create space for frame state_report */
+        p_report_in_frame = se_alloc(sizeof(rlc_channel_repeated_nack_report_in_frame));
 
-    /* Update channel status to remember *this* frame */
-    p_channel_status->previousFrameNum = pinfo->fd->num;
-    p_channel_status->previousSequenceNumber = sequenceNumber;
-    p_channel_status->previousSegmentIncomplete = !last_includes_end;
+        /* Copy in found duplicates */
+        for (n=0; n < MIN(tap_info->noOfNACKs, MAX_NACKs); n++) {
+            p_report_in_frame->repeatedNACKs[n] = repeatedNACKs[n];
+        }
+        p_report_in_frame->noOfNACKsRepeated = noOfNACKsRepeated;
 
-    /* Add state report for this frame into tree */
-    addChannelSequenceInfo(p_report_in_frame, sequenceNumber, first_includes_start,
-                           pinfo, tree, tvb);
+        p_report_in_frame->previousFrameNum = p_channel_status->frameNum;
+
+        /* Associate with this frame number */
+        g_hash_table_insert(rlc_lte_frame_repeated_nack_report_hash, &pinfo->fd->num, p_report_in_frame);
+
+        /* Add state report for this frame into tree */
+        addChannelRepeatedNACKInfo(p_report_in_frame, p_rlc_lte_info,
+                                   pinfo, tree, tvb);
+    }
+
+    /* Save frame number for next comparison */
+    p_channel_status->frameNum = pinfo->fd->num;
 }
 
+/* Check that the ACK is consistent with data the expected sequence number
+   in the other direction */
+static void checkChannelACKWindow(guint16 ack_sn,
+                                  packet_info *pinfo,
+                                  rlc_lte_info *p_rlc_lte_info,
+                                  rlc_lte_tap_info *tap_info,
+                                  proto_tree *tree,
+                                  tvbuff_t *tvb)
+{
+    rlc_channel_hash_key   channel_key;
+    rlc_channel_sequence_analysis_status     *p_channel_status;
+    state_sequence_analysis_report_in_frame  *p_report_in_frame = NULL;
+
+    /* If find stat_report_in_frame already, use that and get out */
+    if (pinfo->fd->flags.visited) {
+        p_report_in_frame = (state_sequence_analysis_report_in_frame*)g_hash_table_lookup(rlc_lte_frame_sequence_analysis_report_hash,
+                                                                                          &pinfo->fd->num);
+        if (p_report_in_frame != NULL) {
+            /* Add any info to tree */
+            addChannelSequenceInfo(p_report_in_frame, TRUE, p_rlc_lte_info,
+                                   0, FALSE,
+                                   tap_info, pinfo, tree, tvb);
+            return;
+        }
+        else {
+            /* Give up - we must have tried already... */
+            return;
+        }
+    }
+
+    /*******************************************************************/
+    /* Find an entry for this channel state (in the opposite direction */
+    channel_key.ueId = p_rlc_lte_info->ueid;
+    channel_key.channelType = p_rlc_lte_info->channelType;
+    channel_key.channelId = p_rlc_lte_info->channelId;
+    channel_key.direction =
+        (p_rlc_lte_info->direction == DIRECTION_UPLINK) ? DIRECTION_DOWNLINK : DIRECTION_UPLINK;
+
+    /* Do the table lookup */
+    p_channel_status = (rlc_channel_sequence_analysis_status*)g_hash_table_lookup(rlc_lte_sequence_analysis_channel_hash, &channel_key);
+
+    /* Create table entry if necessary */
+    if (p_channel_status == NULL) {
+        return;
+    }
+
+    /* Is it in the rx window? This test will catch if its ahead, but we don't
+       really know what the back of the tx window is... */
+    if (((1024 + p_channel_status->previousSequenceNumber+1 - ack_sn) % 1024) > 512) {
+
+        /* Set result */
+        p_report_in_frame = se_alloc(sizeof(state_sequence_analysis_report_in_frame));
+        p_report_in_frame->state = ACK_Out_of_Window;
+        p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum;
+        p_report_in_frame->sequenceExpected = p_channel_status->previousSequenceNumber;
+        p_report_in_frame->firstSN = ack_sn;
+
+        /* Associate with this frame number */
+        g_hash_table_insert(rlc_lte_frame_sequence_analysis_report_hash,
+                            &pinfo->fd->num, p_report_in_frame);
+
+        /* Add state report for this frame into tree */
+        addChannelSequenceInfo(p_report_in_frame, TRUE, p_rlc_lte_info, 0,
+                               FALSE, tap_info, pinfo, tree, tvb);
+    }
+}
 
 
 
 
+/***************************************************/
+/* Transparent mode PDU. Call RRC if configured to */
+static void dissect_rlc_lte_tm(tvbuff_t *tvb, packet_info *pinfo,
+                               proto_tree *tree,
+                               int offset,
+                               rlc_lte_info *p_rlc_lte_info,
+                               proto_item *top_ti)
+{
+    proto_item *raw_tm_ti;
+    proto_item *tm_ti;
+
+    /* Create hidden TM root */
+    tm_ti = proto_tree_add_string_format(tree, hf_rlc_lte_tm,
+                                         tvb, offset, 0, "", "TM");
+    PROTO_ITEM_SET_HIDDEN(tm_ti);
+
+    /* Remaining bytes are all data */
+    raw_tm_ti = proto_tree_add_item(tree, hf_rlc_lte_tm_data, tvb, offset, -1, FALSE);
+    if (!global_rlc_lte_call_rrc) {
+        write_pdu_label_and_info(top_ti, NULL, pinfo,
+                                 "   [%u-bytes]", tvb_length_remaining(tvb, offset));
+    }
+
+    if (global_rlc_lte_call_rrc) {
+        tvbuff_t *rrc_tvb = tvb_new_subset(tvb, offset, -1, tvb_length_remaining(tvb, offset));
+        volatile dissector_handle_t protocol_handle = 0;
+
+        switch (p_rlc_lte_info->channelType) {
+            case CHANNEL_TYPE_CCCH:
+                if (p_rlc_lte_info->direction == DIRECTION_UPLINK) {
+                    protocol_handle = find_dissector("lte_rrc.ul_ccch");
+                }
+                else {
+                    protocol_handle = find_dissector("lte_rrc.dl_ccch");
+                }
+                break;
+
+            case CHANNEL_TYPE_BCCH_BCH:
+                protocol_handle = find_dissector("lte_rrc.bcch_bch");
+                break;
+            case CHANNEL_TYPE_BCCH_DL_SCH:
+                protocol_handle = find_dissector("lte_rrc.bcch_dl_sch");
+                break;
+            case CHANNEL_TYPE_PCCH:
+                protocol_handle = find_dissector("lte-rrc.pcch");
+                break;
+
+            case CHANNEL_TYPE_SRB:
+            case CHANNEL_TYPE_DRB:
+
+            default:
+                /* Shouldn't happen, just return... */
+                return;
+        }
+
+        /* Hide raw view of bytes */
+        PROTO_ITEM_SET_HIDDEN(raw_tm_ti);
+
+        /* Call it (catch exceptions) */
+        TRY {
+            call_dissector_only(protocol_handle, rrc_tvb, pinfo, tree);
+        }
+        CATCH_ALL {
+        }
+        ENDTRY
+    }
+}
+
+
+
 /***************************************************/
 /* Unacknowledged mode PDU                         */
 static void dissect_rlc_lte_um(tvbuff_t *tvb, packet_info *pinfo,
                                proto_tree *tree,
                                int offset,
-                               rlc_lte_info *p_rlc_lte_info)
+                               rlc_lte_info *p_rlc_lte_info,
+                               proto_item *top_ti,
+                               rlc_lte_tap_info *tap_info)
 {
     guint64 framing_info;
     gboolean first_includes_start;
@@ -613,15 +1462,21 @@ static void dissect_rlc_lte_um(tvbuff_t *tvb, packet_info *pinfo,
     guint64 fixed_extension;
     guint64 sn;
     gint    start_offset = offset;
+    proto_item *um_ti;
     proto_tree *um_header_tree;
     proto_item *um_header_ti;
+    gboolean is_truncated;
+    proto_item *truncated_ti;
+
+    /* Hidden UM root */
+    um_ti = proto_tree_add_string_format(tree, hf_rlc_lte_um,
+                                         tvb, offset, 0, "", "UM");
+    PROTO_ITEM_SET_HIDDEN(um_ti);
 
     /* Add UM header subtree */
-    um_header_ti = proto_tree_add_string_format(tree,
-                                                hf_rlc_lte_um_header,
+    um_header_ti = proto_tree_add_string_format(tree, hf_rlc_lte_um_header,
                                                 tvb, offset, 0,
-                                                "",
-                                                "UM header");
+                                                "", "UM header");
     um_header_tree = proto_item_add_subtree(um_header_ti,
                                             ett_rlc_lte_um_header);
 
@@ -684,14 +1539,11 @@ static void dissect_rlc_lte_um(tvbuff_t *tvb, packet_info *pinfo,
         return;
     }
 
+    tap_info->sequenceNumber = (guint16)sn;
+
     /* Show SN in info column */
-    if (check_col(pinfo->cinfo, COL_INFO)) {
-        col_append_fstr(pinfo->cinfo, COL_INFO, "  SN=%04u",
-                        (guint16)sn);
-    }
+    write_pdu_label_and_info(top_ti, um_header_ti, pinfo, "  SN=%-4u", (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);
 
 
@@ -701,6 +1553,21 @@ static void dissect_rlc_lte_um(tvbuff_t *tvb, packet_info *pinfo,
         offset = dissect_rlc_lte_extension_header(tvb, pinfo, tree, offset);
     }
 
+    if (global_rlc_lte_headers_expected) {
+        /* There might not be any data, if only headers (plus control data) were logged */
+        is_truncated = (tvb_length_remaining(tvb, offset) == 0);
+        truncated_ti = proto_tree_add_uint(tree, hf_rlc_lte_header_only, tvb, 0, 0,
+                                           is_truncated);
+        if (is_truncated) {
+            PROTO_ITEM_SET_GENERATED(truncated_ti);
+            expert_add_info_format(pinfo, truncated_ti, PI_SEQUENCE, PI_NOTE,
+                                   "RLC PDU SDUs have been omitted");
+            return;
+        }
+        else {
+            PROTO_ITEM_SET_HIDDEN(truncated_ti);
+        }
+    }
 
     /* Extract these 2 flags from framing_info */
     first_includes_start = ((guint8)framing_info & 0x02) == 0;
@@ -708,10 +1575,16 @@ static void dissect_rlc_lte_um(tvbuff_t *tvb, packet_info *pinfo,
 
 
     /* Call sequence analysis function now */
-    if (global_rlc_lte_sequence_analysis) {
+    if (((global_rlc_lte_um_sequence_analysis == SEQUENCE_ANALYSIS_MAC_ONLY) &&
+         (p_get_proto_data(pinfo->fd, proto_mac_lte) != NULL)) ||
+        ((global_rlc_lte_um_sequence_analysis == SEQUENCE_ANALYSIS_RLC_ONLY) &&
+         (p_get_proto_data(pinfo->fd, proto_mac_lte) == NULL))) {
+
         checkChannelSequenceInfo(pinfo, tvb, p_rlc_lte_info,
+                                FALSE,
                                 (guint16)sn, first_includes_start, last_includes_end,
-                                um_header_tree);
+                                FALSE, /* UM doesn't re-segment */
+                                tap_info, um_header_tree);
     }
 
 
@@ -721,8 +1594,9 @@ static void dissect_rlc_lte_um(tvbuff_t *tvb, packet_info *pinfo,
         /* 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],
+            show_PDU_in_tree(pinfo, tree, tvb, offset, s_lengths[n], p_rlc_lte_info,
+                             (n==0) ? first_includes_start : TRUE);
+            show_PDU_in_info(pinfo, top_ti, s_lengths[n],
                              (n==0) ? first_includes_start : TRUE,
                              TRUE);
             tvb_ensure_bytes_exist(tvb, offset, s_lengths[n]);
@@ -731,24 +1605,30 @@ static void dissect_rlc_lte_um(tvbuff_t *tvb, packet_info *pinfo,
     }
 
     /* 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),
+    show_PDU_in_tree(pinfo, tree, tvb, offset, -1, p_rlc_lte_info,
+                     ((s_number_of_extensions == 0) ? first_includes_start : TRUE) && last_includes_end);
+
+
+    show_PDU_in_info(pinfo, top_ti, (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,
                                           proto_item *status_ti,
-                                          int offset)
+                                          int offset,
+                                          proto_item *top_ti,
+                                          rlc_lte_info *p_rlc_lte_info,
+                                          rlc_lte_tap_info *tap_info)
 {
     guint8     cpt;
     guint64    ack_sn, nack_sn;
+    guint16    nack_count = 0;
     guint64    e1 = 0, e2 = 0;
     guint64    so_start, so_end;
     int        bit_offset = offset * 8;
@@ -767,21 +1647,16 @@ static void dissect_rlc_lte_am_status_pdu(tvbuff_t *tvb,
         return;
     }
 
-
-    /*****************************************************************/
-    /* STATUS PDU                                                    */
-
-    /* The PDU itself starts 4 bits into the byte */
+    /* The Status PDU itself starts 4 bits into the byte */
     bit_offset += 4;
 
     /* ACK SN */
     proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_ack_sn, tvb,
                                 bit_offset, 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);
-    }
+    write_pdu_label_and_info(top_ti, status_ti, pinfo, "  ACK_SN=%-4u", (guint16)ack_sn);
 
+    tap_info->ACKNo = (guint16)ack_sn;
 
     /* E1 */
     proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e1, tvb,
@@ -802,12 +1677,16 @@ static void dissect_rlc_lte_am_status_pdu(tvbuff_t *tvb,
             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);
+            write_pdu_label_and_info(top_ti, NULL, pinfo, "  NACK_SN=%-4u", (guint16)nack_sn);
 
+            /* Copy into struct, but don't exceed buffer */
+            if (nack_count < MAX_NACKs) {
+                tap_info->NACKs[nack_count++] = (guint16)nack_sn;
+            }
+            else {
+                /* Let it get bigger than the array for accurate stats... */
+                nack_count++;
+            }
 
             /* E1 */
             proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e1, tvb,
@@ -817,6 +1696,21 @@ static void dissect_rlc_lte_am_status_pdu(tvbuff_t *tvb,
             /* E2 */
             proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e2, tvb,
                                         bit_offset, 1, &e2, FALSE);
+
+            /* Report as expert info */
+            if (e2) {
+                expert_add_info_format(pinfo, nack_ti, PI_SEQUENCE, PI_WARN,
+                                       "Status PDU reports NACK (partial) on %s for UE %u",
+                                       val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
+                                       p_rlc_lte_info->ueid);
+            }
+            else {
+                expert_add_info_format(pinfo, nack_ti, PI_SEQUENCE, PI_WARN,
+                                       "Status PDU reports NACK on %s for UE %u",
+                                       val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"),
+                                       p_rlc_lte_info->ueid);
+            }
+
             bit_offset++;
         }
 
@@ -830,13 +1724,16 @@ static void dissect_rlc_lte_am_status_pdu(tvbuff_t *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)");
-                }
+            if ((guint16)so_end == 0x7fff) {
+                write_pdu_label_and_info(top_ti, NULL, pinfo,
+                                         "  (SOstart=%u SOend=<END-OF_PDU>)",
+                                         (guint16)so_start);
+            }
+            else {
+                write_pdu_label_and_info(top_ti, NULL, pinfo,
+                                         "  (SOstart=%u SOend=%u)",
+                                         (guint16)so_start, (guint16)so_end);
             }
 
             /* Reset this flag here */
@@ -844,16 +1741,36 @@ static void dissect_rlc_lte_am_status_pdu(tvbuff_t *tvb,
         }
     } while (e1 || e2);
 
+    if (nack_count > 0) {
+        proto_item *count_ti = proto_tree_add_uint(tree, hf_rlc_lte_am_nacks, tvb, 0, 1, nack_count);
+        PROTO_ITEM_SET_GENERATED(count_ti);
+        proto_item_append_text(status_ti, "  (%u NACKs)", nack_count);
+        tap_info->noOfNACKs = nack_count;
+    }
+
     /* Check that we've reached the end of the PDU. If not, show malformed */
     offset = (bit_offset+7) / 8;
     if (tvb_length_remaining(tvb, offset) > 0) {
         expert_add_info_format(pinfo, status_ti, PI_MALFORMED, PI_ERROR,
-                               "%u bytes remaining after Status PDU complete",
+                               "%cL %u bytes remaining after Status PDU complete",
+                               (p_rlc_lte_info->direction == DIRECTION_UPLINK) ? 'U' : 'D',
                                tvb_length_remaining(tvb, offset));
     }
 
     /* Set selected length of control tree */
     proto_item_set_len(status_ti, offset);
+
+    /* Repeated NACK analysis & check ACK-SN is in range */
+    if (((global_rlc_lte_am_sequence_analysis == SEQUENCE_ANALYSIS_MAC_ONLY) &&
+         (p_get_proto_data(pinfo->fd, proto_mac_lte) != NULL)) ||
+        ((global_rlc_lte_am_sequence_analysis == SEQUENCE_ANALYSIS_RLC_ONLY) &&
+         (p_get_proto_data(pinfo->fd, proto_mac_lte) == NULL))) {
+
+        if (!is_mac_lte_frame_retx(pinfo, p_rlc_lte_info->direction)) {
+            checkChannelRepeatedNACKInfo(pinfo, p_rlc_lte_info, tap_info, tree, tvb);
+            checkChannelACKWindow((guint16)ack_sn, pinfo, p_rlc_lte_info, tap_info, tree, tvb);
+        }
+     }
 }
 
 
@@ -862,62 +1779,72 @@ static void dissect_rlc_lte_am_status_pdu(tvbuff_t *tvb,
 static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
                                proto_tree *tree,
                                int offset,
-                               rlc_lte_info *p_rlc_lte_info _U_)
+                               rlc_lte_info *p_rlc_lte_info,
+                               proto_item *top_ti,
+                               rlc_lte_tap_info *tap_info)
 {
     guint8 is_data;
-    guint8 is_segment;
+    guint8 is_resegmented;
     guint8 polling;
     guint8 fixed_extension;
     guint8 framing_info;
     gboolean first_includes_start;
     gboolean last_includes_end;
+    proto_item *am_ti;
     proto_tree *am_header_tree;
     proto_item *am_header_ti;
     gint   start_offset = offset;
     guint16    sn;
+    gboolean is_truncated;
+    proto_item *truncated_ti;
 
-    /* Add UM header subtree */
-    am_header_ti = proto_tree_add_string_format(tree,
-                                                hf_rlc_lte_am_header,
+    /* Hidden AM root */
+    am_ti = proto_tree_add_string_format(tree, hf_rlc_lte_am,
+                                         tvb, offset, 0, "", "AM");
+    PROTO_ITEM_SET_HIDDEN(am_ti);
+
+    /* Add AM header subtree */
+    am_header_ti = proto_tree_add_string_format(tree, hf_rlc_lte_am_header,
                                                 tvb, offset, 0,
-                                                "",
-                                                "AM header");
+                                                "", "AM Header ");
     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]");
-    }
-
+    tap_info->isControlPDU = !is_data;
 
-    /**************************************************/
-    /* Control PDUs are a completely separate format  */
     if (!is_data) {
-        dissect_rlc_lte_am_status_pdu(tvb, pinfo, am_header_tree, am_header_ti, offset);
+        /**********************/
+        /* Status PDU         */
+        write_pdu_label_and_info(top_ti, NULL, pinfo, " [CONTROL]");
+
+        /* Control PDUs are a completely separate format  */
+        dissect_rlc_lte_am_status_pdu(tvb, pinfo, am_header_tree, am_header_ti,
+                                      offset, top_ti,
+                                      p_rlc_lte_info, tap_info);
         return;
     }
 
-
     /******************************/
     /* Data PDU fixed header      */
 
     /* Re-segmentation Flag (RF) field */
-    is_segment = (tvb_get_guint8(tvb, offset) & 0x40) >> 6;
+    is_resegmented = (tvb_get_guint8(tvb, offset) & 0x40) >> 6;
     proto_tree_add_item(am_header_tree, hf_rlc_lte_am_rf, tvb, offset, 1, FALSE);
+    tap_info->isResegmented = is_resegmented;
+
+    write_pdu_label_and_info(top_ti, NULL, pinfo,
+                             (is_resegmented) ? " [DATA-SEGMENT]" : " [DATA]");
 
     /* Polling bit */
     polling = (tvb_get_guint8(tvb, offset) & 0x20) >> 5;
     proto_tree_add_item(am_header_tree, hf_rlc_lte_am_p, tvb, offset, 1, FALSE);
-    if (check_col(pinfo->cinfo, COL_INFO)) {
-        col_append_str(pinfo->cinfo, COL_INFO, (polling) ? " (P) " : "     ");
-    }
+
+    write_pdu_label_and_info(top_ti, NULL, pinfo, (polling) ? " (P) " : "     ");
     if (polling) {
-        proto_item_append_text(am_header_ti, " (P)");
+        proto_item_append_text(am_header_ti, " (P) ");
     }
 
     /* Framing Info */
@@ -932,25 +1859,22 @@ static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
     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;
+    tap_info->sequenceNumber = sn;
 
-    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);
+    write_pdu_label_and_info(top_ti, am_header_ti, pinfo, "sn=%-4u", sn);
 
     /***************************************/
     /* Dissect extra segment header fields */
-    if (is_segment) {
+    if (is_resegmented) {
+        guint16 segmentOffset;
+
         /* Last Segment Field (LSF) */
         proto_tree_add_item(am_header_tree, hf_rlc_lte_am_segment_lsf, tvb, offset, 1, FALSE);
 
         /* SO */
+        segmentOffset = tvb_get_ntohs(tvb, offset) & 0x7fff;
         proto_tree_add_item(am_header_tree, hf_rlc_lte_am_segment_so, tvb, offset, 2, FALSE);
-
+        write_pdu_label_and_info(top_ti, am_header_ti, pinfo, " SO=%u ", segmentOffset);
         offset += 2;
     }
 
@@ -960,20 +1884,40 @@ static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
         offset = dissect_rlc_lte_extension_header(tvb, pinfo, tree, offset);
     }
 
+    /* There might not be any data, if only headers (plus control data) were logged */
+    if (global_rlc_lte_headers_expected) {
+        is_truncated = (tvb_length_remaining(tvb, offset) == 0);
+        truncated_ti = proto_tree_add_uint(tree, hf_rlc_lte_header_only, tvb, 0, 0,
+                                           is_truncated);
+        if (is_truncated) {
+            PROTO_ITEM_SET_GENERATED(truncated_ti);
+            expert_add_info_format(pinfo, truncated_ti, PI_SEQUENCE, PI_NOTE,
+                                   "RLC PDU SDUs have been omitted");
+            return;
+        }
+        else {
+            PROTO_ITEM_SET_HIDDEN(truncated_ti);
+        }
+    }
+
+    /* Head is now complete */
+    proto_item_set_len(am_header_ti, offset-start_offset);
 
     /* Extract these 2 flags from framing_info */
     first_includes_start = (framing_info & 0x02) == 0;
     last_includes_end =    (framing_info & 0x01) == 0;
 
 
-    /* Call sequence analysis function now (pretty limited for AM) */
-#if 0
-    if (global_rlc_lte_sequence_analysis) {
-        checkChannelSequenceInfo(pinfo, tvb, p_rlc_lte_info, (guint16)sn,
+    /* Call sequence analysis function now */
+    if (((global_rlc_lte_am_sequence_analysis == SEQUENCE_ANALYSIS_MAC_ONLY) &&
+         (p_get_proto_data(pinfo->fd, proto_mac_lte) != NULL)) ||
+        ((global_rlc_lte_am_sequence_analysis == SEQUENCE_ANALYSIS_RLC_ONLY) &&
+         (p_get_proto_data(pinfo->fd, proto_mac_lte) == NULL))) {
+
+        checkChannelSequenceInfo(pinfo, tvb, p_rlc_lte_info, FALSE, (guint16)sn,
                                  first_includes_start, last_includes_end,
-                                 am_header_tree);
+                                 is_resegmented, tap_info, tree);
     }
-#endif
 
 
     /*************************************/
@@ -982,8 +1926,9 @@ static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
         /* 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],
+            show_PDU_in_tree(pinfo, tree, tvb, offset, s_lengths[n], p_rlc_lte_info,
+                             (n==0) ? first_includes_start : TRUE);
+            show_PDU_in_info(pinfo, top_ti, s_lengths[n],
                              (n==0) ? first_includes_start : TRUE,
                              TRUE);
             tvb_ensure_bytes_exist(tvb, offset, s_lengths[n]);
@@ -993,35 +1938,167 @@ static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
 
     /* Final data element */
     if (tvb_length_remaining(tvb, offset) > 0) {
-        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),
+        show_PDU_in_tree(pinfo, tree, tvb, offset, -1, p_rlc_lte_info,
+                         ((s_number_of_extensions == 0) ? first_includes_start : TRUE) && last_includes_end);
+        show_PDU_in_info(pinfo, top_ti, (guint16)tvb_length_remaining(tvb, offset),
                          (s_number_of_extensions == 0) ? first_includes_start : TRUE,
                          last_includes_end);
     }
+    else {
+        /* Report that expected data was missing (unless we know it might happen) */
+        if (!global_rlc_lte_headers_expected) {
+            if (s_number_of_extensions > 0) {
+                expert_add_info_format(pinfo, am_header_ti, PI_MALFORMED, PI_ERROR,
+                                      "AM data PDU doesn't contain any data beyond extensions");
+            }
+            else {
+                expert_add_info_format(pinfo, am_header_ti, PI_MALFORMED, PI_ERROR,
+                                      "AM data PDU doesn't contain any data");
+            }
+        }
+    }
 }
 
 
+/* Heuristic dissector looks for supported framing protocol (see wiki page)  */
+static gboolean dissect_rlc_lte_heur(tvbuff_t *tvb, packet_info *pinfo,
+                                     proto_tree *tree)
+{
+    gint                 offset = 0;
+    struct rlc_lte_info  *p_rlc_lte_info;
+    tvbuff_t             *rlc_tvb;
+    guint8               tag = 0;
+    gboolean             infoAlreadySet = FALSE;
+    gboolean             umSeqNumLengthTagPresent = FALSE;
+
+    /* This is a heuristic dissector, which means we get all the UDP
+     * traffic not sent to a known dissector and not claimed by
+     * a heuristic dissector called before us!
+     */
+
+    if (!global_rlc_lte_heur) {
+        return FALSE;
+    }
+
+    /* Do this again on re-dissection to re-discover offset of actual PDU */
+
+    /* Needs to be at least as long as:
+       - the signature string
+       - fixed header bytes
+       - tag for data
+       - at least one byte of RLC PDU payload */
+    if ((size_t)tvb_length_remaining(tvb, offset) < (strlen(RLC_LTE_START_STRING)+1+2)) {
+        return FALSE;
+    }
+
+    /* OK, compare with signature string */
+    if (tvb_strneql(tvb, offset, RLC_LTE_START_STRING, (gint)strlen(RLC_LTE_START_STRING)) != 0) {
+        return FALSE;
+    }
+    offset += (gint)strlen(RLC_LTE_START_STRING);
+
+
+    /* If redissecting, use previous info struct (if available) */
+    p_rlc_lte_info = p_get_proto_data(pinfo->fd, proto_rlc_lte);
+    if (p_rlc_lte_info == NULL) {
+        /* Allocate new info struct for this frame */
+        p_rlc_lte_info = se_alloc0(sizeof(struct rlc_lte_info));
+        infoAlreadySet = FALSE;
+    }
+    else {
+        infoAlreadySet = TRUE;
+    }
+
+
+    /* Read fixed fields */
+    p_rlc_lte_info->rlcMode = tvb_get_guint8(tvb, offset++);
+
+    /* Read optional fields */
+    while (tag != RLC_LTE_PAYLOAD_TAG) {
+        /* Process next tag */
+        tag = tvb_get_guint8(tvb, offset++);
+        switch (tag) {
+            case RLC_LTE_UM_SN_LENGTH_TAG:
+                p_rlc_lte_info->UMSequenceNumberLength = tvb_get_guint8(tvb, offset);
+                offset++;
+                umSeqNumLengthTagPresent = TRUE;
+                break;
+            case RLC_LTE_DIRECTION_TAG:
+                p_rlc_lte_info->direction = tvb_get_guint8(tvb, offset);
+                offset++;
+                break;
+            case RLC_LTE_PRIORITY_TAG:
+                p_rlc_lte_info->priority = tvb_get_guint8(tvb, offset);
+                offset++;
+                break;
+            case RLC_LTE_UEID_TAG:
+                p_rlc_lte_info->ueid = tvb_get_ntohs(tvb, offset);
+                offset += 2;
+                break;
+            case RLC_LTE_CHANNEL_TYPE_TAG:
+                p_rlc_lte_info->channelType = tvb_get_ntohs(tvb, offset);
+                offset += 2;
+                break;
+            case RLC_LTE_CHANNEL_ID_TAG:
+                p_rlc_lte_info->channelId = tvb_get_ntohs(tvb, offset);
+                offset += 2;
+                break;
+
+            case RLC_LTE_PAYLOAD_TAG:
+                /* Have reached data, so set payload length and get out of loop */
+                p_rlc_lte_info->pduLength= tvb_length_remaining(tvb, offset);
+                continue;
+
+            default:
+                /* It must be a recognised tag */
+                return FALSE;
+        }
+    }
+
+    if ((p_rlc_lte_info->rlcMode == RLC_UM_MODE) && (umSeqNumLengthTagPresent == FALSE)) {
+        /* Conditional field is not present */
+        return FALSE;
+    }
+
+    if (!infoAlreadySet) {
+        /* Store info in packet */
+        p_add_proto_data(pinfo->fd, proto_rlc_lte, p_rlc_lte_info);
+    }
+
+    /**************************************/
+    /* OK, now dissect as RLC LTE         */
+
+    /* Create tvb that starts at actual RLC PDU */
+    rlc_tvb = tvb_new_subset(tvb, offset, -1, tvb_reported_length(tvb)-offset);
+    dissect_rlc_lte(rlc_tvb, pinfo, tree);
+    return TRUE;
+}
+
 
 /*****************************/
 /* Main dissection function. */
 /*****************************/
 
-void dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+static void dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
 {
     proto_tree             *rlc_lte_tree;
+    proto_tree             *context_tree;
+    proto_item             *top_ti;
+    proto_item             *context_ti;
     proto_item             *ti;
     proto_item             *mode_ti;
     gint                   offset = 0;
     struct rlc_lte_info    *p_rlc_lte_info = NULL;
 
+    /* Allocate and Zero tap struct */
+    rlc_lte_tap_info *tap_info = ep_alloc0(sizeof(rlc_lte_tap_info));
+
     /* Set protocol name */
-    if (check_col(pinfo->cinfo, COL_PROTOCOL)) {
-        col_set_str(pinfo->cinfo, COL_PROTOCOL, "RLC-LTE");
-    }
+    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);
+    top_ti = proto_tree_add_item(tree, proto_rlc_lte, tvb, offset, -1, FALSE);
+    rlc_lte_tree = proto_item_add_subtree(top_ti, ett_rlc_lte);
 
 
     /* Look for packet info! */
@@ -1029,77 +2106,106 @@ void dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
 
     /* 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!");
+        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,
+    /* Create context root */
+    context_ti = proto_tree_add_string_format(rlc_lte_tree, hf_rlc_lte_context,
+                                              tvb, offset, 0, "", "Context");
+    context_tree = proto_item_add_subtree(context_ti, ett_rlc_lte_context);
+    PROTO_ITEM_SET_GENERATED(context_ti);
+
+    ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_direction,
                              tvb, 0, 0, p_rlc_lte_info->direction);
     PROTO_ITEM_SET_GENERATED(ti);
 
-    mode_ti = proto_tree_add_uint(rlc_lte_tree, hf_rlc_lte_context_mode,
+    mode_ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_mode,
                                   tvb, 0, 0, p_rlc_lte_info->rlcMode);
     PROTO_ITEM_SET_GENERATED(mode_ti);
 
     if (p_rlc_lte_info->ueid != 0) {
-        ti = proto_tree_add_uint(rlc_lte_tree, hf_rlc_lte_context_ueid,
+        ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_ueid,
                                  tvb, 0, 0, p_rlc_lte_info->ueid);
         PROTO_ITEM_SET_GENERATED(ti);
     }
 
-    ti = proto_tree_add_uint(rlc_lte_tree, hf_rlc_lte_context_priority,
+    ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_priority,
                              tvb, 0, 0, p_rlc_lte_info->priority);
     PROTO_ITEM_SET_GENERATED(ti);
 
-    ti = proto_tree_add_uint(rlc_lte_tree, hf_rlc_lte_context_channel_type,
+    ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_channel_type,
                              tvb, 0, 0, p_rlc_lte_info->channelType);
     PROTO_ITEM_SET_GENERATED(ti);
 
     if ((p_rlc_lte_info->channelType == CHANNEL_TYPE_SRB) ||
         (p_rlc_lte_info->channelType == CHANNEL_TYPE_DRB)) {
-        ti = proto_tree_add_uint(rlc_lte_tree, hf_rlc_lte_context_channel_id,
+        ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_channel_id,
                                  tvb, 0, 0, p_rlc_lte_info->channelId);
         PROTO_ITEM_SET_GENERATED(ti);
     }
 
-    ti = proto_tree_add_uint(rlc_lte_tree, hf_rlc_lte_context_pdu_length,
+    ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_pdu_length,
                              tvb, 0, 0, p_rlc_lte_info->pduLength);
     PROTO_ITEM_SET_GENERATED(ti);
 
     if (p_rlc_lte_info->rlcMode == RLC_UM_MODE) {
-        ti = proto_tree_add_uint(rlc_lte_tree, hf_rlc_lte_context_um_sn_length,
+        ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_um_sn_length,
                                  tvb, 0, 0, p_rlc_lte_info->UMSequenceNumberLength);
         PROTO_ITEM_SET_GENERATED(ti);
     }
 
+    /* Append highlights to top-level item */
+    if (p_rlc_lte_info->ueid != 0) {
+        proto_item_append_text(top_ti, "   UEId=%u", p_rlc_lte_info->ueid);
+    }
+
+    if (p_rlc_lte_info->channelId == 0) {
+        proto_item_append_text(top_ti, " (%s) ",
+                               val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"));
+    }
+    else {
+        proto_item_append_text(top_ti, " (%s:%u) ",
+                               val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
+                               p_rlc_lte_info->channelId);
+    }
 
     /* 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);
-        }
+    write_pdu_label_and_info(top_ti, NULL, pinfo,
+                             "[%s] [%s] ",
+                             (p_rlc_lte_info->direction == 0) ? "UL" : "DL",
+                             val_to_str_const(p_rlc_lte_info->rlcMode, rlc_mode_short_vals, "Unknown"));
+    if (p_rlc_lte_info->ueid != 0) {
+        col_append_fstr(pinfo->cinfo, COL_INFO, "UEId=%u ", p_rlc_lte_info->ueid);
+    }
+    if (p_rlc_lte_info->channelId == 0) {
+        write_pdu_label_and_info(top_ti, NULL, pinfo, "%s",
+                                 val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"));
+    }
+    else {
+        write_pdu_label_and_info(top_ti, NULL, pinfo, "%s:%-2u",
+                                 val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"),
+                                 p_rlc_lte_info->channelId);
     }
 
+    /* Set context-info parts of tap struct */
+    tap_info->rlcMode = p_rlc_lte_info->rlcMode;
+    tap_info->direction = p_rlc_lte_info->direction;
+    tap_info->priority = p_rlc_lte_info->priority;
+    tap_info->ueid = p_rlc_lte_info->ueid;
+    tap_info->channelType = p_rlc_lte_info->channelType;
+    tap_info->channelId = p_rlc_lte_info->channelId;
+    tap_info->pduLength = p_rlc_lte_info->pduLength;
+    tap_info->UMSequenceNumberLength = p_rlc_lte_info->UMSequenceNumberLength;
+    tap_info->loggedInMACFrame = (p_get_proto_data(pinfo->fd, proto_mac_lte) != NULL);
+
+    tap_info->time = pinfo->fd->abs_ts;
+
     /* Reset this count */
     s_number_of_extensions = 0;
 
@@ -1107,25 +2213,24 @@ void dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
     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));
-            }
+            dissect_rlc_lte_tm(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info, top_ti);
             break;
 
         case RLC_UM_MODE:
-            dissect_rlc_lte_um(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info);
+            dissect_rlc_lte_um(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info, top_ti,
+                               tap_info);
             break;
 
         case RLC_AM_MODE:
-            dissect_rlc_lte_am(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info);
+            dissect_rlc_lte_am(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info, top_ti,
+                               tap_info);
             break;
 
         case RLC_PREDEF:
             /* Predefined data (i.e. not containing a valid RLC header */
             proto_tree_add_item(rlc_lte_tree, hf_rlc_lte_predefined_pdu, tvb, offset, -1, FALSE);
+            write_pdu_label_and_info(top_ti, NULL, pinfo, "   [%u-bytes]",
+                                     tvb_length_remaining(tvb, offset));
             break;
 
         default:
@@ -1134,6 +2239,9 @@ void dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
                                    "Unrecognised RLC Mode set (%u)", p_rlc_lte_info->rlcMode);
             break;
     }
+
+    /* Queue tap info */
+    tap_queue_packet(rlc_lte_tap, pinfo, tap_info);
 }
 
 
@@ -1144,20 +2252,36 @@ static void
 rlc_lte_init_protocol(void)
 {
     /* Destroy any existing hashes. */
-    if (rlc_lte_channel_hash) {
-        g_hash_table_destroy(rlc_lte_channel_hash);
+    if (rlc_lte_sequence_analysis_channel_hash) {
+        g_hash_table_destroy(rlc_lte_sequence_analysis_channel_hash);
     }
-
-    if (rlc_lte_frame_report_hash) {
-        g_hash_table_destroy(rlc_lte_frame_report_hash);
+    if (rlc_lte_frame_sequence_analysis_report_hash) {
+        g_hash_table_destroy(rlc_lte_frame_sequence_analysis_report_hash);
+    }
+    if (rlc_lte_repeated_nack_channel_hash) {
+        g_hash_table_destroy(rlc_lte_repeated_nack_channel_hash);
+    }
+    if (rlc_lte_frame_repeated_nack_report_hash) {
+        g_hash_table_destroy(rlc_lte_frame_repeated_nack_report_hash);
     }
 
+
     /* Now create them over */
-    rlc_lte_channel_hash = g_hash_table_new(rlc_channel_hash_func, rlc_channel_equal);
-    rlc_lte_frame_report_hash = g_hash_table_new(rlc_frame_hash_func, rlc_frame_equal);
+    rlc_lte_sequence_analysis_channel_hash = g_hash_table_new(rlc_channel_hash_func, rlc_channel_equal);
+    rlc_lte_frame_sequence_analysis_report_hash = g_hash_table_new(rlc_frame_hash_func, rlc_frame_equal);
+
+    rlc_lte_repeated_nack_channel_hash = g_hash_table_new(rlc_channel_hash_func, rlc_channel_equal);
+    rlc_lte_frame_repeated_nack_report_hash = g_hash_table_new(rlc_frame_hash_func, rlc_frame_equal);
 }
 
 
+/* Configure number of PDCP SN bits to use for DRB channels.
+   TODO: currently assume all UEs/Channels will use the same length... */
+void set_rlc_lte_drb_pdcp_seqnum_length(guint16 ueid _U_, guint8 drbid _U_, guint8 userplane_seqnum_length)
+{
+    signalled_pdcp_sn_bits = userplane_seqnum_length;
+}
+
 
 
 void proto_register_rlc_lte(void)
@@ -1166,10 +2290,16 @@ void proto_register_rlc_lte(void)
     {
         /**********************************/
         /* Items for decoding context     */
+        { &hf_rlc_lte_context,
+            { "Context",
+              "rlc-lte.context", FT_STRING, BASE_NONE, NULL, 0x0,
+              NULL, HFILL
+            }
+        },
         { &hf_rlc_lte_context_mode,
             { "RLC Mode",
               "rlc-lte.mode", FT_UINT8, BASE_DEC, VALS(rlc_mode_vals), 0x0,
-              "RLC Mode", HFILL
+              NULL, HFILL
             }
         },
         { &hf_rlc_lte_context_direction,
@@ -1181,7 +2311,7 @@ void proto_register_rlc_lte(void)
         { &hf_rlc_lte_context_priority,
             { "Priority",
               "rlc-lte.priority", FT_UINT8, BASE_DEC, 0, 0x0,
-              "Priority", HFILL
+              NULL, HFILL
             }
         },
         { &hf_rlc_lte_context_ueid,
@@ -1217,14 +2347,26 @@ void proto_register_rlc_lte(void)
 
 
         /* Transparent mode fields */
+        { &hf_rlc_lte_tm,
+            { "TM",
+              "rlc-lte.tm", FT_STRING, BASE_NONE, NULL, 0x0,
+              "Transparent Mode", HFILL
+            }
+        },
         { &hf_rlc_lte_tm_data,
             { "TM Data",
-              "rlc-lte.tm.data", FT_BYTES, BASE_HEX, 0, 0x0,
+              "rlc-lte.tm.data", FT_BYTES, BASE_NONE, 0, 0x0,
               "Transparent Mode Data", HFILL
             }
         },
 
         /* Unacknowledged mode fields */
+        { &hf_rlc_lte_um,
+            { "UM",
+              "rlc-lte.um", FT_STRING, BASE_NONE, NULL, 0x0,
+              "Unackowledged Mode", HFILL
+            }
+        },
         { &hf_rlc_lte_um_header,
             { "UM Header",
               "rlc-lte.um.header", FT_STRING, BASE_NONE, NULL, 0x0,
@@ -1234,7 +2376,7 @@ void proto_register_rlc_lte(void)
         { &hf_rlc_lte_um_fi,
             { "Framing Info",
               "rlc-lte.um.fi", FT_UINT8, BASE_HEX, VALS(framing_info_vals), 0x0,
-              "Framing Info", HFILL
+              NULL, HFILL
             }
         },
         { &hf_rlc_lte_um_fixed_e,
@@ -1257,14 +2399,14 @@ void proto_register_rlc_lte(void)
         },
         { &hf_rlc_lte_um_data,
             { "UM Data",
-              "rlc-lte.um.data", FT_BYTES, BASE_HEX, 0, 0x0,
+              "rlc-lte.um.data", FT_BYTES, BASE_NONE, 0, 0x0,
               "Unacknowledged Mode Data", HFILL
             }
         },
         { &hf_rlc_lte_extension_part,
             { "Extension Part",
               "rlc-lte.extension-part", FT_STRING, BASE_NONE, 0, 0x0,
-              "Extension Part", HFILL
+              NULL, HFILL
             }
         },
 
@@ -1278,7 +2420,7 @@ void proto_register_rlc_lte(void)
         { &hf_rlc_lte_extension_li,
             { "Length Indicator",
               "rlc-lte.extension.li", FT_UINT16, BASE_DEC, 0, 0x0,
-              "Length Indicator", HFILL
+              NULL, HFILL
             }
         },
         { &hf_rlc_lte_extension_padding,
@@ -1288,9 +2430,14 @@ void proto_register_rlc_lte(void)
             }
         },
 
-
+        { &hf_rlc_lte_am,
+            { "AM",
+              "rlc-lte.am", FT_STRING, BASE_NONE, NULL, 0x0,
+              "Ackowledged Mode", HFILL
+            }
+        },
         { &hf_rlc_lte_am_header,
-            { "UM Header",
+            { "AM Header",
               "rlc-lte.am.header", FT_STRING, BASE_NONE, NULL, 0x0,
               "Ackowledged Mode Header", HFILL
             }
@@ -1310,7 +2457,7 @@ void proto_register_rlc_lte(void)
         { &hf_rlc_lte_am_p,
             { "Polling Bit",
               "rlc-lte.am.p", FT_UINT8, BASE_HEX, VALS(polling_bit_vals), 0x20,
-              "Polling Bit", HFILL
+              NULL, HFILL
             }
         },
         { &hf_rlc_lte_am_fi,
@@ -1327,25 +2474,25 @@ void proto_register_rlc_lte(void)
         },
         { &hf_rlc_lte_am_fixed_sn,
             { "Sequence Number",
-              "rlc-lte.am.fixed.sn", FT_UINT16, BASE_HEX, 0, 0x03ff,
+              "rlc-lte.am.fixed.sn", FT_UINT16, BASE_DEC, 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
+              NULL, HFILL
             }
         },
         { &hf_rlc_lte_am_segment_so,
             { "Segment Offset",
               "rlc-lte.am.segment.offset", FT_UINT16, BASE_DEC, 0, 0x7fff,
-              "Segment Offset", HFILL
+              NULL, HFILL
             }
         },
         { &hf_rlc_lte_am_data,
             { "AM Data",
-              "rlc-lte.am.data", FT_BYTES, BASE_HEX, 0, 0x0,
+              "rlc-lte.am.data", FT_BYTES, BASE_NONE, 0, 0x0,
               "Acknowledged Mode Data", HFILL
             }
         },
@@ -1360,19 +2507,25 @@ void proto_register_rlc_lte(void)
         { &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
+              "Sequence Number we expect to receive next", HFILL
             }
         },
         { &hf_rlc_lte_am_e1,
             { "Extension bit 1",
               "rlc-lte.am.e1", FT_UINT8, BASE_HEX, VALS(am_e1_vals), 0x0,
-              "Extension bit 1", HFILL
+              NULL, 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
+              NULL, HFILL
+            }
+        },
+        { &hf_rlc_lte_am_nacks,
+            { "Number of NACKs",
+              "rlc-lte.am.nacks", FT_UINT16, BASE_DEC, 0, 0x0,
+              "Number of NACKs in this status PDU", HFILL
             }
         },
         { &hf_rlc_lte_am_nack_sn,
@@ -1384,19 +2537,19 @@ void proto_register_rlc_lte(void)
         { &hf_rlc_lte_am_so_start,
             { "SO Start",
               "rlc-lte.am.so-start", FT_UINT16, BASE_DEC, 0, 0x0,
-              "SO Start", HFILL
+              "Segment Offset Start byte index", HFILL
             }
         },
         { &hf_rlc_lte_am_so_end,
             { "SO End",
               "rlc-lte.am.so-end", FT_UINT16, BASE_DEC, 0, 0x0,
-              "SO End", HFILL
+              "Segment Offset End byte index", HFILL
             }
         },
 
         { &hf_rlc_lte_predefined_pdu,
             { "Predefined data",
-              "rlc-lte.predefined-data", FT_BYTES, BASE_HEX, 0, 0x0,
+              "rlc-lte.predefined-data", FT_BYTES, BASE_NONE, 0, 0x0,
               "Predefined test data", HFILL
             }
         },
@@ -1404,25 +2557,87 @@ void proto_register_rlc_lte(void)
         { &hf_rlc_lte_sequence_analysis,
             { "Sequence Analysis",
               "rlc-lte.sequence-analysis", FT_STRING, BASE_NONE, 0, 0x0,
-              "Sequence Analysis", HFILL
+              NULL, HFILL
+            }
+        },
+        { &hf_rlc_lte_sequence_analysis_ok,
+            { "OK",
+              "rlc-lte.sequence-analysis.ok", FT_BOOLEAN, BASE_NONE, 0, 0x0,
+              NULL, HFILL
             }
         },
         { &hf_rlc_lte_sequence_analysis_previous_frame,
             { "Previous frame for channel",
               "rlc-lte.sequence-analysis.previous-frame", FT_FRAMENUM, BASE_NONE, 0, 0x0,
-              "Previous frame for channel", HFILL
+              NULL, HFILL
             }
         },
         { &hf_rlc_lte_sequence_analysis_expected_sn,
             { "Expected SN",
               "rlc-lte.sequence-analysis.expected-sn", FT_UINT16, BASE_DEC, 0, 0x0,
-              "Expected SN", HFILL
+              NULL, HFILL
             }
         },
         { &hf_rlc_lte_sequence_analysis_framing_info_correct,
             { "Frame info continued correctly",
-              "rlc-lte.sequence-analysis.framing-info-correct", FT_UINT8, BASE_DEC, 0, 0x0,
-              "Frame info continued correctly", HFILL
+              "rlc-lte.sequence-analysis.framing-info-correct", FT_BOOLEAN, BASE_NONE, 0, 0x0,
+              NULL, HFILL
+            }
+        },
+        { &hf_rlc_lte_sequence_analysis_mac_retx,
+            { "Frame retransmitted by MAC",
+              "rlc-lte.sequence-analysis.mac-retx", FT_BOOLEAN, BASE_NONE, 0, 0x0,
+              NULL, HFILL
+            }
+        },
+        { &hf_rlc_lte_sequence_analysis_retx,
+            { "Retransmitted frame",
+              "rlc-lte.sequence-analysis.retx", FT_BOOLEAN, BASE_NONE, 0, 0x0,
+              NULL, HFILL
+            }
+        },
+        { &hf_rlc_lte_sequence_analysis_skipped,
+            { "Skipped frames",
+              "rlc-lte.sequence-analysis.skipped-frames", FT_BOOLEAN, BASE_NONE, 0, 0x0,
+              NULL, HFILL
+            }
+        },
+        { &hf_rlc_lte_sequence_analysis_repeated,
+            { "Repeated frame",
+              "rlc-lte.sequence-analysis.repeated-frame", FT_BOOLEAN, BASE_NONE, 0, 0x0,
+              NULL, HFILL
+            }
+        },
+        { &hf_rlc_lte_sequence_analysis_repeated_nack,
+            { "Repeated NACK",
+              "rlc-lte.sequence-analysis.repeated-nack", FT_UINT16, BASE_DEC, 0, 0x0,
+              NULL, HFILL
+            }
+        },
+        { &hf_rlc_lte_sequence_analysis_repeated_nack_original_frame,
+            { "Frame with previous status PDU",
+              "rlc-lte.sequence-analysis.repeated-nack.original-frame",  FT_FRAMENUM, BASE_NONE, 0, 0x0,
+              NULL, HFILL
+            }
+        },
+
+        { &hf_rlc_lte_sequence_analysis_ack_out_of_range,
+            { "Out of range ACK",
+              "rlc-lte.sequence-analysis.ack-out-of-range", FT_BOOLEAN, BASE_NONE, 0, 0x0,
+              NULL, HFILL
+            }
+        },
+        { &hf_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame,
+            { "Frame with most recent SN",
+              "rlc-lte.sequence-analysis.ack-out-of-range.last-sn-frame",  FT_FRAMENUM, BASE_NONE, 0, 0x0,
+              NULL, HFILL
+            }
+        },
+
+        { &hf_rlc_lte_header_only,
+            { "RLC PDU Header only",
+              "rlc-lte.header-only", FT_UINT8, BASE_DEC, VALS(header_only_vals), 0x0,
+              NULL, HFILL
             }
         },
     };
@@ -1430,12 +2645,20 @@ void proto_register_rlc_lte(void)
     static gint *ett[] =
     {
         &ett_rlc_lte,
+        &ett_rlc_lte_context,
         &ett_rlc_lte_um_header,
         &ett_rlc_lte_am_header,
         &ett_rlc_lte_extension_part,
         &ett_rlc_lte_sequence_analysis
     };
 
+    static enum_val_t sequence_analysis_vals[] = {
+        {"no-analysis", "No-Analysis",     FALSE},
+        {"mac-only",    "Only-MAC-frames", SEQUENCE_ANALYSIS_MAC_ONLY},
+        {"rlc-only",    "Only-RLC-frames", SEQUENCE_ANALYSIS_RLC_ONLY},
+        {NULL, NULL, -1}
+    };
+
     module_t *rlc_lte_module;
 
     /* Register protocol. */
@@ -1446,15 +2669,58 @@ void proto_register_rlc_lte(void)
     /* Allow other dissectors to find this one by name. */
     register_dissector("rlc-lte", dissect_rlc_lte, proto_rlc_lte);
 
+    /* Register the tap name */
+    rlc_lte_tap = register_tap("rlc-lte");
+
     /* Preferences */
     rlc_lte_module = prefs_register_protocol(proto_rlc_lte, NULL);
 
-    prefs_register_bool_preference(rlc_lte_module, "do_sequence_analysis",
+    prefs_register_enum_preference(rlc_lte_module, "do_sequence_analysis_am",
+        "Do sequence analysis for AM channels",
+        "Attempt to keep track of PDUs for AM channels, and point out problems",
+        &global_rlc_lte_am_sequence_analysis, sequence_analysis_vals, FALSE);
+
+    prefs_register_enum_preference(rlc_lte_module, "do_sequence_analysis",
         "Do sequence analysis for UM channels",
         "Attempt to keep track of PDUs for UM channels, and point out problems",
-        &global_rlc_lte_sequence_analysis);
+        &global_rlc_lte_um_sequence_analysis, sequence_analysis_vals, FALSE);
+
+    prefs_register_bool_preference(rlc_lte_module, "call_pdcp_for_srb",
+        "Call PDCP dissector for SRB PDUs",
+        "Call PDCP dissector for signalling PDUs.  Note that without reassembly, it can"
+        "only be called for complete PDus (i.e. not segmented over RLC)",
+        &global_rlc_lte_call_pdcp_for_srb);
+
+    prefs_register_enum_preference(rlc_lte_module, "call_pdcp_for_drb",
+        "Call PDCP dissector for DRB PDUs",
+        "Call PDCP dissector for user-plane PDUs.  Note that without reassembly, it can"
+        "only be called for complete PDus (i.e. not segmented over RLC)",
+        &global_rlc_lte_call_pdcp_for_drb, pdcp_drb_col_vals, FALSE);
+
+
+    prefs_register_bool_preference(rlc_lte_module, "call_rrc_for_ccch",
+        "Call RRC dissector for CCCH PDUs",
+        "Call RRC dissector for CCCH PDUs",
+        &global_rlc_lte_call_rrc);
+
+    prefs_register_bool_preference(rlc_lte_module, "heuristic_rlc_lte_over_udp",
+        "Try Heuristic LTE-RLC over UDP framing",
+        "When enabled, use heuristic dissector to find RLC-LTE frames sent with "
+        "UDP framing",
+        &global_rlc_lte_heur);
+
+    prefs_register_bool_preference(rlc_lte_module, "header_only_mode",
+        "May see RLC headers only",
+        "When enabled, if data is not present, don't report as an error, but instead "
+        "add expert info to indicate that headers were omitted",
+        &global_rlc_lte_headers_expected);
 
     register_init_routine(&rlc_lte_init_protocol);
 }
 
-
+void
+proto_reg_handoff_rlc_lte(void)
+{
+    /* Add as a heuristic UDP dissector */
+    heur_dissector_add("udp", dissect_rlc_lte_heur, proto_rlc_lte);
+}