gprs: Parse PTMSI and update TLLI accordingly

This commit adds code to parse the PTMSI in network originated
messages

  - Attach Accept,
  - Routing Area Update Accept, and
  - P-TMSI Reallocation Command (see below)

to keep track of the TLLI identifying the LLC connection.

The P_TMSI Realloc Command specific code is not being tested yet, so
a corresponding notice is logged when such a message will be
received.

NOTE:
  The gbproxy will lose the TLLI when the MS doesn't receive/use
  the message (normally the SGSN remembers the old TLLI for some time
  to avoid this kind of problem). If this happens the MS will
  probably restart the procedure and the network will have to answer
  again eventually using one of the above messages which will
  re-associate the IMSI with the TLLI before the MS can send a
  PDP Context Request message.

Ticket: OW#1192
Sponsored-by: On-Waves ehf
diff --git a/openbsc/src/gprs/gb_proxy.c b/openbsc/src/gprs/gb_proxy.c
index 5fcd25a..b41bedb 100644
--- a/openbsc/src/gprs/gb_proxy.c
+++ b/openbsc/src/gprs/gb_proxy.c
@@ -519,6 +519,23 @@
 	return -1;
 }
 
+static int parse_mi_tmsi(uint8_t *value, size_t value_len, uint32_t *tmsi)
+{
+	uint32_t tmsi_be;
+
+	if (value_len != GSM48_TMSI_LEN)
+		return 0;
+
+	if ((value[0] & 0x0f) != GSM_MI_TYPE_TMSI)
+		return 0;
+
+	memcpy(&tmsi_be, value + 1, sizeof(tmsi_be));
+
+	*tmsi = ntohl(tmsi_be);
+
+	return 1;
+}
+
 struct gbprox_parse_context {
 	/* Pointer to protocol specific parts */
 	struct gsm48_hdr *g48_hdr;
@@ -991,6 +1008,7 @@
 				       struct gbprox_parse_context *parse_ctx)
 {
 	uint8_t *value;
+	size_t value_len;
 
 	/* Skip Attach result */
 	/* Skip Force to standby */
@@ -1004,6 +1022,17 @@
 
 	gbprox_patch_raid(value, peer, parse_ctx->to_bss, "LLC/ATTACH_ACK");
 
+	/* Skip P-TMSI signature (P-TMSI signature, opt, TV, length 4) */
+	tv_fixed_match(&data, &data_len, GSM48_IE_GMM_PTMSI_SIG, 3, NULL);
+
+	/* Skip Negotiated READY timer value (GPRS timer, opt, TV, length 2) */
+	tv_fixed_match(&data, &data_len, GSM48_IE_GMM_TIMER_READY, 1, NULL);
+
+	/* Allocated P-TMSI (Mobile identity, opt, TLV, length 7) */
+	if (tlv_match(&data, &data_len, GSM48_IE_GMM_ALLOC_PTMSI,
+		      &value, &value_len) > 0)
+		parse_mi_tmsi(value, value_len, &parse_ctx->new_ptmsi);
+
 	return 1;
 }
 
@@ -1032,6 +1061,7 @@
 				       struct gbprox_parse_context *parse_ctx)
 {
 	uint8_t *value;
+	size_t value_len;
 
 	/* Skip Force to standby */
 	/* Skip Update result */
@@ -1043,6 +1073,13 @@
 
 	gbprox_patch_raid(value, peer, parse_ctx->to_bss, "LLC/RA_UPD_ACK");
 
+	/* Skip P-TMSI signature (P-TMSI signature, opt, TV, length 4) */
+	tv_fixed_match(&data, &data_len, GSM48_IE_GMM_PTMSI_SIG, 3, NULL);
+
+	/* Allocated P-TMSI (Mobile identity, opt, TLV, length 7) */
+	if (tlv_match(&data, &data_len, GSM48_IE_GMM_ALLOC_PTMSI,
+		      &value, &value_len) > 0)
+		parse_mi_tmsi(value, value_len, &parse_ctx->new_ptmsi);
 
 	return 1;
 }
@@ -1056,8 +1093,12 @@
 	uint8_t *value;
 	size_t value_len;
 
-	/* Skip Allocated P-TMSI */
-	if (lv_shift(&data, &data_len, NULL, &value_len) <= 0 || value_len != 5)
+	LOGP(DLLC, LOGL_NOTICE,
+	     "Got P-TMSI Reallocation Command which is not covered by unit tests yet.\n");
+
+	/* Allocated P-TMSI */
+	if (lv_shift(&data, &data_len, &value, &value_len) > 0 &&
+	    parse_mi_tmsi(value, value_len, &parse_ctx->new_ptmsi) < 0)
 		/* invalid */
 		return 0;
 
@@ -1272,6 +1313,17 @@
 
 	rc = gbprox_patch_dtap(msg, data, data_len, peer, &len_change, parse_ctx);
 
+	if (parse_ctx->new_ptmsi &&
+	    (parse_ctx->new_ptmsi | 0xc000) != (tlli | 0xc000) &&
+	    gbcfg.core_apn && parse_ctx->to_bss && parse_ctx->imsi) {
+		/* A new TLLI (PTMSI) has been signaled in the message */
+		LOGP(DGPRS, LOGL_INFO,
+		     "Got new TLLI/PTMSI %08x (current is %08x)\n",
+		     parse_ctx->new_ptmsi, tlli);
+		gbprox_register_tlli(peer, parse_ctx->new_ptmsi,
+				     parse_ctx->imsi, parse_ctx->imsi_len);
+	}
+
 	if (rc > 0) {
 		llc_len += len_change;
 		ghp.crc_length += len_change;