wsgcrypt.h checks internally if we HAVE_LIBGCRYPT
[metze/wireshark/wip.git] / epan / dissectors / packet-pdcp-lte.c
index 1c1f2e84582d61e825b4fabacf86f74f19dc8886..0f4313b7296b637dfaadf40f5bc279458a558597 100644 (file)
@@ -1,9 +1,8 @@
-/* Routines for LTE PDCP
+/* packet-pdcp-lte.c
+ * Routines for LTE PDCP
  *
  * Martin Mathieson
  *
- * $Id$
- *
  * Wireshark - Network traffic analyzer
  * By Gerald Combs <gerald@wireshark.org>
  * Copyright 1998 Gerald Combs
 
 #include "config.h"
 
-#include <string.h>
 
-#include <glib.h>
 #include <epan/packet.h>
 #include <epan/prefs.h>
 #include <epan/expert.h>
-#include <epan/addr_resolv.h>
-#include <epan/wmem/wmem.h>
-
-#ifdef HAVE_LIBGCRYPT
 #include <epan/uat.h>
 #include <wsutil/wsgcrypt.h>
-#endif /* HAVE_LIBGCRYPT */
+
+/* Define this symbol if you have a working implementation of SNOW3G f8() and f9() available.
+   Note that the use of this algorithm is restricted, and that an administrative charge
+   may be applicable if you use it (see e.g. http://www.gsma.com/technicalprojects/fraud-security/security-algorithms).
+   A version of Wireshark with this enabled would not be distributable. */
+/* #define HAVE_SNOW3G */
 
 #include "packet-rlc-lte.h"
 #include "packet-pdcp-lte.h"
@@ -52,9 +50,10 @@ void proto_reg_handoff_pdcp_lte(void);
 
 
 /* TODO:
-   - More deciphering. Next steps are:
-       - separate preferences to control signalling/user-plane decryption?
-       - Verify MAC authentication bytes for supported protocol(s)?
+   - Decipher even if sequence analysis isn't 'OK'?
+      - know SN, but might be unsure about HFN.
+   - Speed up AES decryption by keeping the crypt handle around for the channel
+     (like ESP decryption in IPSEC dissector)
    - Add Relay Node user plane data PDU dissection
 */
 
@@ -99,6 +98,7 @@ static int hf_pdcp_lte_fms = -1;
 static int hf_pdcp_lte_reserved4 = -1;
 static int hf_pdcp_lte_fms2 = -1;
 static int hf_pdcp_lte_bitmap = -1;
+static int hf_pdcp_lte_bitmap_byte = -1;
 
 
 /* Sequence Analysis */
@@ -120,7 +120,8 @@ static int hf_pdcp_lte_security_ciphering_algorithm = -1;
 static int hf_pdcp_lte_security_bearer = -1;
 static int hf_pdcp_lte_security_direction = -1;
 static int hf_pdcp_lte_security_count = -1;
-static int hf_pdcp_lte_security_key = -1;
+static int hf_pdcp_lte_security_cipher_key = -1;
+static int hf_pdcp_lte_security_integrity_key = -1;
 
 
 
@@ -136,8 +137,8 @@ static expert_field ei_pdcp_lte_sequence_analysis_wrong_sequence_number = EI_INI
 static expert_field ei_pdcp_lte_reserved_bits_not_zero = EI_INIT;
 static expert_field ei_pdcp_lte_sequence_analysis_sn_repeated = EI_INIT;
 static expert_field ei_pdcp_lte_sequence_analysis_sn_missing = EI_INIT;
+static expert_field ei_pdcp_lte_digest_wrong = EI_INIT;
 
-#ifdef HAVE_LIBGCRYPT
 /*-------------------------------------
  * UAT for UE Keys
  *-------------------------------------
@@ -145,21 +146,26 @@ static expert_field ei_pdcp_lte_sequence_analysis_sn_missing = EI_INIT;
 /* UAT entry structure. */
 typedef struct {
    guint16 ueid;
-   gchar   *rrcKeyString;
-   gchar   *upKeyString;
-
-   guint8   rrcBinaryKey[16];
-   gboolean rrcKeyOK;
-   guint8   upBinaryKey[16];
-   gboolean upKeyOK;
+   gchar   *rrcCipherKeyString;
+   gchar   *upCipherKeyString;
+   gchar   *rrcIntegrityKeyString;
+
+   guint8   rrcCipherBinaryKey[16];
+   gboolean rrcCipherKeyOK;
+   guint8   upCipherBinaryKey[16];
+   gboolean upCipherKeyOK;
+   guint8   rrcIntegrityBinaryKey[16];
+   gboolean rrcIntegrityKeyOK;
 } uat_ue_keys_record_t;
 
 static uat_ue_keys_record_t *uat_ue_keys_records = NULL;
 
+/* Entries added by UAT */
 static uat_t * ue_keys_uat = NULL;
 static guint num_ue_keys_uat = 0;
 
-/* TODO: do this better, being tolerant of spaces and dashes... */
+/* Convert an ascii hex character into a digit.  Should only be given valid
+   hex ascii characters */
 static guchar hex_ascii_to_binary(gchar c)
 {
     if ((c >= '0') && (c <= '9')) {
@@ -175,62 +181,168 @@ static guchar hex_ascii_to_binary(gchar c)
         return 0;
 }
 
-
 static void* uat_ue_keys_record_copy_cb(void* n, const void* o, size_t siz _U_) {
     uat_ue_keys_record_t* new_rec = (uat_ue_keys_record_t *)n;
     const uat_ue_keys_record_t* old_rec = (const uat_ue_keys_record_t *)o;
 
     new_rec->ueid = old_rec->ueid;
-    new_rec->rrcKeyString = (old_rec->rrcKeyString) ? g_strdup(old_rec->rrcKeyString) : NULL;
-    new_rec->upKeyString = (old_rec->upKeyString) ? g_strdup(old_rec->upKeyString) : NULL;
+    new_rec->rrcCipherKeyString = (old_rec->rrcCipherKeyString) ? g_strdup(old_rec->rrcCipherKeyString) : NULL;
+    new_rec->upCipherKeyString = (old_rec->upCipherKeyString) ? g_strdup(old_rec->upCipherKeyString) : NULL;
+    new_rec->rrcIntegrityKeyString = (old_rec->rrcIntegrityKeyString) ? g_strdup(old_rec->rrcIntegrityKeyString) : NULL;
 
     return new_rec;
 }
 
-static void uat_ue_keys_record_update_cb(void* record, const char** error _U_) {
-    uat_ue_keys_record_t* rec = (uat_ue_keys_record_t *)record;
-    int n;
+/* If raw_string is a valid key, set check_string & return TRUE */
+static gboolean check_valid_key_sring(const char* raw_string, char* checked_string)
+{
+    guint n;
+    guint written = 0;
+    guint length = (gint)strlen(raw_string);
 
-    /* Check and convert RRC key */
-    if (strlen(rec->rrcKeyString) != 32) {
-        rec->rrcKeyOK = FALSE;
+    /* Can't be valid if not long enough. */
+    if (length < 32) {
+        return FALSE;
     }
-    else {
-        for (n=0; n < 32; n += 2) {
-            rec->rrcBinaryKey[n/2] = (hex_ascii_to_binary(rec->rrcKeyString[n]) << 4) +
-                                      hex_ascii_to_binary(rec->rrcKeyString[n+1]);
+
+    for (n=0; (n < length) && (written < 32); n++) {
+        char c = raw_string[n];
+
+        /* Skipping past allowed 'padding' characters */
+        if ((c == ' ') || (c == '-')) {
+            continue;
+        }
+
+        /* Other characters must be hex digits, otherwise string is invalid */
+        if (((c >= '0') && (c <= '9')) ||
+            ((c >= 'a') && (c <= 'f')) ||
+            ((c >= 'A') && (c <= 'F'))) {
+            checked_string[written++] = c;
+        }
+        else {
+            return FALSE;
         }
-        rec->rrcKeyOK = TRUE;
     }
 
-    /* Check and convert User-plane key */
-    if (strlen(rec->upKeyString) != 32) {
-        rec->rrcKeyOK = FALSE;
+    /* Must have found exactly 32 hex ascii chars for 16-byte key */
+    return (written == 32);
+}
+
+static void update_key_from_string(const char *stringKey, guint8 *binaryKey, gboolean *pKeyOK)
+{
+    int  n;
+    char cleanString[32];
+
+    if (!check_valid_key_sring(stringKey, cleanString)) {
+        *pKeyOK = FALSE;
     }
     else {
         for (n=0; n < 32; n += 2) {
-            rec->upBinaryKey[n/2] = (hex_ascii_to_binary(rec->upKeyString[n]) << 4) +
-                                     hex_ascii_to_binary(rec->upKeyString[n+1]);
+            binaryKey[n/2] = (hex_ascii_to_binary(cleanString[n]) << 4) +
+                              hex_ascii_to_binary(cleanString[n+1]);
         }
-        rec->upKeyOK = TRUE;
+        *pKeyOK = TRUE;
     }
 }
 
+/* Update by checking whether the 3 key strings are valid or not, and storing result */
+static gboolean uat_ue_keys_record_update_cb(void* record, char** error _U_) {
+    uat_ue_keys_record_t* rec = (uat_ue_keys_record_t *)record;
+
+    /* Check and convert RRC key */
+    update_key_from_string(rec->rrcCipherKeyString, rec->rrcCipherBinaryKey, &rec->rrcCipherKeyOK);
+
+    /* Check and convert User-plane key */
+    update_key_from_string(rec->upCipherKeyString, rec->upCipherBinaryKey, &rec->upCipherKeyOK);
 
+    /* Check and convert Integrity key */
+    update_key_from_string(rec->rrcIntegrityKeyString, rec->rrcIntegrityBinaryKey, &rec->rrcIntegrityKeyOK);
+
+    return TRUE;
+}
+
+/* Free heap parts of record */
 static void uat_ue_keys_record_free_cb(void*r) {
     uat_ue_keys_record_t* rec = (uat_ue_keys_record_t*)r;
 
-    g_free(rec->rrcKeyString);
-    g_free(rec->upKeyString);
+    g_free(rec->rrcCipherKeyString);
+    g_free(rec->upCipherKeyString);
+    g_free(rec->rrcIntegrityKeyString);
 }
 
 UAT_DEC_CB_DEF(uat_ue_keys_records, ueid, uat_ue_keys_record_t)
-UAT_CSTRING_CB_DEF(uat_ue_keys_records, rrcKeyString, uat_ue_keys_record_t)
-UAT_CSTRING_CB_DEF(uat_ue_keys_records, upKeyString,  uat_ue_keys_record_t)
+UAT_CSTRING_CB_DEF(uat_ue_keys_records, rrcCipherKeyString, uat_ue_keys_record_t)
+UAT_CSTRING_CB_DEF(uat_ue_keys_records, upCipherKeyString,  uat_ue_keys_record_t)
+UAT_CSTRING_CB_DEF(uat_ue_keys_records, rrcIntegrityKeyString,  uat_ue_keys_record_t)
+
+
+/* Also supporting a hash table with entries from these functions */
+
+/* Table from ueid -> uat_ue_keys_record_t* */
+static GHashTable *pdcp_security_key_hash = NULL;
+
+
+void set_pdcp_lte_rrc_ciphering_key(guint16 ueid, const char *key)
+{
+    /* Get or create struct for this UE */
+    uat_ue_keys_record_t *key_record = (uat_ue_keys_record_t*)g_hash_table_lookup(pdcp_security_key_hash,
+                                                                                  GUINT_TO_POINTER((guint)ueid));
+    if (key_record == NULL) {
+        /* Create and add to table */
+        key_record = wmem_new0(wmem_file_scope(), uat_ue_keys_record_t);
+        key_record->ueid = ueid;
+        g_hash_table_insert(pdcp_security_key_hash, GUINT_TO_POINTER((guint)ueid), key_record);
+    }
+
+    /* Check and convert RRC key */
+    key_record->rrcCipherKeyString = g_strdup(key);
+    update_key_from_string(key_record->rrcCipherKeyString, key_record->rrcCipherBinaryKey, &key_record->rrcCipherKeyOK);}
+
+void set_pdcp_lte_rrc_integrity_key(guint16 ueid, const char *key)
+{
+    /* Get or create struct for this UE */
+    uat_ue_keys_record_t *key_record = (uat_ue_keys_record_t*)g_hash_table_lookup(pdcp_security_key_hash,
+                                                                                  GUINT_TO_POINTER((guint)ueid));
+    if (key_record == NULL) {
+        /* Create and add to table */
+        key_record = wmem_new0(wmem_file_scope(), uat_ue_keys_record_t);
+        key_record->ueid = ueid;
+        g_hash_table_insert(pdcp_security_key_hash, GUINT_TO_POINTER((guint)ueid), key_record);
+    }
+
+    /* Check and convert RRC integrity key */
+    key_record->rrcIntegrityKeyString = g_strdup(key);
+    update_key_from_string(key_record->rrcIntegrityKeyString, key_record->rrcIntegrityBinaryKey, &key_record->rrcIntegrityKeyOK);
+}
+
+void set_pdcp_lte_up_ciphering_key(guint16 ueid, const char *key)
+{
+    /* Get or create struct for this UE */
+    uat_ue_keys_record_t *key_record = (uat_ue_keys_record_t*)g_hash_table_lookup(pdcp_security_key_hash,
+                                                                                  GUINT_TO_POINTER((guint)ueid));
+    if (key_record == NULL) {
+        /* Create and add to table */
+        key_record = wmem_new0(wmem_file_scope(), uat_ue_keys_record_t);
+        key_record->ueid = ueid;
+        g_hash_table_insert(pdcp_security_key_hash, GUINT_TO_POINTER((guint)ueid), key_record);
+    }
+
+    /* Check and convert UP key */
+    key_record->upCipherKeyString = g_strdup(key);
+    update_key_from_string(key_record->upCipherKeyString, key_record->upCipherBinaryKey, &key_record->upCipherKeyOK);
+}
+
+
+/* Preference settings for deciphering and integrity checking.  Currently all default to off */
+static gboolean global_pdcp_decipher_signalling = TRUE;
+static gboolean global_pdcp_decipher_userplane = FALSE;  /* Can be slow, so default to FALSE */
+static gboolean global_pdcp_check_integrity = TRUE;
+
+/* Use these values where we know the keys but may have missed the algorithm,
+   e.g. when handing over and RRCReconfigurationRequest goes to target cell only */
+static enum security_ciphering_algorithm_e global_default_ciphering_algorithm = eea0;
+static enum security_integrity_algorithm_e global_default_integrity_algorithm = eia0;
 
-static gboolean global_pdcp_decipher_signalling = FALSE;
-static gboolean global_pdcp_decipher_userplane = FALSE;
-#endif
 
 static const value_string direction_vals[] =
 {
@@ -300,6 +412,7 @@ static const value_string integrity_algorithm_vals[] = {
     { 0,   "EIA0" },
     { 1,   "EIA1" },
     { 2,   "EIA2" },
+    { 3,   "EIA3" },
     { 0,   NULL }
 };
 
@@ -307,6 +420,7 @@ static const value_string ciphering_algorithm_vals[] = {
     { 0,   "EEA0" },
     { 1,   "EEA1" },
     { 2,   "EEA2" },
+    { 3,   "EEA3" },
     { 0,   NULL }
 };
 
@@ -361,20 +475,6 @@ typedef struct
    Maps key -> status */
 static GHashTable *pdcp_sequence_analysis_channel_hash = NULL;
 
-/* Equal keys */
-static gint pdcp_channel_equal(gconstpointer v, gconstpointer v2)
-{
-    /* Key fits in 4 bytes, so just compare pointers! */
-    return (v == v2);
-}
-
-/* Compute a hash value for a given key. */
-static guint pdcp_channel_hash_func(gconstpointer v)
-{
-    /* Just use pointer, as the fields are all in this value */
-    return GPOINTER_TO_UINT(v);
-}
-
 
 /* Hash table types & functions for frame reports */
 
@@ -473,15 +573,39 @@ static GHashTable *pdcp_lte_sequence_analysis_report_hash = NULL;
 /* Gather together security settings in order to be able to do deciphering */
 typedef struct pdu_security_settings_t
 {
-    gboolean valid;
     enum security_ciphering_algorithm_e ciphering;
-    guint8* key;
+    enum security_integrity_algorithm_e integrity;
+    guint8* cipherKey;
+    guint8* integrityKey;
+    gboolean cipherKeyValid;
+    gboolean integrityKeyValid;
     guint32 count;
     guint8  bearer;
     guint8  direction;
 } pdu_security_settings_t;
 
 
+static uat_ue_keys_record_t* look_up_keys_record(guint16 ueid)
+{
+    unsigned int record_id;
+    /* Try hash table first */
+    uat_ue_keys_record_t* key_record = (uat_ue_keys_record_t*)g_hash_table_lookup(pdcp_security_key_hash,
+                                                                                  GUINT_TO_POINTER((guint)ueid));
+    if (key_record != NULL) {
+        return key_record;
+    }
+
+    /* Else look up UAT entries */
+    for (record_id=0; record_id < num_ue_keys_uat; record_id++) {
+        if (uat_ue_keys_records[record_id].ueid == ueid) {
+            return &uat_ue_keys_records[record_id];
+        }
+    }
+
+    /* No match at all - return NULL */
+    return NULL;
+}
+
 /* Add to the tree values associated with sequence analysis for this frame */
 static void addChannelSequenceInfo(pdcp_sequence_report_in_frame *p,
                                    pdcp_lte_info *p_pdcp_lte_info,
@@ -494,6 +618,7 @@ static void addChannelSequenceInfo(pdcp_sequence_report_in_frame *p,
     proto_item *seqnum_ti;
     proto_item *ti_expected_sn;
     proto_item *ti;
+    uat_ue_keys_record_t *keys_record;
 
     /* Create subtree */
     seqnum_ti = proto_tree_add_string_format(tree,
@@ -543,13 +668,13 @@ static void addChannelSequenceInfo(pdcp_sequence_report_in_frame *p,
             }
 
             /* May also be able to add key inputs to security tree here */
-            if (pdu_security->ciphering != eea0) {
+            if ((pdu_security->ciphering != eea0) ||
+                (pdu_security->integrity != eia0)) {
                 guint32              hfn_multiplier;
                 guint32              count;
-#if HAVE_LIBGCRYPT
-                gchar                *key = NULL;
-                guint                record_id;
-#endif
+                gchar                *cipher_key = NULL;
+                gchar                *integrity_key = NULL;
+
                 /* BEARER */
                 ti = proto_tree_add_uint(security_tree, hf_pdcp_lte_security_bearer,
                                          tvb, 0, 0, p_pdcp_lte_info->channelId-1);
@@ -570,7 +695,7 @@ static void addChannelSequenceInfo(pdcp_sequence_report_in_frame *p,
                         hfn_multiplier = 128;
                         break;
                     case PDCP_SN_LENGTH_12_BITS:
-                        hfn_multiplier = 2048;
+                        hfn_multiplier = 4096;
                         break;
                     case PDCP_SN_LENGTH_15_BITS:
                         hfn_multiplier = 32768;
@@ -585,35 +710,46 @@ static void addChannelSequenceInfo(pdcp_sequence_report_in_frame *p,
                 PROTO_ITEM_SET_GENERATED(ti);
                 pdu_security->count = count;
 
-#if HAVE_LIBGCRYPT
-                /* KEY */
-                for (record_id=0; record_id < num_ue_keys_uat; record_id++) {
-                    if (uat_ue_keys_records[record_id].ueid == p_pdcp_lte_info->ueid) {
-                        if (p_pdcp_lte_info->plane == SIGNALING_PLANE) {
-                            if (uat_ue_keys_records[record_id].rrcKeyOK) {
-                                key = uat_ue_keys_records[record_id].rrcKeyString;
-                                pdu_security->key = &(uat_ue_keys_records[record_id].rrcBinaryKey[0]);
-                                pdu_security->valid = TRUE;
-                            }
+                /* KEY.  Look this UE up among UEs that have keys configured */
+                keys_record = look_up_keys_record(p_pdcp_lte_info->ueid);
+                if (keys_record != NULL) {
+                    if (p_pdcp_lte_info->plane == SIGNALING_PLANE) {
+                        /* Get RRC ciphering key */
+                        if (keys_record->rrcCipherKeyOK) {
+                            cipher_key = keys_record->rrcCipherKeyString;
+                            pdu_security->cipherKey = &(keys_record->rrcCipherBinaryKey[0]);
+                            pdu_security->cipherKeyValid = TRUE;
                         }
-                        else {
-                            if (uat_ue_keys_records[record_id].upKeyOK) {
-                                key = uat_ue_keys_records[record_id].upKeyString;
-                                pdu_security->key = &(uat_ue_keys_records[record_id].upBinaryKey[0]);
-                                pdu_security->valid = TRUE;
-                            }
+                        /* Get RRC integrity key */
+                        if (keys_record->rrcIntegrityKeyOK) {
+                            integrity_key = keys_record->rrcIntegrityKeyString;
+                            pdu_security->integrityKey = &(keys_record->rrcIntegrityBinaryKey[0]);
+                            pdu_security->integrityKeyValid = TRUE;
                         }
-
-                        if (key != NULL) {
-                            ti = proto_tree_add_string(security_tree, hf_pdcp_lte_security_key,
-                                                       tvb, 0, 0, key);
-                            PROTO_ITEM_SET_GENERATED(ti);
+                    }
+                    else {
+                        /* Get userplane ciphering key */
+                        if (keys_record->upCipherKeyOK) {
+                            cipher_key = keys_record->upCipherKeyString;
+                            pdu_security->cipherKey = &(keys_record->upCipherBinaryKey[0]);
+                            pdu_security->cipherKeyValid = TRUE;
                         }
-                        break;
                     }
+
+                    /* Show keys where known and valid */
+                    if (cipher_key != NULL) {
+                        ti = proto_tree_add_string(security_tree, hf_pdcp_lte_security_cipher_key,
+                                                   tvb, 0, 0, cipher_key);
+                        PROTO_ITEM_SET_GENERATED(ti);
+                    }
+                    if (integrity_key != NULL) {
+                        ti = proto_tree_add_string(security_tree, hf_pdcp_lte_security_integrity_key,
+                                                   tvb, 0, 0, integrity_key);
+                        PROTO_ITEM_SET_GENERATED(ti);
+                    }
+
+                    pdu_security->direction = p_pdcp_lte_info->direction;
                 }
-#endif
-                pdu_security->direction = p_pdcp_lte_info->direction;
             }
             break;
 
@@ -887,13 +1023,13 @@ static gpointer get_ueid_frame_hash_key(guint16 ueid, guint32 frameNumber,
 
 static gint pdcp_lte_ueid_frame_hash_equal(gconstpointer v, gconstpointer v2)
 {
-    ueid_frame_t *ueid_frame_1 = (ueid_frame_t *)v;
-    ueid_frame_t *ueid_frame_2 = (ueid_frame_t *)v2;
+    const ueid_frame_t *ueid_frame_1 = (const ueid_frame_t *)v;
+    const ueid_frame_t *ueid_frame_2 = (const ueid_frame_t *)v2;
     return ((ueid_frame_1->framenum == ueid_frame_2->framenum) && (ueid_frame_1->ueid == ueid_frame_2->ueid));
 }
 static guint pdcp_lte_ueid_frame_hash_func(gconstpointer v)
 {
-    ueid_frame_t *ueid_frame = (ueid_frame_t *)v;
+    const ueid_frame_t *ueid_frame = (const ueid_frame_t *)v;
     return ueid_frame->framenum + 100*ueid_frame->ueid;
 }
 static GHashTable *pdcp_security_result_hash = NULL;
@@ -1093,9 +1229,6 @@ static dissector_handle_t lookup_rrc_dissector_handle(struct pdcp_lte_info  *p_p
 /* Forwad declarations */
 static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);
 
-/* Heuristic dissection */
-static gboolean global_pdcp_lte_heur = FALSE;
-
 /* Heuristic dissector looks for supported framing protocol (see wiki page)  */
 static gboolean dissect_pdcp_lte_heur(tvbuff_t *tvb, packet_info *pinfo,
                                      proto_tree *tree, void *data _U_)
@@ -1107,15 +1240,6 @@ static gboolean dissect_pdcp_lte_heur(tvbuff_t *tvb, packet_info *pinfo,
     gboolean              infoAlreadySet         = FALSE;
     gboolean              seqnumLengthTagPresent = 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_pdcp_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:
@@ -1123,7 +1247,7 @@ static gboolean dissect_pdcp_lte_heur(tvbuff_t *tvb, packet_info *pinfo,
        - fixed header bytes
        - tag for data
        - at least one byte of PDCP PDU payload */
-    if (tvb_length_remaining(tvb, offset) < (gint)(strlen(PDCP_LTE_START_STRING)+3+2)) {
+    if (tvb_captured_length_remaining(tvb, offset) < (gint)(strlen(PDCP_LTE_START_STRING)+3+2)) {
         return FALSE;
     }
 
@@ -1149,6 +1273,9 @@ static gboolean dissect_pdcp_lte_heur(tvbuff_t *tvb, packet_info *pinfo,
     /* Read fixed fields */
     p_pdcp_lte_info->no_header_pdu = (gboolean)tvb_get_guint8(tvb, offset++);
     p_pdcp_lte_info->plane = (enum pdcp_plane)tvb_get_guint8(tvb, offset++);
+    if (p_pdcp_lte_info->plane == SIGNALING_PLANE) {
+        p_pdcp_lte_info->seqnum_length = PDCP_SN_LENGTH_5_BITS;
+    }
     p_pdcp_lte_info->rohc.rohc_compression = (gboolean)tvb_get_guint8(tvb, offset++);
 
     /* Read optional fields */
@@ -1242,35 +1369,93 @@ static gboolean dissect_pdcp_lte_heur(tvbuff_t *tvb, packet_info *pinfo,
 /* Called from control protocol to configure security algorithms for the given UE */
 void set_pdcp_lte_security_algorithms(guint16 ueid, pdcp_security_info_t *security_info)
 {
-    /* Copy security struct */
-    pdcp_security_info_t *p_security = wmem_new(wmem_file_scope(), pdcp_security_info_t);
-    *p_security = *security_info;
+    /* Use for this frame so can check integrity on SecurityCommandRequest frame */
+    /* N.B. won't work for internal, non-RRC signalling methods... */
+    pdcp_security_info_t *p_frame_security;
+
+    /* Create or update current settings, by UEID */
+    pdcp_security_info_t* ue_security =
+        (pdcp_security_info_t*)g_hash_table_lookup(pdcp_security_hash,
+                                                   GUINT_TO_POINTER((guint)ueid));
+    if (ue_security == NULL) {
+        /* Copy whole security struct */
+        ue_security = wmem_new(wmem_file_scope(), pdcp_security_info_t);
+        *ue_security = *security_info;
+
+        /* And add into security table */
+        g_hash_table_insert(pdcp_security_hash, GUINT_TO_POINTER((guint)ueid), ue_security);
+    }
+    else {
+        /* Just update existing entry already in table */
+        ue_security->previous_configuration_frame = ue_security->configuration_frame;
+        ue_security->previous_integrity = ue_security->integrity;
+        ue_security->previous_ciphering = ue_security->ciphering;
+
+        ue_security->configuration_frame = security_info->configuration_frame;
+        ue_security->integrity = security_info->integrity;
+        ue_security->ciphering = security_info->ciphering;
+        ue_security->seen_next_ul_pdu = FALSE;
+    }
+
+    /* Also add an entry for this PDU already to use these settings, as otherwise it won't be present
+       when we query it on the first pass. */
+    p_frame_security = wmem_new(wmem_file_scope(), pdcp_security_info_t);
+    *p_frame_security = *ue_security;
+    g_hash_table_insert(pdcp_security_result_hash,
+                        get_ueid_frame_hash_key(ueid, ue_security->configuration_frame, TRUE),
+                        p_frame_security);
+}
 
-    /* And add into security table */
-    g_hash_table_insert(pdcp_security_hash, GUINT_TO_POINTER((guint)ueid), p_security);
+/* UE failed to process SecurityModeCommand so go back to previous security settings */
+void set_pdcp_lte_security_algorithms_failed(guint16 ueid)
+{
+    /* Look up current state by UEID */
+    pdcp_security_info_t* ue_security =
+        (pdcp_security_info_t*)g_hash_table_lookup(pdcp_security_hash,
+                                                   GUINT_TO_POINTER((guint)ueid));
+    if (ue_security != NULL) {
+        /* TODO: could remove from table if previous_configuration_frame is 0 */
+        /* Go back to previous state */
+        ue_security->configuration_frame = ue_security->previous_configuration_frame;
+        ue_security->integrity = ue_security->previous_integrity;
+        ue_security->ciphering = ue_security->previous_ciphering;
+    }
 }
 
-#if HAVE_LIBGCRYPT
 /* Decipher payload if algorithm is supported and plausible inputs are available */
 static tvbuff_t *decipher_payload(tvbuff_t *tvb, packet_info *pinfo, int *offset,
                                   pdu_security_settings_t *pdu_security_settings,
-                                  enum pdcp_plane plane, gboolean *deciphered)
+                                  enum pdcp_plane plane, gboolean will_be_deciphered,
+                                  gboolean *deciphered)
 {
-    unsigned char ctr_block[16];
-    gcry_cipher_hd_t cypher_hd;
-    int gcrypt_err;
-    guint8* encrypted_data;
-    guint8* decrypted_data;
-    gint payload_length;
+    guint8* decrypted_data = NULL;
+    gint payload_length = 0;
     tvbuff_t *decrypted_tvb;
 
-    /* Nothing to do if no ciphering algorithm was specified */
-    if (!pdu_security_settings->valid) {
+    /* Nothing to do if NULL ciphering */
+    if (pdu_security_settings->ciphering == eea0) {
         return tvb;
     }
 
-    /* Only EEA2 supported at the moment */
-    if (pdu_security_settings->ciphering != eea2) {
+    /* Nothing to do if don't have valid cipher key */
+    if (!pdu_security_settings->cipherKeyValid) {
+        return tvb;
+    }
+
+    /* Check whether algorithm supported (only drop through and process if we do) */
+    if (pdu_security_settings->ciphering == eea1) {
+#ifndef HAVE_SNOW3G
+        return tvb;
+#endif
+    }
+    else
+    if (pdu_security_settings->ciphering == eea2) {
+#ifndef HAVE_LIBGCRYPT
+        return tvb;
+#endif
+    }
+    else {
+        /* An algorithm we don't support at all! */
         return tvb;
     }
 
@@ -1280,74 +1465,217 @@ static tvbuff_t *decipher_payload(tvbuff_t *tvb, packet_info *pinfo, int *offset
         return tvb;
     }
 
-    /* Set CTR */
-    memset(ctr_block, 0, 16);
-    /* Only first 5 bytes set */
-    ctr_block[0] = (pdu_security_settings->count & 0xff000000) >> 24;
-    ctr_block[1] = (pdu_security_settings->count & 0x00ff0000) >> 16;
-    ctr_block[2] = (pdu_security_settings->count & 0x0000ff00) >> 8;
-    ctr_block[3] = (pdu_security_settings->count & 0x000000ff);
-    ctr_block[4] = (pdu_security_settings->bearer << 3) + (pdu_security_settings->direction << 2);
-
-    /* Open gcrypt handle */
-    gcrypt_err = gcry_cipher_open(&cypher_hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, 0);
-    if (gcrypt_err != 0) {
+    /* Don't decipher control messages */
+    if ((plane == USER_PLANE) && ((tvb_get_guint8(tvb, 0) & 0x80) == 0x00)) {
         return tvb;
     }
 
-    /* Set the key */
-    gcrypt_err = gcry_cipher_setkey(cypher_hd, pdu_security_settings->key, 16);
-    if (gcrypt_err != 0) {
+    /* Don't decipher if not yet past SecurityModeResponse */
+    if (!will_be_deciphered) {
         return tvb;
     }
 
-    /* Set the CTR */
-    gcrypt_err = gcry_cipher_setctr(cypher_hd, ctr_block, 16);
-    if (gcrypt_err != 0) {
-        return tvb;
-    }
+#ifdef HAVE_LIBGCRYPT
+    /* AES */
+    if (pdu_security_settings->ciphering == eea2) {
+        unsigned char ctr_block[16];
+        gcry_cipher_hd_t cypher_hd;
+        int gcrypt_err;
+
+        /* Set CTR */
+        memset(ctr_block, 0, 16);
+        /* Only first 5 bytes set */
+        ctr_block[0] = (pdu_security_settings->count & 0xff000000) >> 24;
+        ctr_block[1] = (pdu_security_settings->count & 0x00ff0000) >> 16;
+        ctr_block[2] = (pdu_security_settings->count & 0x0000ff00) >> 8;
+        ctr_block[3] = (pdu_security_settings->count & 0x000000ff);
+        ctr_block[4] = (pdu_security_settings->bearer << 3) + (pdu_security_settings->direction << 2);
+
+        /* Open gcrypt handle */
+        gcrypt_err = gcry_cipher_open(&cypher_hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, 0);
+        if (gcrypt_err != 0) {
+            return tvb;
+        }
 
-    /* Extract the encrypted data into a buffer */
-    payload_length = tvb_length_remaining(tvb, *offset);
-    encrypted_data = (guint8 *)g_malloc0(payload_length);
-    tvb_memcpy(tvb, encrypted_data, *offset, payload_length);
+        /* Set the key */
+        gcrypt_err = gcry_cipher_setkey(cypher_hd, pdu_security_settings->cipherKey, 16);
+        if (gcrypt_err != 0) {
+            gcry_cipher_close(cypher_hd);
+            return tvb;
+        }
 
-    /* Allocate memory to receive decrypted payload */
-    decrypted_data = (guint8 *)g_malloc0(payload_length);
+        /* Set the CTR */
+        gcrypt_err = gcry_cipher_setctr(cypher_hd, ctr_block, 16);
+        if (gcrypt_err != 0) {
+            gcry_cipher_close(cypher_hd);
+            return tvb;
+        }
 
-    /* Decrypt the actual data */
-    gcrypt_err = gcry_cipher_decrypt(cypher_hd,
-                                     decrypted_data, payload_length,
-                                     encrypted_data, payload_length);
-    if (gcrypt_err != 0) {
-        return tvb;
+        /* Extract the encrypted data into a buffer */
+        payload_length = tvb_captured_length_remaining(tvb, *offset);
+        decrypted_data = (guint8 *)g_malloc0(payload_length);
+        tvb_memcpy(tvb, decrypted_data, *offset, payload_length);
+
+        /* Decrypt the actual data */
+        gcrypt_err = gcry_cipher_decrypt(cypher_hd,
+                                         decrypted_data, payload_length,
+                                         NULL, 0);
+        if (gcrypt_err != 0) {
+            gcry_cipher_close(cypher_hd);
+            g_free(decrypted_data);
+            return tvb;
+        }
+
+        /* Close gcrypt handle */
+        gcry_cipher_close(cypher_hd);
     }
+#endif
 
-    /* Close gcrypt handle */
-    gcry_cipher_close(cypher_hd);
+#ifdef HAVE_SNOW3G
+    /* SNOW-3G */
+    if (pdu_security_settings->ciphering == eea1) {
+        /* Extract the encrypted data into a buffer */
+        payload_length = tvb_captured_length_remaining(tvb, *offset);
+        decrypted_data = (guint8 *)g_malloc0(payload_length+4);
+        tvb_memcpy(tvb, decrypted_data, *offset, payload_length);
+
+        /* Do the algorithm */
+        snow3g_f8(pdu_security_settings->cipherKey,
+                  pdu_security_settings->count,
+                  pdu_security_settings->bearer,
+                  pdu_security_settings->direction,
+                  decrypted_data, payload_length*8);
+    }
+#endif
 
     /* Create tvb for resulting deciphered sdu */
     decrypted_tvb = tvb_new_child_real_data(tvb, decrypted_data, payload_length, payload_length);
     tvb_set_free_cb(decrypted_tvb, g_free);
     add_new_data_source(pinfo, decrypted_tvb, "Deciphered Payload");
 
-    /* Free temp buffer */
-    g_free(encrypted_data);
-
     /* Return deciphered data, i.e. beginning of new tvb */
     *offset = 0;
     *deciphered = TRUE;
     return decrypted_tvb;
 }
-#else
-static tvbuff_t *decipher_payload(tvbuff_t *tvb, packet_info *pinfo _U_, int *offset _U_,
-                                  pdu_security_settings_t *pdu_security_settings _U_,
-                                  enum pdcp_plane plane _U_, gboolean *deciphered _U_)
+
+
+/* Try to calculate digest to compare with that found in frame. */
+static guint32 calculate_digest(pdu_security_settings_t *pdu_security_settings, guint8 header _U_,
+                                tvbuff_t *tvb _U_, gint offset _U_, gboolean *calculated)
 {
-    return tvb;
-}
+    *calculated = FALSE;
+
+    if (pdu_security_settings->integrity == eia0) {
+        /* Should be zero in this case */
+        *calculated = TRUE;
+        return 0;
+    }
+
+    /* Can't calculate if don't have valid integrity key */
+    if (!pdu_security_settings->integrityKeyValid) {
+        return 0;
+    }
+
+    /* Can only do if indicated in preferences */
+    if (!global_pdcp_check_integrity) {
+        return 0;
+    }
+
+    switch (pdu_security_settings->integrity) {
+
+#ifdef HAVE_SNOW3G
+        case eia1:
+            {
+                guint8  *mac;
+                gint message_length = tvb_captured_length_remaining(tvb, offset) - 4;
+                guint8 *message_data = (guint8 *)g_malloc0(message_length+5);
+                message_data[0] = header;
+                tvb_memcpy(tvb, message_data+1, offset, message_length);
+
+                mac = (u8*)snow3g_f9(pdu_security_settings->integrityKey,
+                                     pdu_security_settings->count,
+                                     /* 'Fresh' is the bearer bits then zeros */
+                                     pdu_security_settings->bearer << 27,
+                                     pdu_security_settings->direction,
+                                     message_data,
+                                     (message_length+1)*8);
+
+                *calculated = TRUE;
+                g_free(message_data);
+                return ((mac[0] << 24) | (mac[1] << 16) | (mac[2] << 8) | mac[3]);
+            }
+#endif
+
+#if (defined GCRYPT_VERSION_NUMBER) && (GCRYPT_VERSION_NUMBER >= 0x010600)
+        case eia2:
+            {
+                gcry_mac_hd_t mac_hd;
+                int gcrypt_err;
+                gint message_length;
+                guint8 *message_data;
+                guint8  mac[4];
+                size_t read_digest_length = 4;
+
+                /* Open gcrypt handle */
+                gcrypt_err = gcry_mac_open(&mac_hd, GCRY_MAC_CMAC_AES, 0, NULL);
+                if (gcrypt_err != 0) {
+                    return 0;
+                }
+
+                /* Set the key */
+                gcrypt_err = gcry_mac_setkey(mac_hd, pdu_security_settings->integrityKey, 16);
+                if (gcrypt_err != 0) {
+                    gcry_mac_close(mac_hd);
+                    return 0;
+                }
+
+                /* Extract the encrypted data into a buffer */
+                message_length = tvb_captured_length_remaining(tvb, offset) - 4;
+                message_data = (guint8 *)g_malloc0(message_length+9);
+                message_data[0] = (pdu_security_settings->count & 0xff000000) >> 24;
+                message_data[1] = (pdu_security_settings->count & 0x00ff0000) >> 16;
+                message_data[2] = (pdu_security_settings->count & 0x0000ff00) >> 8;
+                message_data[3] = (pdu_security_settings->count & 0x000000ff);
+                message_data[4] = (pdu_security_settings->bearer << 3) + (pdu_security_settings->direction << 2);
+                /* rest of first 8 bytes are left as zeroes... */
+                message_data[8] = header;
+                tvb_memcpy(tvb, message_data+9, offset, message_length);
+
+                /* Pass in the message */
+                gcrypt_err = gcry_mac_write(mac_hd, message_data, message_length+9);
+                if (gcrypt_err != 0) {
+                    gcry_mac_close(mac_hd);
+                    g_free(message_data);
+                    return 0;
+                }
+
+                /* Read out the digest */
+                gcrypt_err = gcry_mac_read(mac_hd, mac, &read_digest_length);
+                if (gcrypt_err != 0) {
+                    gcry_mac_close(mac_hd);
+                    g_free(message_data);
+                    return 0;
+                }
+
+                /* Now close the mac handle */
+                gcry_mac_close(mac_hd);
+
+                g_free(message_data);
+
+                *calculated = TRUE;
+                return ((mac[0] << 24) | (mac[1] << 16) | (mac[2] << 8) | mac[3]);
+            }
 #endif
 
+        default:
+            /* Can't calculate */
+            *calculated = FALSE;
+            return 0;
+    }
+}
+
+
 
 /******************************/
 /* Main dissection function.  */
@@ -1357,7 +1685,6 @@ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree
     proto_tree           *pdcp_tree           = NULL;
     proto_item           *root_ti             = NULL;
     gint                  offset              = 0;
-    gint                  rohc_offset;
     struct pdcp_lte_info *p_pdcp_info;
     tvbuff_t             *rohc_tvb            = NULL;
 
@@ -1370,7 +1697,7 @@ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree
     gboolean payload_deciphered = FALSE;
 
     /* Initialise security settings */
-    pdu_security_settings.valid = FALSE;
+    memset(&pdu_security_settings, 0, sizeof(pdu_security_settings));
 
     /* Set protocol name. */
     col_set_str(pinfo->cinfo, COL_PROTOCOL, "PDCP-LTE");
@@ -1419,15 +1746,30 @@ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree
     if (!pinfo->fd->flags.visited) {
         /* Look up current state by UEID */
         current_security = (pdcp_security_info_t*)g_hash_table_lookup(pdcp_security_hash,
-                                                                                            GUINT_TO_POINTER((guint)p_pdcp_info->ueid));
+                                                                      GUINT_TO_POINTER((guint)p_pdcp_info->ueid));
         if (current_security != NULL) {
             /* Store any result for this frame in the result table */
             pdcp_security_info_t *security_to_store = wmem_new(wmem_file_scope(), pdcp_security_info_t);
+            /* Take a deep copy of the settings */
             *security_to_store = *current_security;
             g_hash_table_insert(pdcp_security_result_hash,
                                 get_ueid_frame_hash_key(p_pdcp_info->ueid, pinfo->fd->num, TRUE),
                                 security_to_store);
         }
+        else {
+            /* No entry added from RRC, but still use configured defaults */
+            if ((global_default_ciphering_algorithm != eea0) ||
+                (global_default_integrity_algorithm != eia0)) {
+                /* Copy algorithms from preference defaults */
+                pdcp_security_info_t *security_to_store = wmem_new0(wmem_file_scope(), pdcp_security_info_t);
+                security_to_store->ciphering = global_default_ciphering_algorithm;
+                security_to_store->integrity = global_default_integrity_algorithm;
+                security_to_store->seen_next_ul_pdu = TRUE;
+                g_hash_table_insert(pdcp_security_result_hash,
+                                    get_ueid_frame_hash_key(p_pdcp_info->ueid, pinfo->fd->num, TRUE),
+                                    security_to_store);
+            }
+        }
     }
 
     /* Show security settings for this PDU */
@@ -1463,6 +1805,7 @@ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree
                                val_to_str_const(pdu_security->integrity, integrity_algorithm_vals, "Unknown"));
 
         pdu_security_settings.ciphering = pdu_security->ciphering;
+        pdu_security_settings.integrity = pdu_security->integrity;
     }
 
 
@@ -1495,7 +1838,7 @@ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree
             write_pdu_label_and_info(root_ti, pinfo, " sn=%-2u ", seqnum);
             offset++;
 
-            if (tvb_length_remaining(tvb, offset) == 0) {
+            if (tvb_captured_length_remaining(tvb, offset) == 0) {
                 /* Only PDCP header was captured, stop dissection here */
                 return;
             }
@@ -1611,13 +1954,13 @@ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree
                             }
 
                             /* Bitmap tree */
-                            if (tvb_length_remaining(tvb, offset) > 0) {
+                            if (tvb_reported_length_remaining(tvb, offset) > 0) {
                                 bitmap_ti = proto_tree_add_item(pdcp_tree, hf_pdcp_lte_bitmap, tvb,
                                                                 offset, -1, ENC_NA);
                                 bitmap_tree = proto_item_add_subtree(bitmap_ti, ett_pdcp_report_bitmap);
 
                                  buff = (gchar *)wmem_alloc(wmem_packet_scope(), BUFF_SIZE);
-                                 len = tvb_length_remaining(tvb, offset);
+                                 len = tvb_reported_length_remaining(tvb, offset);
                                  bit_offset = offset<<3;
                                 /* For each byte... */
                                 for (i=0; i<len; i++) {
@@ -1630,7 +1973,7 @@ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree
                                             not_received++;
                                         }
                                     }
-                                    proto_tree_add_text(bitmap_tree, tvb, bit_offset/8, 1, "%s", buff);
+                                    proto_tree_add_uint_format(bitmap_tree, hf_pdcp_lte_bitmap_byte, tvb, bit_offset/8, 1, bits, "%s", buff);
                                     bit_offset += 8;
                                 }
                             }
@@ -1697,24 +2040,34 @@ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree
 
     /* Check pdu_security_settings - may need to do deciphering before calling
        further dissectors on payload */
-    payload_tvb = decipher_payload(tvb, pinfo, &offset, &pdu_security_settings, p_pdcp_info->plane, &payload_deciphered);
+    payload_tvb = decipher_payload(tvb, pinfo, &offset, &pdu_security_settings, p_pdcp_info->plane,
+                                   pdu_security ? pdu_security->seen_next_ul_pdu: FALSE, &payload_deciphered);
 
     if (p_pdcp_info->plane == SIGNALING_PLANE) {
         guint32 data_length;
         guint32 mac;
+        proto_item *mac_ti;
+        guint32  calculated_digest = 0;
+        gboolean digest_was_calculated = FALSE;
+
+        /* Try to calculate digest so we can check it */
+        if (global_pdcp_check_integrity) {
+            calculated_digest = calculate_digest(&pdu_security_settings, tvb_get_guint8(tvb, 0), payload_tvb,
+                                                 offset, &digest_was_calculated);
+        }
 
         /* RRC data is all but last 4 bytes.
-           Call lte-rrc dissector (according to direction and channel type) */
+           Call lte-rrc dissector (according to direction and channel type) if we have valid data */
         if ((global_pdcp_dissect_signalling_plane_as_rrc) &&
-            ((pdu_security == NULL) || (pdu_security->ciphering == 0) || payload_deciphered || !pdu_security->seen_next_ul_pdu)){
+            ((pdu_security == NULL) || (pdu_security->ciphering == eea0) || payload_deciphered || !pdu_security->seen_next_ul_pdu)) {
             /* Get appropriate dissector handle */
             dissector_handle_t rrc_handle = lookup_rrc_dissector_handle(p_pdcp_info);
 
             if (rrc_handle != 0) {
                 /* Call RRC dissector if have one */
                 tvbuff_t *rrc_payload_tvb = tvb_new_subset(payload_tvb, offset,
-                                                           tvb_length_remaining(payload_tvb, offset) - 4,
-                                                           tvb_length_remaining(payload_tvb, offset) - 4);
+                                                           tvb_captured_length_remaining(payload_tvb, offset) - 4,
+                                                           tvb_reported_length_remaining(payload_tvb, offset) - 4);
                 gboolean was_writable = col_get_writable(pinfo->cinfo);
 
                 /* We always want to see this in the info column */
@@ -1728,7 +2081,7 @@ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree
             else {
                  /* Just show data */
                     proto_tree_add_item(pdcp_tree, hf_pdcp_lte_signalling_data, payload_tvb, offset,
-                                        tvb_length_remaining(tvb, offset) - 4, ENC_NA);
+                                        tvb_reported_length_remaining(tvb, offset) - 4, ENC_NA);
             }
 
             if (!pinfo->fd->flags.visited &&
@@ -1743,33 +2096,45 @@ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree
         else {
             /* Just show as unparsed data */
             proto_tree_add_item(pdcp_tree, hf_pdcp_lte_signalling_data, payload_tvb, offset,
-                                tvb_length_remaining(tvb, offset) - 4, ENC_NA);
+                                tvb_reported_length_remaining(tvb, offset) - 4, ENC_NA);
         }
 
-        data_length = tvb_length_remaining(payload_tvb, offset) - 4;
+        data_length = tvb_reported_length_remaining(payload_tvb, offset) - 4;
         offset += data_length;
 
         /* Last 4 bytes are MAC */
-        mac = tvb_get_ntohl(tvb, offset);
-        proto_tree_add_item(pdcp_tree, hf_pdcp_lte_mac, payload_tvb, offset, 4, ENC_BIG_ENDIAN);
+        mac = tvb_get_ntohl(payload_tvb, offset);
+        mac_ti = proto_tree_add_item(pdcp_tree, hf_pdcp_lte_mac, payload_tvb, offset, 4, ENC_BIG_ENDIAN);
         offset += 4;
 
+        if (digest_was_calculated) {
+            /* Compare what was found with calculated value! */
+            if (mac != calculated_digest) {
+                expert_add_info_format(pinfo, mac_ti, &ei_pdcp_lte_digest_wrong,
+                                       "MAC-I Digest wrong expected %08x but found %08x",
+                                       calculated_digest, mac);
+            }
+            else {
+                proto_item_append_text(mac_ti, " [Matches calculated result]");
+            }
+        }
+
         col_append_fstr(pinfo->cinfo, COL_INFO, " MAC=0x%08x (%u bytes data)",
                         mac, data_length);
     }
-    else {
+    else if (tvb_captured_length_remaining(payload_tvb, offset)) {
         /* User-plane payload here */
 
         /* If not compressed with ROHC, show as user-plane data */
         if (!p_pdcp_info->rohc.rohc_compression) {
-            gint payload_length = tvb_length_remaining(payload_tvb, offset);
+            gint payload_length = tvb_reported_length_remaining(payload_tvb, offset);
             if (payload_length > 0) {
                 if (p_pdcp_info->plane == USER_PLANE) {
 
                     /* Not attempting to decode payload if ciphering is enabled
                        (and NULL ciphering is not being used) */
                     if (global_pdcp_dissect_user_plane_as_ip &&
-                        ((pdu_security == NULL) || (pdu_security->ciphering == 0) || payload_deciphered))
+                        ((pdu_security == NULL) || (pdu_security->ciphering == eea0) || payload_deciphered))
                     {
                         tvbuff_t *ip_payload_tvb = tvb_new_subset_remaining(payload_tvb, offset);
 
@@ -1778,7 +2143,7 @@ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree
                             col_set_writable(pinfo->cinfo, FALSE);
                         }
 
-                        switch (tvb_get_guint8(ip_payload_tvb, offset) & 0xf0) {
+                        switch (tvb_get_guint8(ip_payload_tvb, 0) & 0xf0) {
                             case 0x40:
                                 call_dissector_only(ip_handle, ip_payload_tvb, pinfo, pdcp_tree, NULL);
                                 break;
@@ -1825,8 +2190,7 @@ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree
                 return;
             }
 
-            rohc_offset = offset;
-            rohc_tvb = tvb_new_subset_remaining(payload_tvb, rohc_offset);
+            rohc_tvb = tvb_new_subset_remaining(payload_tvb, offset);
 
             /* Only enable writing to column if configured to show ROHC */
             if (global_pdcp_lte_layer_to_show != ShowTrafficLayer) {
@@ -1849,25 +2213,20 @@ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree
  * file is loaded or re-loaded in wireshark */
 static void pdcp_lte_init_protocol(void)
 {
-    /* Destroy any existing hashes. */
-    if (pdcp_sequence_analysis_channel_hash) {
-        g_hash_table_destroy(pdcp_sequence_analysis_channel_hash);
-    }
-    if (pdcp_lte_sequence_analysis_report_hash) {
-        g_hash_table_destroy(pdcp_lte_sequence_analysis_report_hash);
-    }
-    if (pdcp_security_hash) {
-        g_hash_table_destroy(pdcp_security_hash);
-    }
-    if (pdcp_security_result_hash) {
-        g_hash_table_destroy(pdcp_security_result_hash);
-    }
-
-    /* Now create them over */
-    pdcp_sequence_analysis_channel_hash = g_hash_table_new(pdcp_channel_hash_func, pdcp_channel_equal);
+    pdcp_sequence_analysis_channel_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
     pdcp_lte_sequence_analysis_report_hash = g_hash_table_new(pdcp_result_hash_func, pdcp_result_hash_equal);
     pdcp_security_hash = g_hash_table_new(pdcp_lte_ueid_hash_func, pdcp_lte_ueid_hash_equal);
     pdcp_security_result_hash = g_hash_table_new(pdcp_lte_ueid_frame_hash_func, pdcp_lte_ueid_frame_hash_equal);
+    pdcp_security_key_hash = g_hash_table_new(pdcp_lte_ueid_hash_func, pdcp_lte_ueid_hash_equal);
+}
+
+static void pdcp_lte_cleanup_protocol(void)
+{
+    g_hash_table_destroy(pdcp_sequence_analysis_channel_hash);
+    g_hash_table_destroy(pdcp_lte_sequence_analysis_report_hash);
+    g_hash_table_destroy(pdcp_security_hash);
+    g_hash_table_destroy(pdcp_security_result_hash);
+    g_hash_table_destroy(pdcp_security_key_hash);
 }
 
 
@@ -2014,7 +2373,7 @@ void proto_register_pdcp(void)
         },
         { &hf_pdcp_lte_mac,
             { "MAC",
-              "pdcp-lte.mac", FT_UINT32, BASE_HEX_DEC, NULL, 0x0,
+              "pdcp-lte.mac", FT_UINT32, BASE_HEX, NULL, 0x0,
               NULL, HFILL
             }
         },
@@ -2060,7 +2419,12 @@ void proto_register_pdcp(void)
               "Status report bitmap (0=error, 1=OK)", HFILL
             }
         },
-
+        { &hf_pdcp_lte_bitmap_byte,
+            { "Bitmap byte",
+              "pdcp-lte.bitmap.byte", FT_UINT8, BASE_HEX, NULL, 0x0,
+              NULL, HFILL
+            }
+        },
 
         { &hf_pdcp_lte_sequence_analysis,
             { "Sequence Analysis",
@@ -2147,9 +2511,15 @@ void proto_register_pdcp(void)
               NULL, HFILL
             }
         },
-        { &hf_pdcp_lte_security_key,
-            { "KEY",
-              "pdcp-lte.security-config.key", FT_STRING, BASE_NONE, NULL, 0x0,
+        { &hf_pdcp_lte_security_cipher_key,
+            { "CIPHER KEY",
+              "pdcp-lte.security-config.cipher-key", FT_STRING, BASE_NONE, NULL, 0x0,
+              NULL, HFILL
+            }
+        },
+        { &hf_pdcp_lte_security_integrity_key,
+            { "INTEGRITY KEY",
+              "pdcp-lte.security-config.integrity-key", FT_STRING, BASE_NONE, NULL, 0x0,
               NULL, HFILL
             }
         },
@@ -2170,6 +2540,7 @@ void proto_register_pdcp(void)
         { &ei_pdcp_lte_sequence_analysis_sn_repeated, { "pdcp-lte.sequence-analysis.sn-repeated", PI_SEQUENCE, PI_WARN, "PDCP SN repeated", EXPFILL }},
         { &ei_pdcp_lte_sequence_analysis_wrong_sequence_number, { "pdcp-lte.sequence-analysis.wrong-sequence-number", PI_SEQUENCE, PI_WARN, "Wrong Sequence Number", EXPFILL }},
         { &ei_pdcp_lte_reserved_bits_not_zero, { "pdcp-lte.reserved-bits-not-zero", PI_MALFORMED, PI_ERROR, "Reserved bits not zero", EXPFILL }},
+        { &ei_pdcp_lte_digest_wrong, { "pdcp-lte.maci-wrong", PI_SEQUENCE, PI_ERROR, "MAC-I doesn't match expected value", EXPFILL }}
     };
 
     static const enum_val_t sequence_analysis_vals[] = {
@@ -2186,14 +2557,29 @@ void proto_register_pdcp(void)
         {NULL, NULL, -1}
     };
 
-#ifdef HAVE_LIBGCRYPT
+    static const enum_val_t default_ciphering_algorithm_vals[] = {
+        {"eea0", "EEA0 (NULL)",   eea0},
+        {"eea1", "EEA1 (SNOW3G)", eea1},
+        {"eea2", "EEA2 (AES)",    eea2},
+        {"eea3", "EEA3 (ZUC)",    eea3},
+        {NULL, NULL, -1}
+    };
+
+    static const enum_val_t default_integrity_algorithm_vals[] = {
+        {"eia0", "EIA0 (NULL)",   eia0},
+        {"eia1", "EIA1 (SNOW3G)", eia1},
+        {"eia2", "EIA2 (AES)",    eia2},
+        {"eia3", "EIA3 (ZUC)",    eia3},
+        {NULL, NULL, -1}
+    };
+
   static uat_field_t ue_keys_uat_flds[] = {
       UAT_FLD_DEC(uat_ue_keys_records, ueid, "UEId", "UE Identifier of UE associated with keys"),
-      UAT_FLD_CSTRING(uat_ue_keys_records, rrcKeyString, "RRC Key",        "Key for deciphering signalling messages"),
-      UAT_FLD_CSTRING(uat_ue_keys_records, upKeyString,  "User-Plane Key", "Key for deciphering user-plane messages"),
+      UAT_FLD_CSTRING(uat_ue_keys_records, rrcCipherKeyString, "RRC Cipher Key",        "Key for deciphering signalling messages"),
+      UAT_FLD_CSTRING(uat_ue_keys_records, upCipherKeyString,  "User-Plane Cipher Key", "Key for deciphering user-plane messages"),
+      UAT_FLD_CSTRING(uat_ue_keys_records, rrcIntegrityKeyString,  "RRC Integrity Key", "Key for deciphering user-plane messages"),
       UAT_END_FIELDS
     };
-#endif
 
     module_t *pdcp_lte_module;
     expert_module_t* expert_pdcp_lte;
@@ -2238,23 +2624,18 @@ void proto_register_pdcp(void)
         "Attempt to decode ROHC data",
         &global_pdcp_dissect_rohc);
 
-    prefs_register_bool_preference(pdcp_lte_module, "heuristic_pdcp_lte_over_udp",
-        "Try Heuristic LTE-PDCP over UDP framing",
-        "When enabled, use heuristic dissector to find PDCP-LTE frames sent with "
-        "UDP framing",
-        &global_pdcp_lte_heur);
+    prefs_register_obsolete_preference(pdcp_lte_module, "heuristic_pdcp_lte_over_udp");
 
     prefs_register_enum_preference(pdcp_lte_module, "layer_to_show",
         "Which layer info to show in Info column",
         "Can show RLC, PDCP or Traffic layer info in Info column",
         &global_pdcp_lte_layer_to_show, show_info_col_vals, FALSE);
 
-#ifdef HAVE_LIBGCRYPT
     ue_keys_uat = uat_new("PDCP UE security keys",
               sizeof(uat_ue_keys_record_t),    /* record size */
               "pdcp_lte_ue_keys",              /* filename */
               TRUE,                            /* from_profile */
-              (void**) &uat_ue_keys_records,   /* data_ptr */
+              &uat_ue_keys_records,            /* data_ptr */
               &num_ue_keys_uat,                /* numitems_ptr */
               UAT_AFFECTS_DISSECTION,          /* affects dissection of packets, but not set of named fields */
               NULL,                            /* help */
@@ -2270,26 +2651,42 @@ void proto_register_pdcp(void)
                                   "Preconfigured PDCP keys",
                                   ue_keys_uat);
 
+    prefs_register_enum_preference(pdcp_lte_module, "default_ciphering_algorithm",
+        "Ciphering algorithm to use if not signalled",
+        "If RRC Security Info not seen, e.g. in Handover",
+        (gint*)&global_default_ciphering_algorithm, default_ciphering_algorithm_vals, FALSE);
+
+    prefs_register_enum_preference(pdcp_lte_module, "default_integrity_algorithm",
+        "Integrity algorithm to use if not signalled",
+        "If RRC Security Info not seen, e.g. in Handover",
+        (gint*)&global_default_integrity_algorithm, default_integrity_algorithm_vals, FALSE);
+
     /* Attempt to decipher RRC messages */
     prefs_register_bool_preference(pdcp_lte_module, "decipher_signalling",
         "Attempt to decipher Signalling (RRC) SDUs",
-        "N.B. only possible if key available and configured",
+        "N.B. only possible if build with algorithm support, and have key available and configured",
         &global_pdcp_decipher_signalling);
 
     /* Attempt to decipher user-plane messages */
     prefs_register_bool_preference(pdcp_lte_module, "decipher_userplane",
         "Attempt to decipher User-plane (IP) SDUs",
-        "N.B. only possible if key available and configured",
+        "N.B. only possible if build with algorithm support, and have key available and configured",
         &global_pdcp_decipher_userplane);
-#endif
+
+    /* Attempt to verify RRC integrity/authentication digest */
+    prefs_register_bool_preference(pdcp_lte_module, "verify_integrity",
+        "Attempt to check integrity calculation",
+        "N.B. only possible if build with algorithm support, and have key available and configured",
+        &global_pdcp_check_integrity);
 
     register_init_routine(&pdcp_lte_init_protocol);
+    register_cleanup_routine(&pdcp_lte_cleanup_protocol);
 }
 
 void proto_reg_handoff_pdcp_lte(void)
 {
     /* Add as a heuristic UDP dissector */
-    heur_dissector_add("udp", dissect_pdcp_lte_heur, proto_pdcp_lte);
+    heur_dissector_add("udp", dissect_pdcp_lte_heur, "PDCP-LTE over UDP", "pdcp_lte_udp", proto_pdcp_lte, HEURISTIC_DISABLE);
 
     ip_handle   = find_dissector("ip");
     ipv6_handle = find_dissector("ipv6");