refactor VLR FSM result handling

Instead of keeping separate enums for FSM results and translating between those
and the actual 04.08 reject causes that will ultimately reach the MS, just pass
enum gsm48_reject_value cause codes around everywhere.

Collapse some VLR *_timeout() and *_cancel() api to just *_cancel() with a
gsm48 cause arg.

(Hopefully) improve a few reject causes, but otherwise just aim for more
transparent decisions on which cause value is used, for future fixes of
returned causes.

Depends: I6661f139e68a498fb1bef10c266c2f064b72774a (libosmocore)
Change-Id: I27bf8d68737ff1f8dc6d11fb1eac3d391aab0cb1
diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c
index b51fd16..2b8e4c1 100644
--- a/src/libmsc/gsm_04_08.c
+++ b/src/libmsc/gsm_04_08.c
@@ -644,7 +644,7 @@
 }
 
 static int msc_vlr_tx_cm_serv_acc(void *msc_conn_ref);
-static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum vlr_proc_arq_result result);
+static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum gsm48_reject_value result);
 
 static int cm_serv_reuse_conn(struct gsm_subscriber_connection *conn, const uint8_t *mi_lv)
 {
@@ -675,7 +675,7 @@
 
 	LOGP(DMM, LOGL_ERROR, "%s: CM Service Request with mismatching mobile identity: %s %s\n",
 	     vlr_subscr_name(conn->vsub), gsm48_mi_type_name(mi_type), mi_string);
-	msc_vlr_tx_cm_serv_rej(conn, VLR_PR_ARQ_RES_ILLEGAL_SUBSCR);
+	msc_vlr_tx_cm_serv_rej(conn, GSM48_REJECT_ILLEGAL_MS);
 	return -EINVAL;
 
 accept_reuse:
@@ -774,7 +774,7 @@
 	if (msc_subscr_conn_is_establishing_auth_ciph(conn)) {
 		LOGP(DMM, LOGL_ERROR,
 		     "Cannot accept CM Service Request, conn already busy establishing authenticity\n");
-		msc_vlr_tx_cm_serv_rej(conn, VLR_PR_ARQ_RES_UNKNOWN_ERROR);
+		msc_vlr_tx_cm_serv_rej(conn, GSM48_REJECT_CONGESTION);
 		return -EINVAL;
 		/* or should we accept and note down the service request anyway? */
 	}
@@ -3576,7 +3576,7 @@
 }
 
 /* VLR asks us to transmit a Location Update Reject */
-static int msc_vlr_tx_lu_rej(void *msc_conn_ref, uint8_t cause)
+static int msc_vlr_tx_lu_rej(void *msc_conn_ref, enum gsm48_reject_value cause)
 {
 	struct gsm_subscriber_connection *conn = msc_conn_ref;
 	return gsm0408_loc_upd_rej(conn, cause);
@@ -3605,36 +3605,11 @@
 }
 
 /* VLR asks us to transmit a CM Service Reject */
-static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum vlr_proc_arq_result result)
+static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum gsm48_reject_value cause)
 {
-	uint8_t cause;
 	struct gsm_subscriber_connection *conn = msc_conn_ref;
 	int rc;
 
-	switch (result) {
-	default:
-	case VLR_PR_ARQ_RES_NONE:
-	case VLR_PR_ARQ_RES_SYSTEM_FAILURE:
-	case VLR_PR_ARQ_RES_UNKNOWN_ERROR:
-		cause = GSM48_REJECT_NETWORK_FAILURE;
-		break;
-	case VLR_PR_ARQ_RES_ILLEGAL_SUBSCR:
-		cause = GSM48_REJECT_LOC_NOT_ALLOWED;
-		break;
-	case VLR_PR_ARQ_RES_UNIDENT_SUBSCR:
-		cause = GSM48_REJECT_INVALID_MANDANTORY_INF;
-		break;
-	case VLR_PR_ARQ_RES_ROAMING_NOTALLOWED:
-		cause = GSM48_REJECT_ROAMING_NOT_ALLOWED;
-		break;
-	case VLR_PR_ARQ_RES_ILLEGAL_EQUIP:
-		cause = GSM48_REJECT_ILLEGAL_MS;
-		break;
-	case VLR_PR_ARQ_RES_TIMEOUT:
-		cause = GSM48_REJECT_CONGESTION;
-		break;
-	};
-
 	rc = msc_gsm48_tx_mm_serv_rej(conn, cause);
 
 	if (conn->received_cm_service_request) {
diff --git a/src/libmsc/subscr_conn.c b/src/libmsc/subscr_conn.c
index 9f280bc..1b3b240 100644
--- a/src/libmsc/subscr_conn.c
+++ b/src/libmsc/subscr_conn.c
@@ -103,8 +103,11 @@
 
 static void log_close_event(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
-	if (data)
-		LOGPFSML(fi, LOGL_DEBUG, "Close event, cause %u\n", *(uint32_t*)data);
+	enum gsm48_reject_value *cause = data;
+	/* The close event itself is logged by the FSM. We can only add the cause value, if present. */
+	if (!cause || !*cause)
+		return;
+	LOGPFSML(fi, LOGL_NOTICE, "Close event, cause: %s\n", gsm48_reject_value_name(*cause));
 }
 
 static void subscr_conn_fsm_new(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -250,7 +253,7 @@
 		LOGPFSML(fi, LOGL_ERROR, "Timeout while releasing, discarding right now\n");
 		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_TIMEOUT, NULL);
 	} else {
-		uint32_t cause = GSM_CAUSE_NET_FAIL;
+		enum gsm48_reject_value cause = GSM48_REJECT_CONGESTION;
 		osmo_fsm_inst_dispatch(fi, SUBSCR_CONN_E_CN_CLOSE, &cause);
 	}
 	return 0;
@@ -273,7 +276,7 @@
 	}
 
 	/* Cancel all VLR FSMs, if any */
-	vlr_subscr_conn_timeout(conn->vsub);
+	vlr_subscr_cancel_attach_fsm(conn->vsub, OSMO_FSM_TERM_ERROR, GSM48_REJECT_CONGESTION);
 
 	/* If we're closing in a middle of a trans, we need to clean up */
 	trans_conn_closed(conn);
diff --git a/src/libvlr/vlr.c b/src/libvlr/vlr.c
index 3e14593..2d232be 100644
--- a/src/libvlr/vlr.c
+++ b/src/libvlr/vlr.c
@@ -271,23 +271,19 @@
 	return vlr_subscr_tx_gsup_message(vsub, &gsup_msg);
 }
 
-void vlr_subscr_cancel(struct vlr_subscr *vsub, enum gsm48_gmm_cause cause)
+void vlr_subscr_cancel_attach_fsm(struct vlr_subscr *vsub,
+				  enum osmo_fsm_term_cause fsm_cause,
+				  uint8_t gsm48_cause)
 {
 	if (!vsub)
 		return;
 
-	if (vsub->lu_fsm) {
-		if (vsub->lu_fsm->state == VLR_ULA_S_WAIT_HLR_UPD)
-			osmo_fsm_inst_dispatch(vsub->lu_fsm,
-					       VLR_ULA_E_HLR_LU_RES,
-					       (void*)&cause);
-		else
-			osmo_fsm_inst_term(vsub->lu_fsm, OSMO_FSM_TERM_ERROR,
-					   0);
-	}
-
+	vlr_subscr_get(vsub);
+	if (vsub->lu_fsm)
+		vlr_loc_update_cancel(vsub->lu_fsm, fsm_cause, gsm48_cause);
 	if (vsub->proc_arq_fsm)
-		osmo_fsm_inst_term(vsub->proc_arq_fsm, OSMO_FSM_TERM_ERROR, 0);
+		vlr_parq_cancel(vsub->proc_arq_fsm, fsm_cause, gsm48_cause);
+	vlr_subscr_put(vsub);
 }
 
 /* Call vlr_subscr_cancel(), then completely drop the entry from the VLR */
@@ -803,10 +799,111 @@
 	return 0;
 }
 
+static void gmm_cause_to_fsm_and_mm_cause(enum gsm48_gmm_cause gmm_cause,
+					  enum osmo_fsm_term_cause *fsm_cause_p,
+					  enum gsm48_reject_value *gsm48_rej_p)
+{
+	enum osmo_fsm_term_cause fsm_cause = OSMO_FSM_TERM_ERROR;
+	enum gsm48_reject_value gsm48_rej = GSM48_REJECT_NETWORK_FAILURE;
+	switch (gmm_cause) {
+	case GMM_CAUSE_IMSI_UNKNOWN:
+		gsm48_rej = GSM48_REJECT_IMSI_UNKNOWN_IN_HLR;
+		break;
+	case GMM_CAUSE_ILLEGAL_MS:
+		gsm48_rej = GSM48_REJECT_ILLEGAL_MS;
+		break;
+	case GMM_CAUSE_IMEI_NOT_ACCEPTED:
+		gsm48_rej = GSM48_REJECT_IMEI_NOT_ACCEPTED;
+		break;
+	case GMM_CAUSE_ILLEGAL_ME:
+		gsm48_rej = GSM48_REJECT_ILLEGAL_ME;
+		break;
+	case GMM_CAUSE_GPRS_NOTALLOWED:
+		gsm48_rej = GSM48_REJECT_GPRS_NOT_ALLOWED;
+		break;
+	case GMM_CAUSE_GPRS_OTHER_NOTALLOWED:
+		gsm48_rej = GSM48_REJECT_SERVICES_NOT_ALLOWED;
+		break;
+	case GMM_CAUSE_MS_ID_NOT_DERIVED:
+		gsm48_rej = GSM48_REJECT_MS_IDENTITY_NOT_DERVIVABLE;
+		break;
+	case GMM_CAUSE_IMPL_DETACHED:
+		gsm48_rej = GSM48_REJECT_IMPLICITLY_DETACHED;
+		break;
+	case GMM_CAUSE_PLMN_NOTALLOWED:
+		gsm48_rej = GSM48_REJECT_PLMN_NOT_ALLOWED;
+		break;
+	case GMM_CAUSE_LA_NOTALLOWED:
+		gsm48_rej = GSM48_REJECT_LOC_NOT_ALLOWED;
+		break;
+	case GMM_CAUSE_ROAMING_NOTALLOWED:
+		gsm48_rej = GSM48_REJECT_ROAMING_NOT_ALLOWED;
+		break;
+	case GMM_CAUSE_NO_GPRS_PLMN:
+		gsm48_rej = GSM48_REJECT_GPRS_NOT_ALLOWED_IN_PLMN;
+		break;
+	case GMM_CAUSE_MSC_TEMP_NOTREACH:
+		gsm48_rej = GSM48_REJECT_MSC_TMP_NOT_REACHABLE;
+		break;
+	case GMM_CAUSE_SYNC_FAIL:
+		gsm48_rej = GSM48_REJECT_SYNCH_FAILURE;
+		break;
+	case GMM_CAUSE_CONGESTION:
+		gsm48_rej = GSM48_REJECT_CONGESTION;
+		break;
+	case GMM_CAUSE_SEM_INCORR_MSG:
+		gsm48_rej = GSM48_REJECT_INCORRECT_MESSAGE;
+		break;
+	case GMM_CAUSE_INV_MAND_INFO:
+		gsm48_rej = GSM48_REJECT_INVALID_MANDANTORY_INF;
+		break;
+	case GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL:
+		gsm48_rej = GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED;
+		break;
+	case GMM_CAUSE_MSGT_INCOMP_P_STATE:
+		gsm48_rej = GSM48_REJECT_MSG_TYPE_NOT_COMPATIBLE;
+		break;
+	case GMM_CAUSE_IE_NOTEXIST_NOTIMPL:
+		gsm48_rej = GSM48_REJECT_INF_ELEME_NOT_IMPLEMENTED;
+		break;
+	case GMM_CAUSE_COND_IE_ERR:
+		gsm48_rej = GSM48_REJECT_CONDTIONAL_IE_ERROR;
+		break;
+	case GMM_CAUSE_MSG_INCOMP_P_STATE:
+		gsm48_rej = GSM48_REJECT_MSG_NOT_COMPATIBLE;
+		break;
+	case GMM_CAUSE_PROTO_ERR_UNSPEC:
+		gsm48_rej = GSM48_REJECT_PROTOCOL_ERROR;
+		break;
+
+	case GMM_CAUSE_NO_SUIT_CELL_IN_LA:
+	case GMM_CAUSE_MAC_FAIL:
+	case GMM_CAUSE_GSM_AUTH_UNACCEPT:
+	case GMM_CAUSE_NOT_AUTH_FOR_CSG:
+	case GMM_CAUSE_SMS_VIA_GPRS_IN_RA:
+	case GMM_CAUSE_NO_PDP_ACTIVATED:
+	case GMM_CAUSE_NET_FAIL:
+		gsm48_rej = GSM48_REJECT_NETWORK_FAILURE;
+		break;
+	}
+	switch (gmm_cause) {
+		/* refine any error causes here? */
+	default:
+		fsm_cause = OSMO_FSM_TERM_ERROR;
+		break;
+	}
+	if (fsm_cause_p)
+		*fsm_cause_p = fsm_cause;
+	if (gsm48_rej_p)
+		*gsm48_rej_p = gsm48_rej;
+}
+
 /* Handle LOCATION CANCEL request from HLR */
 static int vlr_subscr_handle_cancel_req(struct vlr_subscr *vsub,
 					struct osmo_gsup_message *gsup_msg)
 {
+	enum gsm48_reject_value gsm48_rej;
+	enum osmo_fsm_term_cause fsm_cause;
 	struct osmo_gsup_message gsup_reply = {0};
 	int rc, is_update_procedure = !gsup_msg->cancel_type ||
 		gsup_msg->cancel_type == OSMO_GSUP_CANCEL_TYPE_UPDATE;
@@ -818,7 +915,8 @@
 	gsup_reply.message_type = OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT;
 	rc = vlr_subscr_tx_gsup_message(vsub, &gsup_reply);
 
-	vlr_subscr_cancel(vsub, gsup_msg->cause);
+	gmm_cause_to_fsm_and_mm_cause(gsup_msg->cause, &fsm_cause, &gsm48_rej);
+	vlr_subscr_cancel_attach_fsm(vsub, fsm_cause, gsm48_rej);
 
 	return rc;
 }
@@ -998,7 +1096,7 @@
 int vlr_subscr_rx_imsi_detach(struct vlr_subscr *vsub)
 {
 	/* paranoia: should any LU or PARQ FSMs still be running, stop them. */
-	vlr_subscr_cancel(vsub, GMM_CAUSE_IMPL_DETACHED);
+	vlr_subscr_cancel_attach_fsm(vsub, OSMO_FSM_TERM_ERROR, GSM48_REJECT_CONGESTION);
 
 	vsub->imsi_detached_flag = true;
 
@@ -1015,15 +1113,7 @@
  */
 void vlr_subscr_conn_timeout(struct vlr_subscr *vsub)
 {
-	if (!vsub)
-		return;
-
-	vlr_subscr_get(vsub);
-	if (vsub->lu_fsm)
-		vlr_loc_update_conn_timeout(vsub->lu_fsm);
-	if (vsub->proc_arq_fsm)
-		vlr_parq_conn_timeout(vsub->proc_arq_fsm);
-	vlr_subscr_put(vsub);
+	vlr_subscr_cancel_attach_fsm(vsub, OSMO_FSM_TERM_TIMEOUT, GSM48_REJECT_CONGESTION);
 }
 
 struct vlr_instance *vlr_alloc(void *ctx, const struct vlr_ops *ops)
diff --git a/src/libvlr/vlr_access_req_fsm.c b/src/libvlr/vlr_access_req_fsm.c
index 3845f26..dd95821 100644
--- a/src/libvlr/vlr_access_req_fsm.c
+++ b/src/libvlr/vlr_access_req_fsm.c
@@ -36,19 +36,6 @@
  * Process_Access_Request_VLR, TS 29.002 Chapter 25.4.2
  ***********************************************************************/
 
-const struct value_string vlr_proc_arq_result_names[] = {
-	OSMO_VALUE_STRING(VLR_PR_ARQ_RES_NONE),
-	OSMO_VALUE_STRING(VLR_PR_ARQ_RES_SYSTEM_FAILURE),
-	OSMO_VALUE_STRING(VLR_PR_ARQ_RES_ILLEGAL_SUBSCR),
-	OSMO_VALUE_STRING(VLR_PR_ARQ_RES_UNIDENT_SUBSCR),
-	OSMO_VALUE_STRING(VLR_PR_ARQ_RES_ROAMING_NOTALLOWED),
-	OSMO_VALUE_STRING(VLR_PR_ARQ_RES_ILLEGAL_EQUIP),
-	OSMO_VALUE_STRING(VLR_PR_ARQ_RES_UNKNOWN_ERROR),
-	OSMO_VALUE_STRING(VLR_PR_ARQ_RES_TIMEOUT),
-	OSMO_VALUE_STRING(VLR_PR_ARQ_RES_PASSED),
-	{ 0, NULL }
-};
-
 static const struct value_string proc_arq_vlr_event_names[] = {
 	OSMO_VALUE_STRING(PR_ARQ_E_START),
 	OSMO_VALUE_STRING(PR_ARQ_E_ID_IMSI),
@@ -73,7 +60,7 @@
 	void *parent_event_data;
 
 	enum vlr_parq_type type;
-	enum vlr_proc_arq_result result;
+	enum gsm48_reject_value result; /*< 0 on success */
 	bool by_tmsi;
 	char imsi[16];
 	uint32_t tmsi;
@@ -97,15 +84,20 @@
 	vlr->ops.subscr_assoc(par->msc_conn_ref, par->vsub);
 }
 
+static const char *vlr_proc_arq_result_name(const struct osmo_fsm_inst *fi)
+{
+	struct proc_arq_priv *par = fi->priv;
+	return par->result? gsm48_reject_value_name(par->result) : "PASSED";
+}
+
 #define proc_arq_fsm_done(fi, res) _proc_arq_fsm_done(fi, res, __FILE__, __LINE__)
 static void _proc_arq_fsm_done(struct osmo_fsm_inst *fi,
-			       enum vlr_proc_arq_result res,
+			       enum gsm48_reject_value gsm48_rej,
 			       const char *file, int line)
 {
 	struct proc_arq_priv *par = fi->priv;
-	LOGPFSMSRC(fi, file, line, "proc_arq_fsm_done(%s)\n",
-		   vlr_proc_arq_result_name(res));
-	par->result = res;
+	par->result = gsm48_rej;
+	LOGPFSMSRC(fi, file, line, "proc_arq_fsm_done(%s)\n", vlr_proc_arq_result_name(fi));
 	osmo_fsm_inst_state_chg(fi, PR_ARQ_S_DONE, 0, 0);
 }
 
@@ -115,10 +107,9 @@
 	struct proc_arq_priv *par = fi->priv;
 	bool success;
 	int rc;
-	LOGPFSM(fi, "Process Access Request result: %s\n",
-		vlr_proc_arq_result_name(par->result));
+	LOGPFSM(fi, "Process Access Request result: %s\n", vlr_proc_arq_result_name(fi));
 
-	success = (par->result == VLR_PR_ARQ_RES_PASSED);
+	success = (par->result == 0);
 
 	/* It would be logical to first dispatch the success event to the
 	 * parent FSM, but that could start actions that send messages to the
@@ -184,7 +175,7 @@
 		return;
 	}
 
-	proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_PASSED);
+	proc_arq_fsm_done(fi, 0);
 }
 
 static void _proc_arq_vlr_post_trace(struct osmo_fsm_inst *fi)
@@ -228,13 +219,13 @@
 
 	if (!vsub->sub_dataconf_by_hlr_ind) {
 		/* Set User Error: Unidentified Subscriber */
-		proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_UNIDENT_SUBSCR);
+		proc_arq_fsm_done(fi, GSM48_REJECT_IMSI_UNKNOWN_IN_HLR);
 		return;
 	}
 	/* We don't feature location area specific blocking (yet). */
 	if (0 /* roaming not allowed in LA */) {
 		/* Set User Error: Roaming not allowed in this LA */
-		proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_ROAMING_NOTALLOWED);
+		proc_arq_fsm_done(fi, GSM48_REJECT_ROAMING_NOT_ALLOWED);
 		return;
 	}
 	vsub->imsi_detached_flag = false;
@@ -302,7 +293,7 @@
 		break;
 	default:
 		LOGPFSML(fi, LOGL_ERROR, "Cannot start ciphering, security context is not established\n");
-		proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_SYSTEM_FAILURE);
+		proc_arq_fsm_done(fi, GSM48_REJECT_NETWORK_FAILURE);
 		return;
 	}
 
@@ -312,7 +303,7 @@
 			      vsub->vlr->cfg.retrieve_imeisv_ciphered)) {
 		LOGPFSML(fi, LOGL_ERROR,
 			 "Failed to send Ciphering Mode Command\n");
-		proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_SYSTEM_FAILURE);
+		proc_arq_fsm_done(fi, GSM48_REJECT_NETWORK_FAILURE);
 		return;
 	}
 
@@ -377,7 +368,7 @@
 				 " terminating the other FSM.\n",
 				 vlr_subscr_name(vsub));
 			proc_arq_fsm_done(vsub->proc_arq_fsm,
-					  VLR_PR_ARQ_RES_SYSTEM_FAILURE);
+					  GSM48_REJECT_NETWORK_FAILURE);
 		}
 		vsub->proc_arq_fsm = fi;
 		assoc_par_with_subscr(fi, vsub);
@@ -390,7 +381,7 @@
 	if (!par->by_tmsi) {
 		/* We couldn't find a subscriber even by IMSI,
 		 * Set User Error: Unidentified Subscriber */
-		proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_UNIDENT_SUBSCR);
+		proc_arq_fsm_done(fi, GSM48_REJECT_MS_IDENTITY_NOT_DERVIVABLE);
 		return;
 	} else {
 		/* TMSI was included, are we permitted to use it? */
@@ -401,7 +392,7 @@
 			return;
 		} else {
 			/* Set User Error: Unidentified Subscriber */
-			proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_UNIDENT_SUBSCR);
+			proc_arq_fsm_done(fi, GSM48_REJECT_MS_IDENTITY_NOT_DERVIVABLE);
 			return;
 		}
 	}
@@ -420,7 +411,7 @@
 	vsub = vlr_subscr_find_by_imsi(vlr, par->imsi);
 	if (!vsub) {
 		/* Set User Error: Unidentified Subscriber */
-		proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_UNIDENT_SUBSCR);
+		proc_arq_fsm_done(fi, GSM48_REJECT_MS_IDENTITY_NOT_DERVIVABLE);
 		return;
 	}
 	assoc_par_with_subscr(fi, vsub);
@@ -432,43 +423,17 @@
 static void proc_arq_vlr_fn_w_auth(struct osmo_fsm_inst *fi,
 				   uint32_t event, void *data)
 {
-	enum vlr_auth_fsm_result res;
-	enum vlr_proc_arq_result ret;
+	enum gsm48_reject_value *cause = data;
 
 	OSMO_ASSERT(event == PR_ARQ_E_AUTH_RES);
 
-	res = data ? *(enum vlr_auth_fsm_result*)data : -1;
-	LOGPFSM(fi, "got %s\n", vlr_auth_fsm_result_name(res));
-
-	switch (res) {
-	case VLR_AUTH_RES_PASSED:
-		/* Node 2 */
-		_proc_arq_vlr_node2(fi);
+	if (!cause || *cause) {
+		proc_arq_fsm_done(fi, cause? *cause : GSM48_REJECT_NETWORK_FAILURE);
 		return;
-	case VLR_AUTH_RES_ABORTED:
-		/* Error */
-		ret = VLR_PR_ARQ_RES_UNKNOWN_ERROR;
-		break;
-	case VLR_AUTH_RES_UNKNOWN_SUBSCR:
-		/* Set User Error: Unidentified Subscriber */
-		ret = VLR_PR_ARQ_RES_UNIDENT_SUBSCR;
-		break;
-	case VLR_AUTH_RES_AUTH_FAILED:
-		/* Set User Error: Illegal Subscriber */
-		ret = VLR_PR_ARQ_RES_ILLEGAL_SUBSCR;
-		break;
-	case VLR_AUTH_RES_PROC_ERR:
-		/* Set User Error: System failure */
-		ret = VLR_PR_ARQ_RES_SYSTEM_FAILURE;
-		break;
-	default:
-		LOGPFSML(fi, LOGL_ERROR, "Unexpected vlr_auth_fsm_result value: %d (data=%p)\n", res, data);
-		ret = VLR_PR_ARQ_RES_UNKNOWN_ERROR;
-		break;
 	}
 
-	/* send process_access_req response to caller, enter error state */
-	proc_arq_fsm_done(fi, ret);
+	/* Node 2 */
+	_proc_arq_vlr_node2(fi);
 }
 
 static void proc_arq_vlr_fn_w_ciph(struct osmo_fsm_inst *fi,
@@ -490,12 +455,12 @@
 		break;
 	case VLR_CIPH_REJECT:
 		LOGPFSM(fi, "ciphering rejected\n");
-		proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_ILLEGAL_SUBSCR);
+		proc_arq_fsm_done(fi, GSM48_REJECT_ILLEGAL_MS);
 		return;
 	default:
 		LOGPFSML(fi, LOGL_ERROR, "invalid ciphering result: %d\n",
 			 res.cause);
-		proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_ILLEGAL_SUBSCR);
+		proc_arq_fsm_done(fi, GSM48_REJECT_ILLEGAL_MS);
 		return;
 	}
 
@@ -549,7 +514,7 @@
 	OSMO_ASSERT(event == PR_ARQ_E_TMSI_ACK);
 
 	/* FIXME: check confirmation? unfreeze? */
-	proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_PASSED);
+	proc_arq_fsm_done(fi, 0);
 }
 
 static const struct osmo_fsm_state proc_arq_vlr_states[] = {
@@ -722,8 +687,7 @@
 	case GSM_MI_TYPE_IMEI:
 		/* TODO: IMEI (emergency call) */
 	default:
-		/* FIXME: directly send reject? */
-		proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_UNIDENT_SUBSCR);
+		proc_arq_fsm_done(fi, GSM48_REJECT_MS_IDENTITY_NOT_DERVIVABLE);
 		return;
 	}
 
@@ -732,12 +696,14 @@
 
 /* Gracefully terminate an FSM created by vlr_proc_acc_req() in case of
  * external timeout (i.e. from MSC). */
-void vlr_parq_conn_timeout(struct osmo_fsm_inst *fi)
+void vlr_parq_cancel(struct osmo_fsm_inst *fi,
+		     enum osmo_fsm_term_cause fsm_cause,
+		     enum gsm48_reject_value gsm48_cause)
 {
 	if (!fi || fi->state == PR_ARQ_S_DONE)
 		return;
-	LOGPFSM(fi, "Connection timed out\n");
-	proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_TIMEOUT);
+	LOGPFSM(fi, "Cancel: %s\n", osmo_fsm_term_cause_name(fsm_cause));
+	proc_arq_fsm_done(fi, gsm48_cause);
 }
 
 
diff --git a/src/libvlr/vlr_auth_fsm.c b/src/libvlr/vlr_auth_fsm.c
index f001eac..d5a8555 100644
--- a/src/libvlr/vlr_auth_fsm.c
+++ b/src/libvlr/vlr_auth_fsm.c
@@ -42,15 +42,6 @@
 	{ 0, NULL }
 };
 
-const struct value_string vlr_auth_fsm_result_names[] = {
-	OSMO_VALUE_STRING(VLR_AUTH_RES_ABORTED),
-	OSMO_VALUE_STRING(VLR_AUTH_RES_UNKNOWN_SUBSCR),
-	OSMO_VALUE_STRING(VLR_AUTH_RES_PROC_ERR),
-	OSMO_VALUE_STRING(VLR_AUTH_RES_AUTH_FAILED),
-	OSMO_VALUE_STRING(VLR_AUTH_RES_PASSED),
-	{0, NULL}
-};
-
 /* private state of the auth_fsm_instance */
 struct auth_fsm_priv {
 	struct vlr_subscr *vsub;
@@ -239,23 +230,30 @@
 	}
 }
 
+static const char *vlr_auth_fsm_result_name(enum gsm48_reject_value result)
+{
+	if (!result)
+		return "PASSED";
+	return get_value_string(gsm48_gmm_cause_names, result);
+}
+
 /* Terminate the Auth FSM Instance and notify parent */
-static void auth_fsm_term(struct osmo_fsm_inst *fi, enum vlr_auth_fsm_result res)
+static void auth_fsm_term(struct osmo_fsm_inst *fi, enum gsm48_reject_value result)
 {
 	struct auth_fsm_priv *afp = fi->priv;
 	struct vlr_subscr *vsub = afp->vsub;
 
 	LOGPFSM(fi, "Authentication terminating with result %s\n",
-		vlr_auth_fsm_result_name(res));
+		vlr_auth_fsm_result_name(result));
 
 	/* Do one final state transition (mostly for logging purpose) */
-	if (res == VLR_AUTH_RES_PASSED)
+	if (!result)
 		osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_AUTHENTICATED, 0, 0);
 	else
 		osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_AUTH_FAILED, 0, 0);
 
 	/* return the result to the parent FSM */
-	osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, &res);
+	osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, &result);
 	vsub->auth_fsm = NULL;
 }
 
@@ -274,7 +272,7 @@
 		LOGPFSML(fi, LOGL_ERROR, "A previous check ensured that an"
 			 " auth tuple was available, but now there is in fact"
 			 " none.\n");
-		auth_fsm_term(fi, VLR_AUTH_RES_PROC_ERR);
+		auth_fsm_term(fi, GSM48_REJECT_NETWORK_FAILURE);
 		return -1;
 	}
 
@@ -350,7 +348,7 @@
 			goto pass;
 		}
 		/* result = procedure error */
-		auth_fsm_term(fi, VLR_AUTH_RES_PROC_ERR);
+		auth_fsm_term(fi, GSM48_REJECT_NETWORK_FAILURE);
 		return;
 	}
 
@@ -362,8 +360,8 @@
 	case VLR_AUTH_E_HLR_SAI_NACK:
 		auth_fsm_term(fi,
 			      gsup->cause == GMM_CAUSE_IMSI_UNKNOWN?
-				      VLR_AUTH_RES_UNKNOWN_SUBSCR
-				      : VLR_AUTH_RES_PROC_ERR);
+				      GSM48_REJECT_IMSI_UNKNOWN_IN_HLR
+				      : GSM48_REJECT_NETWORK_FAILURE);
 		break;
 	}
 
@@ -396,10 +394,10 @@
 						VLR_SUB_AS_WAIT_ID_IMSI,
 						vlr_timer(vlr, 3270), 3270);
 			} else {
-				auth_fsm_term(fi, VLR_AUTH_RES_AUTH_FAILED);
+				auth_fsm_term(fi, GSM48_REJECT_ILLEGAL_MS);
 			}
 		} else {
-			auth_fsm_term(fi, VLR_AUTH_RES_PASSED);
+			auth_fsm_term(fi, 0);
 		}
 		break;
 	case VLR_AUTH_E_MS_AUTH_FAIL:
@@ -411,7 +409,7 @@
 					VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC,
 					GSM_29002_TIMER_M, 0);
 		} else
-			auth_fsm_term(fi, VLR_AUTH_RES_AUTH_FAILED);
+			auth_fsm_term(fi, GSM48_REJECT_ILLEGAL_MS);
 		break;
 	}
 }
@@ -431,7 +429,7 @@
 	     gsup->cause != GMM_CAUSE_IMSI_UNKNOWN) ||
 	    (event == VLR_AUTH_E_HLR_SAI_ABORT)) {
 		/* result = procedure error */
-		auth_fsm_term(fi, VLR_AUTH_RES_PROC_ERR);
+		auth_fsm_term(fi, GSM48_REJECT_NETWORK_FAILURE);
 	}
 	switch (event) {
 	case VLR_AUTH_E_HLR_SAI_ACK:
@@ -441,8 +439,8 @@
 	case VLR_AUTH_E_HLR_SAI_NACK:
 		auth_fsm_term(fi,
 			      gsup->cause == GMM_CAUSE_IMSI_UNKNOWN?
-				      VLR_AUTH_RES_UNKNOWN_SUBSCR
-				      : VLR_AUTH_RES_PROC_ERR);
+				      GSM48_REJECT_IMSI_UNKNOWN_IN_HLR
+				      : GSM48_REJECT_NETWORK_FAILURE);
 		break;
 	}
 
@@ -476,16 +474,16 @@
 						vlr_timer(vlr, 3270), 3270);
 			} else {
 				/* Result = Aborted */
-				auth_fsm_term(fi, VLR_AUTH_RES_ABORTED);
+				auth_fsm_term(fi, GSM48_REJECT_SYNCH_FAILURE);
 			}
 		} else {
 			/* Result = Pass */
-			auth_fsm_term(fi, VLR_AUTH_RES_PASSED);
+			auth_fsm_term(fi, 0);
 		}
 		break;
 	case VLR_AUTH_E_MS_AUTH_FAIL:
 		/* Second failure: Result = Fail */
-		auth_fsm_term(fi, VLR_AUTH_RES_AUTH_FAILED);
+		auth_fsm_term(fi, GSM48_REJECT_SYNCH_FAILURE);
 		break;
 	}
 }
diff --git a/src/libvlr/vlr_auth_fsm.h b/src/libvlr/vlr_auth_fsm.h
index 226435f..618462f 100644
--- a/src/libvlr/vlr_auth_fsm.h
+++ b/src/libvlr/vlr_auth_fsm.h
@@ -11,21 +11,6 @@
 	const uint8_t *auts;
 };
 
-/* Result communicated back to parent FMS */
-enum vlr_auth_fsm_result {
-	VLR_AUTH_RES_ABORTED,
-	VLR_AUTH_RES_UNKNOWN_SUBSCR,
-	VLR_AUTH_RES_PROC_ERR,
-	VLR_AUTH_RES_AUTH_FAILED,
-	VLR_AUTH_RES_PASSED,
-};
-
-extern const struct value_string vlr_auth_fsm_result_names[];
-static inline const char *vlr_auth_fsm_result_name(enum vlr_auth_fsm_result val)
-{
-	return get_value_string(vlr_auth_fsm_result_names, val);
-}
-
 enum vlr_fsm_auth_event {
 	VLR_AUTH_E_START,
 	/* TS 23.018 OAS_VLR1(2): SendAuthInfo ACK from HLR */
diff --git a/src/libvlr/vlr_lu_fsm.c b/src/libvlr/vlr_lu_fsm.c
index 908e0e3..3073bd6 100644
--- a/src/libvlr/vlr_lu_fsm.c
+++ b/src/libvlr/vlr_lu_fsm.c
@@ -741,11 +741,10 @@
 	_lu_fsm_done(fi, VLR_FSM_RESULT_SUCCESS);
 }
 
-static void lu_fsm_failure(struct osmo_fsm_inst *fi, uint8_t rej_cause)
+static void lu_fsm_failure(struct osmo_fsm_inst *fi, enum gsm48_reject_value rej_cause)
 {
 	struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
-	if (rej_cause)
-		lfp->vlr->ops.tx_lu_rej(lfp->msc_conn_ref, rej_cause);
+	lfp->vlr->ops.tx_lu_rej(lfp->msc_conn_ref, rej_cause ? : GSM48_REJECT_NETWORK_FAILURE);
 	_lu_fsm_done(fi, VLR_FSM_RESULT_FAILURE);
 }
 
@@ -1097,44 +1096,19 @@
 			     void *data)
 {
 	struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
-	enum vlr_auth_fsm_result *res = data;
-	uint8_t rej_cause = 0;
+	enum gsm48_reject_value *res = data;
 
 	OSMO_ASSERT(event == VLR_ULA_E_AUTH_RES);
 
 	lfp->upd_hlr_vlr_fsm = NULL;
 
-	if (res) {
-		switch (*res) {
-		case VLR_AUTH_RES_PASSED:
-			/* Result == Pass */
-			vlr_loc_upd_post_auth(fi);
-			return;
-		case VLR_AUTH_RES_ABORTED:
-			/* go to Idle with no response */
-			rej_cause = 0;
-			break;
-		case VLR_AUTH_RES_UNKNOWN_SUBSCR:
-			/* FIXME: delete subscribe record */
-			rej_cause = GSM48_REJECT_IMSI_UNKNOWN_IN_HLR;
-			break;
-		case VLR_AUTH_RES_AUTH_FAILED:
-			/* cause = illegal subscriber */
-			rej_cause = GSM48_REJECT_ILLEGAL_MS;
-			break;
-		case VLR_AUTH_RES_PROC_ERR:
-			/* cause = system failure */
-			rej_cause = GSM48_REJECT_NETWORK_FAILURE;
-			break;
-		default:
-			LOGPFSML(fi, LOGL_ERROR, "event without effect: %s\n",
-				 osmo_fsm_event_name(fi->fsm, event));
-			break;
-		}
-	} else
-		rej_cause = GSM48_REJECT_NETWORK_FAILURE;
+	if (!res || *res) {
+		lu_fsm_failure(fi, res? *res : GSM48_REJECT_NETWORK_FAILURE);
+		return;
+	}
 
-	lu_fsm_failure(fi, rej_cause);
+	/* Result == Pass */
+	vlr_loc_upd_post_auth(fi);
 }
 
 static void lu_fsm_wait_ciph(struct osmo_fsm_inst *fi, uint32_t event,
@@ -1489,14 +1463,20 @@
 	return fi;
 }
 
-/* Gracefully terminate an FSM created by vlr_loc_update() in case of external
- * timeout (i.e. from MSC). */
-void vlr_loc_update_conn_timeout(struct osmo_fsm_inst *fi)
+void vlr_loc_update_cancel(struct osmo_fsm_inst *fi,
+			   enum osmo_fsm_term_cause fsm_cause,
+			   uint8_t gsm48_cause)
 {
-	if (!fi || fi->state == VLR_ULA_S_DONE)
-		return;
-	LOGPFSM(fi, "Connection timed out\n");
-	lu_fsm_failure(fi, GSM48_REJECT_CONGESTION);
+	struct lu_fsm_priv *lfp;
+
+	OSMO_ASSERT(fi);
+	OSMO_ASSERT(fi->fsm == &vlr_lu_fsm);
+
+	lfp = fi->priv;
+	lfp->rej_cause = gsm48_cause;
+
+	if (fi->state != VLR_ULA_S_DONE)
+		lu_fsm_failure(fi, gsm48_cause);
 }
 
 void vlr_lu_fsm_init(void)