Use libvlr in libmsc (large refactoring)

Original libvlr code is by Harald Welte <laforge@gnumonks.org>,
polished and tweaked by Neels Hofmeyr <nhofmeyr@sysmocom.de>.

This is a long series of trial-and-error development collapsed in one patch.
This may be split in smaller commits if reviewers prefer that. If we can keep
it as one, we have saved ourselves the additional separation work.

SMS:

The SQL based lookup of SMS for attached subscribers no longer works since the
SQL database no longer has the subscriber data. Replace with a round-robin on
the SMS recipient MSISDNs paired with a VLR subscriber RAM lookup whether the
subscriber is currently attached.

If there are many SMS for not-attached subscribers in the SMS database, this
will become inefficient: a DB hit returns a pending SMS, the RAM lookup will
reveal that the subscriber is not attached, after which the DB is hit for the
next SMS. It would become more efficient e.g. by having an MSISDN based hash
list for the VLR subscribers and by marking non-attached SMS recipients in the
SMS database so that they can be excluded with the SQL query already.

There is a sanity limit to do at most 100 db hits per attempt to find a pending
SMS. So if there are more than 100 stored SMS waiting for their recipients to
actually attach to the MSC, it may take more than one SMS queue trigger to
deliver SMS for subscribers that are actually attached.

This is not very beautiful, but is merely intended to carry us over to a time
when we have a proper separate SMSC entity.

Introduce gsm_subscriber_connection ref-counting in libmsc.

Remove/Disable VTY and CTRL commands to create subscribers, which is now a task
of the OsmoHLR. Adjust the python tests accordingly.

Remove VTY cmd subscriber-keep-in-ram.

Use OSMO_GSUP_PORT = 4222 instead of 2222. See
I4222e21686c823985be8ff1f16b1182be8ad6175.

So far use the LAC from conn->bts, will be replaced by conn->lac in
Id3705236350d5f69e447046b0a764bbabc3d493c.

Related: OS#1592 OS#1974
Change-Id: I639544a6cdda77a3aafc4e3446a55393f60e4050
diff --git a/src/libmsc/subscr_conn.c b/src/libmsc/subscr_conn.c
index 91ffe40..b28a511 100644
--- a/src/libmsc/subscr_conn.c
+++ b/src/libmsc/subscr_conn.c
@@ -23,19 +23,24 @@
 
 #include <osmocom/core/logging.h>
 #include <osmocom/core/fsm.h>
+#include <osmocom/core/signal.h>
 
 #include <openbsc/osmo_msc.h>
 #include <openbsc/vlr.h>
 #include <openbsc/debug.h>
 #include <openbsc/transaction.h>
+#include <openbsc/signal.h>
+
+#define SUBSCR_CONN_TIMEOUT 5 /* seconds */
 
 static const struct value_string subscr_conn_fsm_event_names[] = {
 	OSMO_VALUE_STRING(SUBSCR_CONN_E_INVALID),
+	OSMO_VALUE_STRING(SUBSCR_CONN_E_START),
 	OSMO_VALUE_STRING(SUBSCR_CONN_E_ACCEPTED),
+	OSMO_VALUE_STRING(SUBSCR_CONN_E_COMMUNICATING),
 	OSMO_VALUE_STRING(SUBSCR_CONN_E_BUMP),
 	OSMO_VALUE_STRING(SUBSCR_CONN_E_MO_CLOSE),
 	OSMO_VALUE_STRING(SUBSCR_CONN_E_CN_CLOSE),
-	OSMO_VALUE_STRING(SUBSCR_CONN_E_CLOSE_CONF),
 	{ 0, NULL }
 };
 
@@ -50,14 +55,21 @@
 static void paging_resp(struct gsm_subscriber_connection *conn,
 			       enum gsm_paging_event pe)
 {
-	subscr_paging_dispatch(GSM_HOOK_RR_PAGING, pe, NULL, conn, conn->subscr);
+	subscr_paging_dispatch(GSM_HOOK_RR_PAGING, pe, NULL, conn, conn->vsub);
+}
+
+void subscr_conn_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	OSMO_ASSERT(event == SUBSCR_CONN_E_START);
+	osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_NEW,
+				SUBSCR_CONN_TIMEOUT, 0);
 }
 
 void subscr_conn_fsm_new(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
 	struct gsm_subscriber_connection *conn = fi->priv;
 	enum subscr_conn_from from = SUBSCR_CONN_FROM_INVALID;
-	enum gsm_paging_event pe;
+	bool success;
 
 	if (data) {
 		from = *(enum subscr_conn_from*)data;
@@ -67,12 +79,12 @@
 	/* If accepted, transition the state, all other cases mean failure. */
 	switch (event) {
 	case SUBSCR_CONN_E_ACCEPTED:
-		osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_ACCEPTED, 0, 0);
+		osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_ACCEPTED,
+					SUBSCR_CONN_TIMEOUT, 0);
 		break;
 
 	case SUBSCR_CONN_E_MO_CLOSE:
 	case SUBSCR_CONN_E_CN_CLOSE:
-	case SUBSCR_CONN_E_CLOSE_CONF:
 		break;
 
 	default:
@@ -81,23 +93,27 @@
 		break;
 	}
 
-	/* if appropriate, signal paging success or failure */
-	if (from == SUBSCR_CONN_FROM_PAGING_RESP) {
-		pe = (fi->state == SUBSCR_CONN_S_ACCEPTED)?
-			GSM_PAGING_SUCCEEDED : GSM_PAGING_EXPIRED;
-		paging_resp(conn, pe);
-	}
+	success = (fi->state == SUBSCR_CONN_S_ACCEPTED);
+
+	if (from == SUBSCR_CONN_FROM_LU)
+		rate_ctr_inc(&conn->network->msc_ctrs->ctr[
+		             	success ? MSC_CTR_LOC_UPDATE_COMPLETED
+					: MSC_CTR_LOC_UPDATE_FAILED]);
+
+	/* signal paging success or failure in case this was a paging */
+	if (from == SUBSCR_CONN_FROM_PAGING_RESP)
+		paging_resp(conn,
+			    success ? GSM_PAGING_SUCCEEDED
+			    	    : GSM_PAGING_EXPIRED);
 
 	/* On failure, discard the conn */
-	if (fi->state != SUBSCR_CONN_S_ACCEPTED) {
+	if (!success) {
 		/* TODO: on MO_CLOSE or CN_CLOSE, first go to RELEASING and
 		 * await BSC confirmation? */
 		osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0);
 		return;
 	}
 
-	/* On success, handle pending requests and/or close conn */
-
 	if (from == SUBSCR_CONN_FROM_CM_SERVICE_REQ) {
 		conn->received_cm_service_request = true;
 		LOGPFSM(fi, "received_cm_service_request = true\n");
@@ -106,33 +122,6 @@
 	osmo_fsm_inst_dispatch(fi, SUBSCR_CONN_E_BUMP, data);
 }
 
-#if 0
-	case SUBSCR_CONN_E_PARQ_SUCCESS:
-		osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_ACCEPTED, 0, 0);
-		accept_conn = true;
-		/* fall through */
-	case SUBSCR_CONN_E_PARQ_FAILURE:
-		parq_type = data ? *(enum vlr_parq_type*)data : VLR_PR_ARQ_T_INVALID;
-		switch (parq_type) {
-
-		case VLR_PR_ARQ_T_CM_SERV_REQ:
-			accept_conn = handle_cm_serv_result(fi, accept_conn);
-			break;
-
-		case VLR_PR_ARQ_T_PAGING_RESP:
-			accept_conn = handle_paging_result(fi, accept_conn);
-			break;
-
-		default:
-			LOGPFSML(fi, LOGL_ERROR,
-				 "Invalid VLR Process Access Request type"
-				 " %d\n", parq_type);
-			accept_conn = false;
-			break;
-		}
-		break;
-#endif
-
 static void subscr_conn_fsm_bump(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
 	struct gsm_subscriber_connection *conn = fi->priv;
@@ -143,8 +132,7 @@
 	if (conn->received_cm_service_request)
 		return;
 
-	/* is this needed? */
-	if (conn->subscr && !llist_empty(&conn->subscr->requests))
+	if (conn->vsub && !llist_empty(&conn->vsub->cs.requests))
 		return;
 
 	if (trans_has_conn(conn))
@@ -153,9 +141,19 @@
 	osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0);
 }
 
+static void subscr_conn_fsm_accepted_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_ATTACHED, conn->vsub);
+}
+
 static void subscr_conn_fsm_accepted(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
 	switch (event) {
+	case SUBSCR_CONN_E_COMMUNICATING:
+		osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_COMMUNICATING, 0, 0);
+		return;
+
 	case SUBSCR_CONN_E_BUMP:
 		subscr_conn_fsm_bump(fi, event, data);
 		return;
@@ -169,34 +167,68 @@
 	osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0);
 }
 
-static void subscr_conn_fsm_release(struct osmo_fsm_inst *fi, uint32_t prev_state)
+static void subscr_conn_fsm_communicating(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case SUBSCR_CONN_E_COMMUNICATING:
+		/* no-op */
+		return;
+
+	case SUBSCR_CONN_E_BUMP:
+		subscr_conn_fsm_bump(fi, event, data);
+		return;
+
+	default:
+		break;
+	}
+	/* Whatever unexpected happens in the accepted state, it means release.
+	 * Even if an unexpected event is passed, the safest thing to do is
+	 * discard the conn. We don't expect another SUBSCR_CONN_E_ACCEPTED. */
+	osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0);
+}
+
+static void subscr_conn_fsm_cleanup(struct osmo_fsm_inst *fi,
+				    enum osmo_fsm_term_cause cause)
 {
 	struct gsm_subscriber_connection *conn = fi->priv;
+	fi->priv = NULL;
+
 	if (!conn)
 		return;
 
-	/* temporary hack, see owned_by_msc */
-	if (!conn->owned_by_msc) {
-		DEBUGP(DMM, "%s leaving bsc_subscr_con_free() to bsc_api.c, owned_by_msc = false\n",
-		       subscr_name(conn->subscr));
-		return;
-	}
+	conn->conn_fsm = NULL;
+ 	msc_subscr_conn_close(conn, cause);
+	msc_subscr_conn_put(conn);
+}
 
-	DEBUGP(DMM, "%s calling bsc_subscr_con_free(), owned_by_msc = true\n",
-	       subscr_name(conn->subscr));
-	gsm0808_clear(conn);
-	bsc_subscr_con_free(conn);
+int subscr_conn_fsm_timeout(struct osmo_fsm_inst *fi)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	if (conn)
+		vlr_subscr_conn_timeout(conn->vsub);
+	osmo_fsm_inst_dispatch(fi, SUBSCR_CONN_E_CN_CLOSE, NULL);
+	return 0;
+}
+
+static void subscr_conn_fsm_release(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
 }
 
 #define S(x)	(1 << (x))
 
 static const struct osmo_fsm_state subscr_conn_fsm_states[] = {
+	[SUBSCR_CONN_S_INIT] = {
+		.name = OSMO_STRINGIFY(SUBSCR_CONN_S_INIT),
+		.in_event_mask = S(SUBSCR_CONN_E_START),
+		.out_state_mask = S(SUBSCR_CONN_S_NEW),
+		.action = subscr_conn_fsm_init,
+	},
 	[SUBSCR_CONN_S_NEW] = {
 		.name = OSMO_STRINGIFY(SUBSCR_CONN_S_NEW),
 		.in_event_mask = S(SUBSCR_CONN_E_ACCEPTED) |
 				 S(SUBSCR_CONN_E_MO_CLOSE) |
-				 S(SUBSCR_CONN_E_CN_CLOSE) |
-				 S(SUBSCR_CONN_E_CLOSE_CONF),
+				 S(SUBSCR_CONN_E_CN_CLOSE),
 		.out_state_mask = S(SUBSCR_CONN_S_ACCEPTED) |
 				  S(SUBSCR_CONN_S_RELEASED),
 		.action = subscr_conn_fsm_new,
@@ -204,14 +236,27 @@
 	[SUBSCR_CONN_S_ACCEPTED] = {
 		.name = OSMO_STRINGIFY(SUBSCR_CONN_S_ACCEPTED),
 		/* allow everything to release for any odd behavior */
-		.in_event_mask = S(SUBSCR_CONN_E_ACCEPTED) |
-				 S(SUBSCR_CONN_E_BUMP) |
+		.in_event_mask = S(SUBSCR_CONN_E_COMMUNICATING) |
+		                 S(SUBSCR_CONN_E_BUMP) |
+				 S(SUBSCR_CONN_E_ACCEPTED) |
 				 S(SUBSCR_CONN_E_MO_CLOSE) |
-				 S(SUBSCR_CONN_E_CN_CLOSE) |
-				 S(SUBSCR_CONN_E_CLOSE_CONF),
-		.out_state_mask = S(SUBSCR_CONN_S_RELEASED),
+				 S(SUBSCR_CONN_E_CN_CLOSE),
+		.out_state_mask = S(SUBSCR_CONN_S_RELEASED) |
+				  S(SUBSCR_CONN_S_COMMUNICATING),
+		.onenter = subscr_conn_fsm_accepted_enter,
 		.action = subscr_conn_fsm_accepted,
 	},
+	[SUBSCR_CONN_S_COMMUNICATING] = {
+		.name = OSMO_STRINGIFY(SUBSCR_CONN_S_COMMUNICATING),
+		/* allow everything to release for any odd behavior */
+		.in_event_mask = S(SUBSCR_CONN_E_BUMP) |
+				 S(SUBSCR_CONN_E_ACCEPTED) |
+				 S(SUBSCR_CONN_E_COMMUNICATING) |
+				 S(SUBSCR_CONN_E_MO_CLOSE) |
+				 S(SUBSCR_CONN_E_CN_CLOSE),
+		.out_state_mask = S(SUBSCR_CONN_S_RELEASED),
+		.action = subscr_conn_fsm_communicating,
+	},
 	[SUBSCR_CONN_S_RELEASED] = {
 		.name = OSMO_STRINGIFY(SUBSCR_CONN_S_RELEASED),
 		.onenter = subscr_conn_fsm_release,
@@ -226,6 +271,8 @@
 	.allstate_action = NULL,
 	.log_subsys = DVLR,
 	.event_names = subscr_conn_fsm_event_names,
+	.cleanup = subscr_conn_fsm_cleanup,
+	.timer_cb = subscr_conn_fsm_timeout,
 };
 
 int msc_create_conn_fsm(struct gsm_subscriber_connection *conn, const char *id)
@@ -239,7 +286,14 @@
 		return -EINVAL;
 	}
 
-	fi = osmo_fsm_inst_alloc(&subscr_conn_fsm, conn, conn, LOGL_DEBUG, id);
+	/* Allocate the FSM not with the subscr_conn. Semantically it would
+	 * make sense, but in subscr_conn_fsm_cleanup(), we want to discard the
+	 * subscriber connection. If the FSM is freed along with the subscriber
+	 * connection, then in _osmo_fsm_inst_term() the osmo_fsm_inst_free()
+	 * that follows the cleanup() call would run into a double free. */
+	fi = osmo_fsm_inst_alloc(&subscr_conn_fsm, conn->network,
+				 msc_subscr_conn_get(conn),
+				 LOGL_DEBUG, id);
 
 	if (!fi) {
 		LOGP(DMM, LOGL_ERROR,
@@ -247,6 +301,7 @@
 		return -ENOMEM;
 	}
 	conn->conn_fsm = fi;
+	osmo_fsm_inst_dispatch(conn->conn_fsm, SUBSCR_CONN_E_START, NULL);
 	return 0;
 }
 
@@ -254,15 +309,23 @@
 {
 	if (!conn)
 		return false;
-	if (!conn->subscr)
+	if (!conn->vsub)
 		return false;
 	if (!conn->conn_fsm)
 		return false;
-	if (conn->conn_fsm->state != SUBSCR_CONN_S_ACCEPTED)
+	if (!(conn->conn_fsm->state == SUBSCR_CONN_S_ACCEPTED
+	      || conn->conn_fsm->state == SUBSCR_CONN_S_COMMUNICATING))
 		return false;
 	return true;
 }
 
+void msc_subscr_conn_communicating(struct gsm_subscriber_connection *conn)
+{
+	OSMO_ASSERT(conn);
+	osmo_fsm_inst_dispatch(conn->conn_fsm, SUBSCR_CONN_E_COMMUNICATING,
+			       NULL);
+}
+
 void msc_subscr_conn_init(void)
 {
 	osmo_fsm_register(&subscr_conn_fsm);