vlr: optionally send IMEI early to HLR

When 'check-imei-rqd 1 early' is set in the config, send the IMEI to
the HLR before doing the location update with the HLR.

The OsmoHLR documentation referenced in the code will be added in
osmo-hlr.git's Change-Id I2dd4a56f7b8be8b5d0e6fc32e04459e5e278d0a9.

Related: OS#2542
Change-Id: I88283cad23793b475445d814ff49db534cb41244
diff --git a/src/libvlr/vlr.c b/src/libvlr/vlr.c
index f76a7ee..b156b43 100644
--- a/src/libvlr/vlr.c
+++ b/src/libvlr/vlr.c
@@ -1043,6 +1043,8 @@
 		return -ENODEV;
 	}
 
+	/* Dispatch result to vsub->lu_fsm, which will either handle the result by itself (Check IMEI early) or dispatch
+         * it further to lu_compl_vlr_fsm (Check IMEI after LU). */
 	if (gsup->message_type == OSMO_GSUP_MSGT_CHECK_IMEI_RESULT) {
 		if (gsup->imei_result == OSMO_GSUP_IMEI_RESULT_ACK)
 			osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_HLR_IMEI_ACK, NULL);
diff --git a/src/libvlr/vlr_lu_fsm.c b/src/libvlr/vlr_lu_fsm.c
index 15ab88c..9dff4aa 100644
--- a/src/libvlr/vlr_lu_fsm.c
+++ b/src/libvlr/vlr_lu_fsm.c
@@ -460,7 +460,8 @@
 
 	/* TODO: Trace_Subscriber_Activity_VLR */
 
-	if (vlr->cfg.check_imei_rqd) {
+	/* If imeisv_early is enabled: IMEI already retrieved and checked (vlr_loc_upd_node1_pre), don't do it again. */
+	if (vlr->cfg.check_imei_rqd && !vlr->cfg.retrieve_imeisv_early) {
 		/* Check IMEI VLR */
 		osmo_fsm_inst_state_chg(fi,
 					lcvp->assign_tmsi ?
@@ -924,6 +925,39 @@
 	}
 }
 
+static void vlr_loc_upd_node1_pre(struct osmo_fsm_inst *fi)
+{
+	struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
+	struct vlr_instance *vlr = lfp->vlr;
+
+	LOGPFSM(fi, "%s()\n", __func__);
+
+	if (vlr->cfg.check_imei_rqd && vlr->cfg.retrieve_imeisv_early) {
+		osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY, vlr_timer(lfp->vlr, 3270), 3270);
+		vlr_subscr_tx_req_check_imei(lfp->vsub);
+	} else {
+		vlr_loc_upd_node1(fi);
+	}
+}
+
+/* End of Check_IMEI Procedure. Executed early (before the location update), so we can send the IMEI to the HLR even if
+ * the MS would be rejected in LU. See the "Configuring the Subscribers Create on Demand Feature" section of the OsmoHLR
+ * user manual for a detailed explanation of the use case. */
+static void lu_fsm_wait_hlr_check_imei_early(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case VLR_ULA_E_HLR_IMEI_ACK:
+		vlr_loc_upd_node1(fi);
+		break;
+	case VLR_ULA_E_HLR_IMEI_NACK:
+		lu_fsm_failure(fi, GSM48_REJECT_ILLEGAL_ME);
+		break;
+	default:
+		OSMO_ASSERT(0);
+		break;
+	}
+}
+
 static void vlr_loc_upd_want_imsi(struct osmo_fsm_inst *fi)
 {
 	struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
@@ -937,7 +971,7 @@
 	osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_IMSI,
 				vlr_timer(vlr, 3270), 3270);
 	vlr->ops.tx_id_req(lfp->msc_conn_ref, GSM_MI_TYPE_IMSI);
-	/* will continue at vlr_loc_upd_node1() once IMSI arrives */
+	/* will continue at vlr_loc_upd_node1_pre() once IMSI arrives */
 }
 
 static int assoc_lfp_with_sub(struct osmo_fsm_inst *fi, struct vlr_subscr *vsub)
@@ -1043,7 +1077,7 @@
 	if (!lfp->vsub->imsi[0])
 		vlr_loc_upd_want_imsi(fi);
 	else
-		vlr_loc_upd_node1(fi);
+		vlr_loc_upd_node1_pre(fi);
 }
 
 static void lu_fsm_idle(struct osmo_fsm_inst *fi, uint32_t event,
@@ -1097,7 +1131,7 @@
 {
 	switch (event) {
 	case VLR_ULA_E_SEND_ID_ACK:
-		vlr_loc_upd_node1(fi);
+		vlr_loc_upd_node1_pre(fi);
 		break;
 	case VLR_ULA_E_SEND_ID_NACK:
 		vlr_loc_upd_want_imsi(fi);
@@ -1165,7 +1199,7 @@
 	switch (event) {
 	case VLR_ULA_E_ID_IMSI:
 		vlr_subscr_set_imsi(vsub, mi_string);
-		vlr_loc_upd_node1(fi);
+		vlr_loc_upd_node1_pre(fi);
 		break;
 	default:
 		OSMO_ASSERT(0);
@@ -1305,6 +1339,7 @@
 				  S(VLR_ULA_S_WAIT_IMSI) |
 				  S(VLR_ULA_S_WAIT_AUTH) |
 				  S(VLR_ULA_S_WAIT_CIPH) |
+				  S(VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY) |
 				  S(VLR_ULA_S_WAIT_HLR_UPD) |
 				  S(VLR_ULA_S_DONE),
 		.name = OSMO_STRINGIFY(VLR_ULA_S_IDLE),
@@ -1316,6 +1351,7 @@
 				  S(VLR_ULA_S_WAIT_IMSI) |
 				  S(VLR_ULA_S_WAIT_AUTH) |
 				  S(VLR_ULA_S_WAIT_CIPH) |
+				  S(VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY) |
 				  S(VLR_ULA_S_WAIT_HLR_UPD) |
 				  S(VLR_ULA_S_DONE),
 		.name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_IMEISV),
@@ -1327,6 +1363,7 @@
 		.out_state_mask = S(VLR_ULA_S_WAIT_IMSI) |
 				  S(VLR_ULA_S_WAIT_AUTH) |
 				  S(VLR_ULA_S_WAIT_CIPH) |
+				  S(VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY) |
 				  S(VLR_ULA_S_DONE),
 		.name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_PVLR),
 		.action = lu_fsm_wait_pvlr,
@@ -1352,11 +1389,23 @@
 		.in_event_mask = S(VLR_ULA_E_ID_IMSI),
 		.out_state_mask = S(VLR_ULA_S_WAIT_AUTH) |
 				  S(VLR_ULA_S_WAIT_CIPH) |
+				  S(VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY) |
 				  S(VLR_ULA_S_WAIT_HLR_UPD) |
 				  S(VLR_ULA_S_DONE),
 		.name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_IMSI),
 		.action = lu_fsm_wait_imsi,
 	},
+	[VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY] = {
+		.in_event_mask = S(VLR_ULA_E_HLR_IMEI_ACK) |
+				 S(VLR_ULA_E_HLR_IMEI_NACK),
+		.out_state_mask = S(VLR_ULA_S_WAIT_AUTH) |
+				  S(VLR_ULA_S_WAIT_CIPH) |
+				  S(VLR_ULA_S_WAIT_HLR_UPD) |
+				  S(VLR_ULA_S_WAIT_LU_COMPL) |
+				  S(VLR_ULA_S_DONE),
+		.name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY),
+		.action = lu_fsm_wait_hlr_check_imei_early,
+	},
 	[VLR_ULA_S_WAIT_HLR_UPD] = {
 		.in_event_mask = S(VLR_ULA_E_HLR_LU_RES) |
 				 S(VLR_ULA_E_UPD_HLR_COMPL),
diff --git a/src/libvlr/vlr_lu_fsm.h b/src/libvlr/vlr_lu_fsm.h
index 5cf13c7..b5c4a5e 100644
--- a/src/libvlr/vlr_lu_fsm.h
+++ b/src/libvlr/vlr_lu_fsm.h
@@ -9,6 +9,7 @@
 	VLR_ULA_S_WAIT_AUTH,	/* Waiting for Authentication */
 	VLR_ULA_S_WAIT_CIPH,	/* Waiting for Ciphering Complete */
 	VLR_ULA_S_WAIT_IMSI,	/* Waiting for IMSI from MS */
+	VLR_ULA_S_WAIT_HLR_CHECK_IMEI_EARLY, /* Waiting for Check IMEI result from HLR */
 	VLR_ULA_S_WAIT_HLR_UPD,	/* Waiting for end of HLR update */
 	VLR_ULA_S_WAIT_LU_COMPL,/* Waiting for LU complete */
 	VLR_ULA_S_WAIT_LU_COMPL_STANDALONE, /* Standalone VLR */