Move gprs_gb_parse.[c,h] to tests/sgsn/

That big file is really only used by tests/sgsn/sgsn_test nowadays, so
let's keep it out of osmo-sgsn app code base.

Change-Id: Ia5a639832f52b2f015a2800bd0d94a28d7bc689b
diff --git a/tests/sgsn/Makefile.am b/tests/sgsn/Makefile.am
index 72a7714..bd8e19c 100644
--- a/tests/sgsn/Makefile.am
+++ b/tests/sgsn/Makefile.am
@@ -29,8 +29,13 @@
 	sgsn_test \
 	$(NULL)
 
+noinst_HEADERS = \
+	gprs_gb_parse.h \
+	$(NULL)
+
 sgsn_test_SOURCES = \
 	sgsn_test.c \
+	gprs_gb_parse.c \
 	$(NULL)
 
 sgsn_test_LDFLAGS = \
@@ -66,7 +71,6 @@
 	$(top_builddir)/src/sgsn/sgsn_rim.o \
 	$(top_builddir)/src/gprs/gprs_utils.o \
 	$(top_builddir)/src/gprs/gprs_llc_parse.o \
-	$(top_builddir)/src/gprs/gprs_gb_parse.o \
 	$(top_builddir)/src/gprs/crc24.o \
 	$(top_builddir)/src/gprs/sgsn_ares.o \
 	$(LIBOSMOABIS_LIBS) \
diff --git a/tests/sgsn/gprs_gb_parse.c b/tests/sgsn/gprs_gb_parse.c
new file mode 100644
index 0000000..3338911
--- /dev/null
+++ b/tests/sgsn/gprs_gb_parse.c
@@ -0,0 +1,686 @@
+/* GPRS Gb message parser */
+
+/* (C) 2014 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+
+#include "gprs_gb_parse.h"
+
+#include <osmocom/sgsn/gprs_utils.h>
+
+#include <osmocom/sgsn/debug.h>
+
+#include <osmocom/gprs/gprs_bssgp.h>
+
+static int gprs_gb_parse_gmm_attach_req(uint8_t *data, size_t data_len,
+					struct gprs_gb_parse_context *parse_ctx)
+{
+	uint8_t *value;
+	size_t value_len;
+
+	parse_ctx->llc_msg_name = "ATTACH_REQ";
+
+	/* Skip MS network capability */
+	if (osmo_shift_lv(&data, &data_len, NULL, &value_len) <= 0 ||
+	    value_len < 1 || value_len > 8)
+		/* invalid */
+		return 0;
+
+	/* Skip Attach type */
+	/* Skip Ciphering key sequence number */
+	/* Skip DRX parameter */
+	if (osmo_shift_v_fixed(&data, &data_len, 3, NULL) < 3)
+		return 0;
+
+	/* Get Mobile identity */
+	if (osmo_shift_lv(&data, &data_len, &value, &value_len) <= 0 ||
+	    value_len < 5 || value_len > 8)
+		/* invalid */
+		return 0;
+
+	if (gprs_is_mi_tmsi(value, value_len)) {
+		parse_ctx->ptmsi_enc = value + 1;
+	} else if (gprs_is_mi_imsi(value, value_len)) {
+		parse_ctx->imsi = value;
+		parse_ctx->imsi_len = value_len;
+	}
+
+	if (osmo_shift_v_fixed(&data, &data_len, 6, &value) <= 0)
+		return 0;
+
+	parse_ctx->old_raid_enc = value;
+
+	return 1;
+}
+
+static int gprs_gb_parse_gmm_attach_ack(uint8_t *data, size_t data_len,
+					struct gprs_gb_parse_context *parse_ctx)
+{
+	uint8_t *value;
+	size_t value_len;
+
+	parse_ctx->llc_msg_name = "ATTACH_ACK";
+
+	/* Skip Attach result */
+	/* Skip Force to standby */
+	/* Skip Periodic RA update timer */
+	/* Skip Radio priority for SMS */
+	/* Skip Spare half octet */
+	if (osmo_shift_v_fixed(&data, &data_len, 3, NULL) < 3)
+		return 0;
+
+	if (osmo_shift_v_fixed(&data, &data_len, 6, &value) <= 0)
+		return 0;
+
+	parse_ctx->raid_enc = value;
+
+	/* Skip P-TMSI signature (P-TMSI signature, opt, TV, length 4) */
+	osmo_match_shift_tv_fixed(&data, &data_len, GSM48_IE_GMM_PTMSI_SIG, 3, NULL);
+
+	/* Skip Negotiated READY timer value (GPRS timer, opt, TV, length 2) */
+	osmo_match_shift_tv_fixed(&data, &data_len, GSM48_IE_GMM_TIMER_READY, 1, NULL);
+
+	/* Allocated P-TMSI (Mobile identity, opt, TLV, length 7) */
+	if (osmo_match_shift_tlv(&data, &data_len, GSM48_IE_GMM_ALLOC_PTMSI,
+		      &value, &value_len) > 0 &&
+	    gprs_is_mi_tmsi(value, value_len))
+		parse_ctx->new_ptmsi_enc = value + 1;
+	return 1;
+}
+
+static int gprs_gb_parse_gmm_attach_rej(uint8_t *data, size_t data_len,
+					struct gprs_gb_parse_context *parse_ctx)
+{
+	uint8_t *value;
+
+	parse_ctx->llc_msg_name = "ATTACH_REJ";
+
+	/* GMM cause */
+	if (osmo_shift_v_fixed(&data, &data_len, 1, &value) <= 0)
+		return 0;
+
+	parse_ctx->invalidate_tlli = 1;
+
+	return 1;
+}
+
+
+static int gprs_gb_parse_gmm_detach_req(uint8_t *data, size_t data_len,
+					struct gprs_gb_parse_context *parse_ctx)
+{
+	uint8_t *value;
+	size_t value_len;
+	int detach_type;
+	int power_off;
+
+	parse_ctx->llc_msg_name = "DETACH_REQ";
+
+	/* Skip spare half octet */
+	/* Get Detach type */
+	if (osmo_shift_v_fixed(&data, &data_len, 1, &value) <= 0)
+		/* invalid */
+		return 0;
+
+	detach_type = *value & 0x07;
+	power_off = *value & 0x08 ? 1 : 0;
+
+	if (parse_ctx->to_bss) {
+		/* Network originated */
+		if (detach_type == GPRS_DET_T_MT_REATT_REQ)
+			parse_ctx->await_reattach = 1;
+	} else {
+		/* Mobile originated */
+
+		if (power_off)
+			parse_ctx->invalidate_tlli = 1;
+
+		/* Get P-TMSI (Mobile identity), see GSM 24.008, 9.4.5.2 */
+		if (osmo_match_shift_tlv(&data, &data_len,
+			      GSM48_IE_GMM_ALLOC_PTMSI, &value, &value_len) > 0)
+		{
+			if (gprs_is_mi_tmsi(value, value_len))
+				parse_ctx->ptmsi_enc = value + 1;
+		}
+	}
+
+	return 1;
+}
+
+static int gprs_gb_parse_gmm_ra_upd_req(uint8_t *data, size_t data_len,
+					struct gprs_gb_parse_context *parse_ctx)
+{
+	uint8_t *value;
+
+	parse_ctx->llc_msg_name = "RA_UPD_REQ";
+
+	/* Skip Update type */
+	/* Skip GPRS ciphering key sequence number */
+	if (osmo_shift_v_fixed(&data, &data_len, 1, NULL) < 1)
+		return 0;
+
+	if (osmo_shift_v_fixed(&data, &data_len, 6, &value) <= 0)
+		return 0;
+
+	parse_ctx->old_raid_enc = value;
+
+	return 1;
+}
+
+static int gprs_gb_parse_gmm_ra_upd_rej(uint8_t *data, size_t data_len,
+					struct gprs_gb_parse_context *parse_ctx)
+{
+	uint8_t *value;
+	uint8_t cause;
+	int force_standby;
+
+	parse_ctx->llc_msg_name = "RA_UPD_REJ";
+
+	/* GMM cause */
+	if (osmo_shift_v_fixed(&data, &data_len, 1, &value) <= 0)
+		return 0;
+
+	cause = value[0];
+
+	/* Force to standby, 1/2 */
+	/* spare bits, 1/2 */
+	if (osmo_shift_v_fixed(&data, &data_len, 1, &value) <= 0)
+		return 0;
+
+	force_standby = (value[0] & 0x07) == 0x01;
+
+	if (cause == GMM_CAUSE_IMPL_DETACHED && !force_standby)
+		parse_ctx->await_reattach = 1;
+
+	parse_ctx->invalidate_tlli = 1;
+
+	return 1;
+}
+
+static int gprs_gb_parse_gmm_ra_upd_ack(uint8_t *data, size_t data_len,
+					struct gprs_gb_parse_context *parse_ctx)
+{
+	uint8_t *value;
+	size_t value_len;
+
+	parse_ctx->llc_msg_name = "RA_UPD_ACK";
+
+	/* Skip Force to standby */
+	/* Skip Update result */
+	/* Skip Periodic RA update timer */
+	if (osmo_shift_v_fixed(&data, &data_len, 2, NULL) < 2)
+		return 0;
+
+	if (osmo_shift_v_fixed(&data, &data_len, 6, &value) <= 0)
+		return 0;
+
+	parse_ctx->raid_enc = value;
+
+	/* Skip P-TMSI signature (P-TMSI signature, opt, TV, length 4) */
+	osmo_match_shift_tv_fixed(&data, &data_len, GSM48_IE_GMM_PTMSI_SIG, 3, NULL);
+
+	/* Allocated P-TMSI (Mobile identity, opt, TLV, length 7) */
+	if (osmo_match_shift_tlv(&data, &data_len, GSM48_IE_GMM_ALLOC_PTMSI,
+		      &value, &value_len) > 0 &&
+	    gprs_is_mi_tmsi(value, value_len))
+		parse_ctx->new_ptmsi_enc = value + 1;
+
+	return 1;
+}
+
+static int gprs_gb_parse_gmm_ptmsi_reall_cmd(uint8_t *data, size_t data_len,
+					     struct gprs_gb_parse_context *parse_ctx)
+{
+	uint8_t *value;
+	size_t value_len;
+
+	parse_ctx->llc_msg_name = "PTMSI_REALL_CMD";
+
+	LOGP(DLLC, LOGL_NOTICE,
+	     "Got P-TMSI Reallocation Command which is not covered by unit tests yet.\n");
+
+	/* Allocated P-TMSI */
+	if (osmo_shift_lv(&data, &data_len, &value, &value_len) > 0 &&
+	    gprs_is_mi_tmsi(value, value_len))
+		parse_ctx->new_ptmsi_enc = value + 1;
+
+	if (osmo_shift_v_fixed(&data, &data_len, 6, &value) <= 0)
+		return 0;
+
+	parse_ctx->raid_enc = value;
+
+	return 1;
+}
+
+static int gprs_gb_parse_gmm_id_resp(uint8_t *data, size_t data_len,
+				     struct gprs_gb_parse_context *parse_ctx)
+{
+	uint8_t *value;
+	size_t value_len;
+
+	parse_ctx->llc_msg_name = "ID_RESP";
+
+	/* Mobile identity, Mobile identity 10.5.1.4, M LV 2-10 */
+	if (osmo_shift_lv(&data, &data_len, &value, &value_len) <= 0 ||
+	    value_len < 1 || value_len > 9)
+		/* invalid */
+		return 0;
+
+	if (gprs_is_mi_tmsi(value, value_len)) {
+		parse_ctx->ptmsi_enc = value + 1;
+	} else if (gprs_is_mi_imsi(value, value_len)) {
+		parse_ctx->imsi = value;
+		parse_ctx->imsi_len = value_len;
+	}
+
+	return 1;
+}
+
+static int gprs_gb_parse_gsm_act_pdp_req(uint8_t *data, size_t data_len,
+					 struct gprs_gb_parse_context *parse_ctx)
+{
+	ssize_t old_len;
+	uint8_t *value;
+	size_t value_len;
+
+	parse_ctx->llc_msg_name = "ACT_PDP_REQ";
+
+	/* Skip Requested NSAPI */
+	/* Skip Requested LLC SAPI */
+	if (osmo_shift_v_fixed(&data, &data_len, 2, NULL) < 2)
+		return 0;
+
+	/* Skip Requested QoS (support 04.08 and 24.008) */
+	if (osmo_shift_lv(&data, &data_len, NULL, &value_len) <= 0 ||
+	    value_len < 4 || value_len > 14)
+		/* invalid */
+		return 0;
+
+	/* Skip Requested PDP address */
+	if (osmo_shift_lv(&data, &data_len, NULL, &value_len) <= 0 ||
+	    value_len < 2 || value_len > 18)
+		/* invalid */
+		return 0;
+
+	/* Access point name */
+	old_len = osmo_match_shift_tlv(&data, &data_len,
+			    GSM48_IE_GSM_APN, &value, &value_len);
+
+	if (old_len > 0 && value_len >=1 && value_len <= 100) {
+		parse_ctx->apn_ie = data - old_len;
+		parse_ctx->apn_ie_len = old_len;
+	}
+
+	return 1;
+}
+
+int gprs_gb_parse_dtap(uint8_t *data, size_t data_len,
+		       struct gprs_gb_parse_context *parse_ctx)
+{
+	struct gsm48_hdr *g48h;
+	uint8_t pdisc;
+	uint8_t msg_type;
+
+	if (osmo_shift_v_fixed(&data, &data_len, sizeof(*g48h), (uint8_t **)&g48h) <= 0)
+		return 0;
+
+	parse_ctx->g48_hdr = g48h;
+
+	pdisc = gsm48_hdr_pdisc(g48h);
+	if (pdisc != GSM48_PDISC_MM_GPRS && pdisc != GSM48_PDISC_SM_GPRS)
+		return 1;
+
+	msg_type = gsm48_hdr_msg_type(g48h);
+	switch (msg_type) {
+	case GSM48_MT_GMM_ATTACH_REQ:
+		return gprs_gb_parse_gmm_attach_req(data, data_len, parse_ctx);
+
+	case GSM48_MT_GMM_ATTACH_REJ:
+		return gprs_gb_parse_gmm_attach_rej(data, data_len, parse_ctx);
+
+	case GSM48_MT_GMM_ATTACH_ACK:
+		return gprs_gb_parse_gmm_attach_ack(data, data_len, parse_ctx);
+
+	case GSM48_MT_GMM_RA_UPD_REQ:
+		return gprs_gb_parse_gmm_ra_upd_req(data, data_len, parse_ctx);
+
+	case GSM48_MT_GMM_RA_UPD_REJ:
+		return gprs_gb_parse_gmm_ra_upd_rej(data, data_len, parse_ctx);
+
+	case GSM48_MT_GMM_RA_UPD_ACK:
+		return gprs_gb_parse_gmm_ra_upd_ack(data, data_len, parse_ctx);
+
+	case GSM48_MT_GMM_PTMSI_REALL_CMD:
+		return gprs_gb_parse_gmm_ptmsi_reall_cmd(data, data_len, parse_ctx);
+
+	case GSM48_MT_GSM_ACT_PDP_REQ:
+		return gprs_gb_parse_gsm_act_pdp_req(data, data_len, parse_ctx);
+
+	case GSM48_MT_GMM_ID_RESP:
+		return gprs_gb_parse_gmm_id_resp(data, data_len, parse_ctx);
+
+	case GSM48_MT_GMM_DETACH_REQ:
+		return gprs_gb_parse_gmm_detach_req(data, data_len, parse_ctx);
+
+	case GSM48_MT_GMM_DETACH_ACK:
+		parse_ctx->llc_msg_name = "DETACH_ACK";
+		parse_ctx->invalidate_tlli = 1;
+		break;
+
+	case GSM48_MT_GSM_DEACT_PDP_REQ:
+		parse_ctx->llc_msg_name = "DEACT_PDP_REQ";
+		break;
+
+	case GSM48_MT_GSM_DEACT_PDP_ACK:
+		parse_ctx->llc_msg_name = "DEACT_PDP_ACK";
+		break;
+
+	default:
+		LOGP(DLLC, LOGL_NOTICE,
+		     "Unhandled GSM 04.08 message type %s for protocol discriminator %s.\n",
+		     get_value_string(gprs_msgt_gmm_names, msg_type), get_value_string(gsm48_pdisc_names, pdisc));
+		break;
+	};
+
+	return 1;
+}
+
+int gprs_gb_parse_llc(uint8_t *llc, size_t llc_len,
+		      struct gprs_gb_parse_context *parse_ctx)
+{
+	struct gprs_llc_hdr_parsed *ghp = &parse_ctx->llc_hdr_parsed;
+	int rc;
+	int fcs;
+
+	/* parse LLC */
+	rc = gprs_llc_hdr_parse(ghp, llc, llc_len);
+	gprs_llc_hdr_dump(ghp, NULL);
+	if (rc != 0) {
+		LOGP(DLLC, LOGL_NOTICE, "Error during LLC header parsing\n");
+		return 0;
+	}
+
+	fcs = gprs_llc_fcs(llc, ghp->crc_length);
+	LOGP(DLLC, LOGL_DEBUG, "Got LLC message, CRC: %06x (computed %06x)\n",
+	     ghp->fcs, fcs);
+
+	if (!ghp->data)
+		return 0;
+
+	if (ghp->sapi != GPRS_SAPI_GMM)
+		return 1;
+
+	if (ghp->cmd != GPRS_LLC_UI)
+		return 1;
+
+	if (ghp->is_encrypted) {
+		parse_ctx->need_decryption = 1;
+		return 0;
+	}
+
+	return gprs_gb_parse_dtap(ghp->data, ghp->data_len, parse_ctx);
+}
+
+/*! Determine the TLLI from the given BSSGP message.
+ *  \param[in] bssgp pointer to start of BSSGP header
+ *  \param[in] bssgp_len length of BSSGP message in octets
+ *  \param[out] tlli TLLI (if any) in host byte order
+ *  \returns 1 if TLLI found; 0 if none found; negative on parse error */
+int gprs_gb_parse_tlli(const uint8_t *bssgp, size_t bssgp_len, uint32_t *tlli)
+{
+	const struct bssgp_normal_hdr *bgph;
+	uint8_t pdu_type;
+
+	if (bssgp_len < sizeof(struct bssgp_normal_hdr))
+		return -EINVAL;
+
+	bgph = (struct bssgp_normal_hdr *)bssgp;
+	pdu_type = bgph->pdu_type;
+
+	if (pdu_type == BSSGP_PDUT_UL_UNITDATA ||
+	    pdu_type == BSSGP_PDUT_DL_UNITDATA) {
+		const struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *)bssgp;
+		if (bssgp_len < sizeof(struct bssgp_ud_hdr))
+			return -EINVAL;
+		*tlli = osmo_load32be((const uint8_t *)&budh->tlli);
+		return 1;
+	} else {
+		const uint8_t *data = bgph->data;
+		size_t data_len = bssgp_len - sizeof(*bgph);
+		struct tlv_parsed tp;
+
+		if (bssgp_tlv_parse(&tp, data, data_len) < 0)
+			return -EINVAL;
+
+		if (TLVP_PRESENT(&tp, BSSGP_IE_TLLI)) {
+			*tlli = osmo_load32be(TLVP_VAL(&tp, BSSGP_IE_TLLI));
+			return 1;
+		}
+	}
+
+	/* No TLLI present in message */
+	return 0;
+}
+
+int gprs_gb_parse_bssgp(uint8_t *bssgp, size_t bssgp_len,
+			struct gprs_gb_parse_context *parse_ctx)
+{
+	struct bssgp_normal_hdr *bgph;
+	struct bssgp_ud_hdr *budh = NULL;
+	struct tlv_parsed *tp = &parse_ctx->bssgp_tp;
+	uint8_t pdu_type;
+	uint8_t *data;
+	size_t data_len;
+	int rc;
+
+	if (bssgp_len < sizeof(struct bssgp_normal_hdr))
+		return 0;
+
+	bgph = (struct bssgp_normal_hdr *)bssgp;
+	pdu_type = bgph->pdu_type;
+
+	if (pdu_type == BSSGP_PDUT_UL_UNITDATA ||
+	    pdu_type == BSSGP_PDUT_DL_UNITDATA) {
+		if (bssgp_len < sizeof(struct bssgp_ud_hdr))
+			return 0;
+		budh = (struct bssgp_ud_hdr *)bssgp;
+		bgph = NULL;
+		data = budh->data;
+		data_len = bssgp_len - sizeof(*budh);
+	} else {
+		data = bgph->data;
+		data_len = bssgp_len - sizeof(*bgph);
+	}
+
+	parse_ctx->pdu_type = pdu_type;
+	parse_ctx->bud_hdr = budh;
+	parse_ctx->bgp_hdr = bgph;
+	parse_ctx->bssgp_data = data;
+	parse_ctx->bssgp_data_len = data_len;
+
+	if (bssgp_tlv_parse(tp, data, data_len) < 0)
+		return 0;
+
+	if (budh)
+		parse_ctx->tlli_enc = (uint8_t *)&budh->tlli;
+
+	if (TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA))
+		parse_ctx->bssgp_raid_enc = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA);
+
+	if (TLVP_PRESENT(tp, BSSGP_IE_CELL_ID))
+		parse_ctx->bssgp_raid_enc = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_CELL_ID);
+
+	if (TLVP_PRESENT(tp, BSSGP_IE_IMSI)) {
+		parse_ctx->imsi = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_IMSI);
+		parse_ctx->imsi_len = TLVP_LEN(tp, BSSGP_IE_IMSI);
+	}
+
+	if (TLVP_PRESENT(tp, BSSGP_IE_TLLI)) {
+		if (parse_ctx->tlli_enc)
+			/* This is TLLI old, don't confuse it with TLLI current */
+			parse_ctx->old_tlli_enc = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_TLLI);
+		else
+			parse_ctx->tlli_enc = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_TLLI);
+	}
+
+	if (TLVP_PRESENT(tp, BSSGP_IE_TMSI) && pdu_type == BSSGP_PDUT_PAGING_PS)
+		parse_ctx->bssgp_ptmsi_enc = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_TMSI);
+
+	if (TLVP_PRESENT(tp, BSSGP_IE_LLC_PDU)) {
+		uint8_t *llc = (uint8_t *)TLVP_VAL(tp, BSSGP_IE_LLC_PDU);
+		size_t llc_len = TLVP_LEN(tp, BSSGP_IE_LLC_PDU);
+
+		rc = gprs_gb_parse_llc(llc, llc_len, parse_ctx);
+		if (!rc)
+			return 0;
+
+		parse_ctx->llc = llc;
+		parse_ctx->llc_len = llc_len;
+	}
+
+	if (parse_ctx->tlli_enc) {
+		uint32_t tmp_tlli;
+		memcpy(&tmp_tlli, parse_ctx->tlli_enc, sizeof(tmp_tlli));
+		parse_ctx->tlli = ntohl(tmp_tlli);
+	}
+
+	if (parse_ctx->bssgp_raid_enc && parse_ctx->old_raid_enc &&
+	    memcmp(parse_ctx->bssgp_raid_enc, parse_ctx->old_raid_enc, 6) != 0)
+		parse_ctx->old_raid_is_foreign = 1;
+
+	return 1;
+}
+
+void gprs_gb_log_parse_context(int log_level,
+			       struct gprs_gb_parse_context *parse_ctx,
+			       const char *default_msg_name)
+{
+	const char *msg_name;
+	const char *sep = "";
+
+	if (!parse_ctx->tlli_enc &&
+	    !parse_ctx->ptmsi_enc &&
+	    !parse_ctx->new_ptmsi_enc &&
+	    !parse_ctx->bssgp_ptmsi_enc &&
+	    !parse_ctx->imsi)
+		return;
+
+	msg_name = gprs_gb_message_name(parse_ctx, default_msg_name);
+
+	if (parse_ctx->llc_msg_name)
+		msg_name = parse_ctx->llc_msg_name;
+
+	LOGP(DGPRS, log_level, "%s: Got", msg_name);
+
+	if (parse_ctx->tlli_enc) {
+		LOGPC(DGPRS, log_level, "%s TLLI %08x", sep, parse_ctx->tlli);
+		sep = ",";
+	}
+
+	if (parse_ctx->old_tlli_enc) {
+		LOGPC(DGPRS, log_level, "%s old TLLI %02x%02x%02x%02x", sep,
+		     parse_ctx->old_tlli_enc[0],
+		     parse_ctx->old_tlli_enc[1],
+		     parse_ctx->old_tlli_enc[2],
+		     parse_ctx->old_tlli_enc[3]);
+		sep = ",";
+	}
+
+	if (parse_ctx->bssgp_raid_enc) {
+		struct gprs_ra_id raid;
+		gsm48_parse_ra(&raid, parse_ctx->bssgp_raid_enc);
+		LOGPC(DGPRS, log_level, "%s BSSGP RAID %s", sep, osmo_rai_name(&raid));
+		sep = ",";
+	}
+
+	if (parse_ctx->raid_enc) {
+		struct gprs_ra_id raid;
+		gsm48_parse_ra(&raid, parse_ctx->raid_enc);
+		LOGPC(DGPRS, log_level, "%s RAID %s", sep, osmo_rai_name(&raid));
+		sep = ",";
+	}
+
+	if (parse_ctx->old_raid_enc) {
+		struct gprs_ra_id raid;
+		gsm48_parse_ra(&raid, parse_ctx->old_raid_enc);
+		LOGPC(DGPRS, log_level, "%s old RAID %s", sep, osmo_rai_name(&raid));
+		sep = ",";
+	}
+
+	if (parse_ctx->bssgp_ptmsi_enc) {
+		uint32_t ptmsi = GSM_RESERVED_TMSI;
+		gprs_parse_tmsi(parse_ctx->bssgp_ptmsi_enc, &ptmsi);
+		LOGPC(DGPRS, log_level, "%s BSSGP PTMSI %08x", sep, ptmsi);
+		sep = ",";
+	}
+
+	if (parse_ctx->ptmsi_enc) {
+		uint32_t ptmsi = GSM_RESERVED_TMSI;
+		gprs_parse_tmsi(parse_ctx->ptmsi_enc, &ptmsi);
+		LOGPC(DGPRS, log_level, "%s PTMSI %08x", sep, ptmsi);
+		sep = ",";
+	}
+
+	if (parse_ctx->new_ptmsi_enc) {
+		uint32_t new_ptmsi = GSM_RESERVED_TMSI;
+		gprs_parse_tmsi(parse_ctx->new_ptmsi_enc, &new_ptmsi);
+		LOGPC(DGPRS, log_level, "%s new PTMSI %08x", sep, new_ptmsi);
+		sep = ",";
+	}
+
+	if (parse_ctx->imsi) {
+		struct osmo_mobile_identity mi;
+		if (osmo_mobile_identity_decode(&mi, parse_ctx->imsi, parse_ctx->imsi_len, false) == 0
+		    && mi.type == GSM_MI_TYPE_IMSI) {
+			LOGPC(DGPRS, log_level, "%s IMSI %s", sep, mi.imsi);
+			sep = ",";
+		}
+	}
+	if (parse_ctx->invalidate_tlli) {
+		LOGPC(DGPRS, log_level, "%s invalidate", sep);
+		sep = ",";
+	}
+	if (parse_ctx->await_reattach) {
+		LOGPC(DGPRS, log_level, "%s re-attach", sep);
+		sep = ",";
+	}
+
+	LOGPC(DGPRS, log_level, "\n");
+}
+
+const char *gprs_gb_message_name(const struct gprs_gb_parse_context *parse_ctx,
+				 const char *default_msg_name)
+{
+	if (parse_ctx->llc_msg_name)
+		return parse_ctx->llc_msg_name;
+
+	if (parse_ctx->g48_hdr)
+		return "GMM";
+
+	if (parse_ctx->llc)
+		return "LLC";
+
+	if (parse_ctx->bud_hdr)
+		return "BSSGP-UNITDATA";
+
+	if (parse_ctx->bgp_hdr)
+		return "BSSGP";
+
+	return "unknown";
+}
diff --git a/tests/sgsn/gprs_gb_parse.h b/tests/sgsn/gprs_gb_parse.h
new file mode 100644
index 0000000..58de17f
--- /dev/null
+++ b/tests/sgsn/gprs_gb_parse.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include <osmocom/sgsn/gprs_llc.h>
+
+#include <sys/types.h>
+
+struct gprs_gb_parse_context {
+	/* Pointer to protocol specific parts */
+	struct gsm48_hdr *g48_hdr;
+	struct bssgp_normal_hdr *bgp_hdr;
+	struct bssgp_ud_hdr *bud_hdr;
+	uint8_t *bssgp_data;
+	size_t bssgp_data_len;
+	uint8_t *llc;
+	size_t llc_len;
+
+	/* Extracted information */
+	struct gprs_llc_hdr_parsed llc_hdr_parsed;
+	struct tlv_parsed bssgp_tp;
+	int to_bss;
+	uint8_t *tlli_enc;
+	uint8_t *old_tlli_enc;
+	uint8_t *imsi;
+	size_t imsi_len;
+	uint8_t *apn_ie;
+	size_t apn_ie_len;
+	uint8_t *ptmsi_enc;
+	uint8_t *new_ptmsi_enc;
+	uint8_t *raid_enc;
+	uint8_t *old_raid_enc;
+	uint8_t *bssgp_raid_enc;
+	uint8_t *bssgp_ptmsi_enc;
+
+	/* General info */
+	const char *llc_msg_name;
+	int invalidate_tlli;
+	int await_reattach;
+	int need_decryption;
+	uint32_t tlli;
+	int pdu_type;
+	int old_raid_is_foreign;
+	int peer_nsei;
+};
+
+int gprs_gb_parse_dtap(uint8_t *data, size_t data_len,
+		       struct gprs_gb_parse_context *parse_ctx);
+
+int gprs_gb_parse_llc(uint8_t *llc, size_t llc_len,
+		      struct gprs_gb_parse_context *parse_ctx);
+
+int gprs_gb_parse_bssgp(uint8_t *bssgp, size_t bssgp_len,
+			struct gprs_gb_parse_context *parse_ctx);
+
+int gprs_gb_parse_tlli(const uint8_t *bssgp, size_t bssgp_len, uint32_t *tlli);
+
+const char *gprs_gb_message_name(const struct gprs_gb_parse_context *parse_ctx,
+				 const char *default_msg_name);
+
+void gprs_gb_log_parse_context(int log_level,
+			       struct gprs_gb_parse_context *parse_ctx,
+			       const char *default_msg_name);
diff --git a/tests/sgsn/sgsn_test.c b/tests/sgsn/sgsn_test.c
index 2c41e8e..a09f1a5 100644
--- a/tests/sgsn/sgsn_test.c
+++ b/tests/sgsn/sgsn_test.c
@@ -27,7 +27,6 @@
 #include <osmocom/gsm/gsup.h>
 #include <osmocom/gsupclient/gsup_client.h>
 #include <osmocom/sgsn/gprs_utils.h>
-#include <osmocom/sgsn/gprs_gb_parse.h>
 #include <osmocom/sgsn/gprs_gmm_fsm.h>
 
 #include <osmocom/gprs/gprs_bssgp.h>
@@ -42,6 +41,8 @@
 
 #include <stdio.h>
 
+#include "gprs_gb_parse.h"
+
 void *tall_sgsn_ctx;
 static struct sgsn_instance sgsn_inst = {
 	.config_file = "osmo_sgsn.cfg",