LLC: Add minimal LLGMM-RESET.req implementation

Using LLGMM-RESET.req, the GMM can request the LLC of the MS to reset
all its parameters, particularly the sequence numbers.  We don't yet do
XID RESET retransmissions, and we don't yet generate a LLGMM-RESET.conf
primitive back to GMM.
diff --git a/openbsc/include/openbsc/gprs_llc.h b/openbsc/include/openbsc/gprs_llc.h
index 9f17605..f905473 100644
--- a/openbsc/include/openbsc/gprs_llc.h
+++ b/openbsc/include/openbsc/gprs_llc.h
@@ -27,6 +27,23 @@
 	GPRS_LLC_U_NULL_CMD		= 0x00,
 };
 
+/* Section 6.4.1.6 / Table 6 */
+enum gprs_llc_xid_type {
+	GPRS_LLC_XID_T_VERSION		= 0,
+	GPRS_LLC_XID_T_IOV_UI		= 1,
+	GPRS_LLC_XID_T_IOV_I		= 2,
+	GPRS_LLC_XID_T_T200		= 3,
+	GPRS_LLC_XID_T_N200		= 4,
+	GPRS_LLC_XID_T_N201_U		= 5,
+	GPRS_LLC_XID_T_N201_I		= 6,
+	GPRS_LLC_XID_T_mD		= 7,
+	GPRS_LLC_XID_T_mU		= 8,
+	GPRS_LLC_XID_T_kD		= 9,
+	GPRS_LLC_XID_T_kU		= 10,
+	GPRS_LLC_XID_T_L3_PAR		= 11,
+	GPRS_LLC_XID_T_RESET		= 12,
+};
+
 /* TS 04.64 Section 7.1.2 Table 7: LLC layer primitives (GMM/SNDCP/SMS/TOM) */
 /* TS 04.65 Section 5.1.2 Table 2: Service primitives used by SNDCP */
 enum gprs_llc_primitive {
@@ -151,6 +168,9 @@
 int gprs_llc_tx_ui(struct msgb *msg, uint8_t sapi, int command,
 		   void *mmctx);
 
+/* Chapter 7.2.1.2 LLGMM-RESET.req */
+int gprs_llgmm_reset(struct gprs_llc_llme *llme);
+
 /* 04.64 Chapter 7.2.1.1 LLGMM-ASSIGN */
 int gprs_llgmm_assign(struct gprs_llc_llme *llme,
 		      uint32_t old_tlli, uint32_t new_tlli,
diff --git a/openbsc/src/gprs/gprs_llc.c b/openbsc/src/gprs/gprs_llc.c
index 3d4e986..83db28c 100644
--- a/openbsc/src/gprs/gprs_llc.c
+++ b/openbsc/src/gprs/gprs_llc.c
@@ -312,14 +312,15 @@
 }
 
 /* Send XID response to LLE */
-static int gprs_llc_tx_xid(struct gprs_llc_lle *lle, struct msgb *msg)
+static int gprs_llc_tx_xid(struct gprs_llc_lle *lle, struct msgb *msg,
+			   int command)
 {
 	/* copy identifiers from LLE to ensure lower layers can route */
 	msgb_tlli(msg) = lle->llme->tlli;
 	msgb_bvci(msg) = lle->llme->bvci;
 	msgb_nsei(msg) = lle->llme->nsei;
 
-	return gprs_llc_tx_u(msg, lle->sapi, 0, GPRS_LLC_U_XID, 1);
+	return gprs_llc_tx_u(msg, lle->sapi, command, GPRS_LLC_U_XID, 1);
 }
 
 /* Transmit a UI frame over the given SAPI */
@@ -422,6 +423,54 @@
 	return gprs_bssgp_tx_dl_ud(msg, mmctx);
 }
 
+/* According to 6.4.1.6 / Figure 11 */
+static int msgb_put_xid_par(struct msgb *msg, uint8_t type, uint8_t length, uint8_t *data)
+{
+	uint8_t header_len = 1;
+	uint8_t *cur;
+
+	/* type is a 5-bit field... */
+	if (type > 0x1f)
+		return -EINVAL;
+
+	if (length > 3)
+		header_len = 2;
+
+	cur = msgb_put(msg, length + header_len);
+
+	/* build the header without or with XL bit */
+	if (length <= 3) {
+		*cur++ = (type << 2) | (length & 3);
+	} else {
+		*cur++ = 0x80 | (type << 2) | (length >> 6);
+		*cur++ = (length << 2);
+	}
+
+	/* copy over the payload of the parameter*/
+	memcpy(cur, data, length);
+
+	return length + header_len;
+}
+
+static void rx_llc_xid(struct gprs_llc_lle *lle,
+			struct gprs_llc_hdr_parsed *gph)
+{
+	/* FIXME: 8.5.3.3: check if XID is invalid */
+	if (gph->is_cmd) {
+		/* FIXME: implement XID negotiation using SNDCP */
+		struct msgb *resp;
+		uint8_t *xid;
+		resp = msgb_alloc_headroom(4096, 1024, "LLC_XID");
+		xid = msgb_put(resp, gph->data_len);
+		memcpy(xid, gph->data, gph->data_len);
+		gprs_llc_tx_xid(lle, resp, 0);
+	} else {
+		/* FIXME: if we had sent a XID reset, send
+		 * LLGMM-RESET.conf to GMM */
+		/* FIXME: implement XID negotiation using SNDCP */
+	}
+}
+
 static void gprs_llc_hdr_dump(struct gprs_llc_hdr_parsed *gph)
 {
 	DEBUGP(DLLC, "LLC SAPI=%u %c %c FCS=0x%06x",
@@ -466,15 +515,7 @@
 	case GPRS_LLC_FRMR: /* Section 6.4.1.5 */
 		break;
 	case GPRS_LLC_XID: /* Section 6.4.1.6 */
-		/* FIXME: implement XID negotiation using SNDCP */
-		{
-			struct msgb *resp;
-			uint8_t *xid;
-			resp = msgb_alloc_headroom(4096, 1024, "LLC_XID");
-			xid = msgb_put(resp, gph->data_len);
-			memcpy(xid, gph->data, gph->data_len);
-			gprs_llc_tx_xid(lle, resp);
-		}
+		rx_llc_xid(lle, gph);
 		break;
 	case GPRS_LLC_UI:
 		if (gph->seq_tx < lle->vu_recv) {
@@ -846,6 +887,21 @@
 	return 0;
 }
 
+/* Chapter 7.2.1.2 LLGMM-RESET.req */
+int gprs_llgmm_reset(struct gprs_llc_llme *llme)
+{
+	struct msgb *msg = msgb_alloc_headroom(4096, 1024, "LLC_XID");
+	int random = rand();
+
+	/* First XID component must be RESET */
+	msgb_put_xid_par(msg, GPRS_LLC_XID_T_RESET, 0, NULL);
+	/* randomly select new IOV-UI */
+	msgb_put_xid_par(msg, GPRS_LLC_XID_T_IOV_UI, 4, &random);
+
+	/* FIXME: Start T200, wait for XID response */
+	return gprs_llc_tx_xid(&llme->lle[1], msg, 1);
+}
+
 int gprs_llc_init(const char *cipher_plugin_path)
 {
 	return gprs_cipher_load(cipher_plugin_path);