implement PURGE-MS from VLR/SGSN to HLR

Using this procedure, the VLR/SGSN can set the cs/ps purged
flag for the subscriber.  We might not even need to store this
persistent in the database according to spec, but let's do it anyway, at
least until it turns out to be a performance issue.
diff --git a/sql/hlr.sql b/sql/hlr.sql
index 9b8156e..041b42b 100644
--- a/sql/hlr.sql
+++ b/sql/hlr.sql
@@ -32,6 +32,8 @@
 	-- Chapter 2.1.8
 	lmsi		INTEGER,
 
+	-- The below purged flags might not even be stored non-volatile,
+	-- refer to TS 23.012 Chapter 3.6.1.4
 	-- Chapter 2.7.5
 	ms_purged_cs	BOOLEAN NOT NULL DEFAULT 0,
 	-- Chapter 2.7.6
diff --git a/src/db.h b/src/db.h
index a776099..d569fb0 100644
--- a/src/db.h
+++ b/src/db.h
@@ -9,6 +9,8 @@
 	UPD_SGSN_BY_ID	= 2,
 	AUC_BY_IMSI	= 3,
 	AUC_UPD_SQN	= 4,
+	UPD_PURGE_CS_BY_IMSI,
+	UPD_PURGE_PS_BY_IMSI,
 	_NUM_STMT
 };
 
@@ -71,3 +73,6 @@
 		 const struct hlr_subscriber *subscr,
 		 const char *vlr_or_sgsn_number,
 		 bool lu_is_ps);
+
+int db_subscr_purge(struct db_context *dbc,
+		const char *imsi, bool is_ps);
diff --git a/src/db_hlr.c b/src/db_hlr.c
index baa0456..ca44e8e 100644
--- a/src/db_hlr.c
+++ b/src/db_hlr.c
@@ -135,3 +135,48 @@
 
 	return ret;
 }
+
+int db_subscr_purge(struct db_context *dbc, const char *imsi, bool is_ps)
+{
+	sqlite3_stmt *stmt = dbc->stmt[UPD_VLR_BY_ID];
+	int rc, ret = 1;
+
+	if (is_ps)
+		stmt = dbc->stmt[UPD_PURGE_PS_BY_IMSI];
+	else
+		stmt = dbc->stmt[UPD_PURGE_CS_BY_IMSI];
+
+	rc = sqlite3_bind_int(stmt, 1, 1);
+	if (rc != SQLITE_OK) {
+		LOGP(DAUC, LOGL_ERROR, "Error binding Purged: %d\n", rc);
+		return -1;
+	}
+
+	rc = sqlite3_bind_text(stmt, 2, imsi, -1, SQLITE_STATIC);
+	if (rc != SQLITE_OK) {
+		LOGP(DAUC, LOGL_ERROR, "Error binding IMSI: %d\n", rc);
+		ret = -1;
+		goto out;
+	}
+
+	/* execute the statement */
+	rc = sqlite3_step(stmt);
+	if (rc != SQLITE_DONE) {
+		LOGP(DAUC, LOGL_ERROR, "Error setting Purged: %d\n", rc);
+		ret = -2;
+		goto out;
+	}
+	/* FIXME: return 0 in case IMSI not known */
+out:
+	/* remove bindings and reset statement to be re-executed */
+	rc = sqlite3_clear_bindings(stmt);
+	if (rc != SQLITE_OK) {
+		LOGP(DAUC, LOGL_ERROR, "Error clearing bindings: %d\n", rc);
+	}
+	rc = sqlite3_reset(stmt);
+	if (rc != SQLITE_OK) {
+		LOGP(DAUC, LOGL_ERROR, "Error in sqlite3_reset: %d\n", rc);
+	}
+
+	return ret;
+}
diff --git a/src/hlr.c b/src/hlr.c
index 3b17708..0e31a43 100644
--- a/src/hlr.c
+++ b/src/hlr.c
@@ -406,6 +406,43 @@
 	return 0;
 }
 
+static int rx_purge_ms_req(struct osmo_gsup_conn *conn,
+			   const struct osmo_gsup_message *gsup)
+{
+	struct osmo_gsup_message gsup_reply = {0};
+	struct msgb *msg_out;
+	bool is_ps = false;
+	int rc;
+
+	LOGP(DAUC, LOGL_INFO, "%s: Purge MS (%s)\n", gsup->imsi,
+		is_ps ? "PS" : "CS");
+
+	memcpy(gsup_reply.imsi, gsup->imsi, sizeof(gsup_reply.imsi));
+
+	if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS)
+		is_ps = true;
+
+	/* FIXME: check if the VLR that sends the purge is the same that
+	 * we have on record. Only update if yes */
+
+	/* Perform the actual update of the DB */
+	rc = db_subscr_purge(g_dbc, gsup->imsi, is_ps);
+
+	if (rc == 1)
+		gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_RESULT;
+	else if (rc == 0) {
+		gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR;
+		gsup_reply.cause = GMM_CAUSE_IMSI_UNKNOWN;
+	} else {
+		gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR;
+		gsup_reply.cause = GMM_CAUSE_NET_FAIL;
+	}
+
+	msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP AUC response");
+	osmo_gsup_encode(msg_out, &gsup_reply);
+	return osmo_gsup_conn_send(conn, msg_out);
+}
+
 static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
 {
 	static struct osmo_gsup_message gsup;
@@ -425,6 +462,9 @@
 	case OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST:
 		rx_upd_loc_req(conn, &gsup);
 		break;
+	case OSMO_GSUP_MSGT_PURGE_MS_REQUEST:
+		rx_purge_ms_req(conn, &gsup);
+		break;
 	/* responses to requests sent by us */
 	case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
 	case OSMO_GSUP_MSGT_INSERT_DATA_RESULT: