aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--openbsc/configure.ac3
-rw-r--r--openbsc/include/openbsc/Makefile.am1
-rw-r--r--openbsc/include/openbsc/vlr.h409
-rw-r--r--openbsc/src/Makefile.am1
-rw-r--r--openbsc/src/libmsc/subscr_conn.c269
-rw-r--r--openbsc/src/libvlr/Makefile.am19
-rw-r--r--openbsc/src/libvlr/vlr.c1108
-rw-r--r--openbsc/src/libvlr/vlr_access_req_fsm.c776
-rw-r--r--openbsc/src/libvlr/vlr_access_req_fsm.h17
-rw-r--r--openbsc/src/libvlr/vlr_auth_fsm.c605
-rw-r--r--openbsc/src/libvlr/vlr_auth_fsm.h52
-rw-r--r--openbsc/src/libvlr/vlr_core.h21
-rw-r--r--openbsc/src/libvlr/vlr_lu_fsm.c1424
-rw-r--r--openbsc/src/libvlr/vlr_lu_fsm.h18
-rw-r--r--openbsc/src/osmo-nitb/Makefile.am1
-rw-r--r--openbsc/tests/vlr/Makefile.am22
-rw-r--r--openbsc/tests/vlr/vlr_test.c694
17 files changed, 5440 insertions, 0 deletions
diff --git a/openbsc/configure.ac b/openbsc/configure.ac
index c6ae15974..8381305e6 100644
--- a/openbsc/configure.ac
+++ b/openbsc/configure.ac
@@ -225,6 +225,7 @@ AC_OUTPUT(
src/libtrau/Makefile
src/libbsc/Makefile
src/libmsc/Makefile
+ src/libvlr/Makefile
src/libmgcp/Makefile
src/libcommon/Makefile
src/libfilter/Makefile
@@ -261,6 +262,8 @@ AC_OUTPUT(
tests/slhc/Makefile
tests/v42bis/Makefile
tests/nanobts_omlattr/Makefile
+ tests/vlr/Makefile
+ tests/subscr_conn/Makefile
doc/Makefile
doc/examples/Makefile
Makefile)
diff --git a/openbsc/include/openbsc/Makefile.am b/openbsc/include/openbsc/Makefile.am
index 7ddf323cd..b903782b3 100644
--- a/openbsc/include/openbsc/Makefile.am
+++ b/openbsc/include/openbsc/Makefile.am
@@ -82,6 +82,7 @@ noinst_HEADERS = \
trau_mux.h \
trau_upqueue.h \
ussd.h \
+ vlr.h \
vty.h \
v42bis.h \
v42bis_private.h \
diff --git a/openbsc/include/openbsc/vlr.h b/openbsc/include/openbsc/vlr.h
new file mode 100644
index 000000000..d7e8f19be
--- /dev/null
+++ b/openbsc/include/openbsc/vlr.h
@@ -0,0 +1,409 @@
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/gsm/gsm23003.h>
+#include <openbsc/gsm_data.h>
+// for GSM_NAME_LENGTH
+#include <openbsc/gsm_subscriber.h>
+
+/* from 3s to 10s */
+#define GSM_29002_TIMER_S 10
+/* from 15s to 30s */
+#define GSM_29002_TIMER_M 30
+/* from 1min to 10min */
+#define GSM_29002_TIMER_ML (10*60)
+/* from 28h to 38h */
+#define GSM_29002_TIMER_L (32*60*60)
+
+
+/* VLR subscriber authentication state */
+enum vlr_subscr_auth_state {
+ /* subscriber needs to be autenticated */
+ VLR_SUB_AS_NEEDS_AUTH,
+ /* waiting for AuthInfo from HLR/AUC */
+ VLR_SUB_AS_NEEDS_AUTH_WAIT_AI,
+ /* waiting for response from subscriber */
+ VLR_SUB_AS_WAIT_RESP,
+ /* successfully authenticated */
+ VLR_SUB_AS_AUTHENTICATED,
+ /* subscriber needs re-sync */
+ VLR_SUB_AS_NEEDS_RESYNC,
+ /* waiting for AuthInfo with ReSync */
+ VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC,
+ /* waiting for response from subscr, resync case */
+ VLR_SUB_AS_WAIT_RESP_RESYNC,
+ /* waiting for IMSI from subscriber */
+ VLR_SUB_AS_WAIT_ID_IMSI,
+ /* authentication has failed */
+ VLR_SUB_AS_AUTH_FAILED,
+};
+
+enum vlr_lu_event {
+ VLR_ULA_E_UPDATE_LA, /* Initial trigger (LU from MS) */
+ VLR_ULA_E_SEND_ID_ACK, /* Result of Send-ID from PVLR */
+ VLR_ULA_E_SEND_ID_NACK, /* Result of Send-ID from PVLR */
+ VLR_ULA_E_AUTH_RES, /* Result of auth procedure */
+ VLR_ULA_E_CIPH_RES, /* Result of Ciphering Mode Command */
+ VLR_ULA_E_ID_IMSI, /* IMSI recieved from MS */
+ VLR_ULA_E_ID_IMEI, /* IMEI received from MS */
+ VLR_ULA_E_ID_IMEISV, /* IMEISV received from MS */
+ VLR_ULA_E_HLR_LU_RES, /* HLR UpdateLocation result */
+ VLR_ULA_E_UPD_HLR_COMPL,/* UpdatE_HLR_VLR result */
+ VLR_ULA_E_LU_COMPL_SUCCESS,/* Location_Update_Completion_VLR result */
+ VLR_ULA_E_LU_COMPL_FAILURE,/* Location_Update_Completion_VLR result */
+ VLR_ULA_E_NEW_TMSI_ACK, /* TMSI Reallocation Complete */
+};
+
+enum vlr_ciph_result_cause {
+ VLR_CIPH_REJECT, /* ? */
+ VLR_CIPH_COMPL,
+};
+
+struct vlr_ciph_result {
+ enum vlr_ciph_result_cause cause;
+ const char *imeisv;
+};
+
+enum vlr_subscr_security_context {
+ VLR_SEC_CTX_NONE,
+ VLR_SEC_CTX_GSM,
+ VLR_SEC_CTX_UMTS,
+};
+
+enum vlr_lu_type {
+ VLR_LU_TYPE_PERIODIC,
+ VLR_LU_TYPE_IMSI_ATTACH,
+ VLR_LU_TYPE_REGULAR,
+};
+
+#define OSMO_LBUF_DECL(name, xlen) \
+ struct { \
+ uint8_t buf[xlen]; \
+ size_t len; \
+ } name
+
+struct sgsn_mm_ctx;
+struct vlr_instance;
+
+/* The VLR subscriber is the part of the GSM subscriber state in VLR (CS) or
+ * SGSN (PS), particularly while interacting with the HLR via GSUP */
+struct vlr_subscr {
+ struct llist_head list;
+ struct vlr_instance *vlr;
+
+ /* TODO either populate from HLR or drop this completely? */
+ long long unsigned int id;
+
+ /* Data from HLR */ /* 3GPP TS 23.008 */
+ /* Always use vlr_subscr_set_imsi() to write to imsi[] */
+ char imsi[GSM23003_IMSI_MAX_DIGITS+1]; /* 2.1.1.1 */
+ char msisdn[GSM_EXTENSION_LENGTH+1]; /* 2.1.2 */
+ char name[GSM_NAME_LENGTH+1]; /* proprietary */
+ OSMO_LBUF_DECL(hlr, 16); /* 2.4.7 */
+ uint32_t periodic_lu_timer; /* 2.4.24 */
+ uint32_t age_indicator; /* 2.17.1 */
+
+ /* Authentication Data */
+ struct gsm_auth_tuple auth_tuples[5]; /* 2.3.1-2.3.4 */
+ struct gsm_auth_tuple *last_tuple;
+ enum vlr_subscr_security_context sec_ctx;
+
+ /* Data local to VLR is below */
+ uint32_t tmsi; /* 2.1.4 */
+ /* Newly allocated TMSI that was not yet acked by MS */
+ uint32_t tmsi_new;
+
+ /* some redundancy in information below? */
+ struct osmo_cell_global_id cgi; /* 2.4.16 */
+ uint16_t lac; /* 2.4.2 */
+
+ char imeisv[GSM23003_IMEISV_NUM_DIGITS+1]; /* 2.2.3 */
+ char imei[GSM23003_IMEISV_NUM_DIGITS+1]; /* 2.1.9 */
+ bool imsi_detached_flag; /* 2.7.1 */
+ bool conf_by_radio_contact_ind; /* 2.7.4.1 */
+ bool sub_dataconf_by_hlr_ind; /* 2.7.4.2 */
+ bool loc_conf_in_hlr_ind; /* 2.7.4.3 */
+ bool dormant_ind; /* 2.7.8 */
+ bool cancel_loc_rx; /* 2.7.8A */
+ bool ms_not_reachable_flag; /* 2.10.2 (MNRF) */
+ bool la_allowed;
+
+ int use_count;
+ time_t expire_lu; /* FIXME: overlap with periodic_lu_timer/age_indicator */
+
+ struct osmo_fsm_inst *lu_fsm;
+ struct osmo_fsm_inst *auth_fsm;
+ struct osmo_fsm_inst *proc_arq_fsm;
+
+ bool lu_complete;
+
+ void *msc_conn_ref;
+
+ /* PS (SGSN) specific parts */
+ struct {
+ struct llist_head pdp_list;
+ uint8_t rac;
+ uint8_t sac;
+ struct gprs_mm_ctx *mmctx;
+ } ps;
+ /* CS (NITB/CSCN) specific parts */
+ struct {
+ /* pending requests */
+ bool is_paging;
+ struct llist_head requests;
+ } cs;
+};
+
+enum vlr_proc_arq_result;
+
+enum vlr_ciph {
+ VLR_CIPH_NONE, /*< A5/0, no encryption */
+ VLR_CIPH_A5_1, /*< A5/1, encryption */
+ VLR_CIPH_A5_2, /*< A5/2, deprecated export-grade encryption */
+ VLR_CIPH_A5_3, /*< A5/3, 'new secure' encryption */
+};
+
+struct vlr_ops {
+ /* encode + transmit an AUTH REQ towards the MS.
+ * \param[in] at auth tuple providing rand, key_seq and autn.
+ * \param[in] send_autn True to send AUTN, for r99 UMTS auth.
+ */
+ int (*tx_auth_req)(void *msc_conn_ref, struct gsm_auth_tuple *at,
+ bool send_autn);
+ /* encode + transmit an AUTH REJECT towards the MS */
+ int (*tx_auth_rej)(void *msc_conn_ref);
+
+ /* encode + transmit an IDENTITY REQUEST towards the MS */
+ int (*tx_id_req)(void *msc_conn_ref, uint8_t mi_type);
+
+ int (*tx_lu_acc)(void *msc_conn_ref, uint32_t send_tmsi);
+ int (*tx_lu_rej)(void *msc_conn_ref, uint8_t cause);
+ int (*tx_cm_serv_acc)(void *msc_conn_ref);
+ int (*tx_cm_serv_rej)(void *msc_conn_ref, enum vlr_proc_arq_result result);
+
+ int (*set_ciph_mode)(void *msc_conn_ref, enum vlr_ciph ciph_mode,
+ bool retrieve_imeisv);
+
+ /* notify MSC/SGSN that the subscriber data in VLR has been updated */
+ void (*subscr_update)(struct vlr_subscr *vsub);
+ /* notify MSC/SGSN that the given subscriber has been associated
+ * with this msc_conn_ref */
+ void (*subscr_assoc)(void *msc_conn_ref, struct vlr_subscr *vsub);
+};
+
+enum vlr_timer {
+ VLR_T_3250,
+ VLR_T_3260,
+ VLR_T_3270,
+ _NUM_VLR_TIMERS
+};
+
+/* An instance of the VLR codebase */
+struct vlr_instance {
+ struct llist_head subscribers;
+ struct llist_head operations;
+ struct gsup_client *gsup_client;
+ struct vlr_ops ops;
+ struct {
+ bool retrieve_imeisv;
+ bool assign_tmsi;
+ bool check_imei_rqd;
+ int auth_tuple_max_use_count;
+ bool auth_reuse_old_sets_on_error;
+ bool parq_retrieve_imsi;
+ bool is_ps;
+ uint32_t timer[_NUM_VLR_TIMERS];
+ } cfg;
+ /* A free-form pointer for use by the caller */
+ void *user_ctx;
+};
+
+extern const struct value_string vlr_ciph_names[];
+static inline const char *vlr_ciph_name(enum vlr_ciph val)
+{
+ return get_value_string(vlr_ciph_names, val);
+}
+
+/* Location Updating request */
+struct osmo_fsm_inst *
+vlr_loc_update(struct osmo_fsm_inst *parent,
+ uint32_t parent_event_success,
+ uint32_t parent_event_failure,
+ void *parent_event_data,
+ struct vlr_instance *vlr, void *msc_conn_ref,
+ enum vlr_lu_type type, uint32_t tmsi, const char *imsi,
+ const struct osmo_location_area_id *old_lai,
+ const struct osmo_location_area_id *new_lai,
+ bool authentication_required,
+ enum vlr_ciph ciphering_required,
+ bool is_r99, bool is_utran,
+ bool assign_tmsi);
+
+void vlr_loc_update_conn_timeout(struct osmo_fsm_inst *fi);
+
+/* tell the VLR that the subscriber connection is gone */
+int vlr_subscr_disconnected(struct vlr_subscr *vsub);
+
+int vlr_subscr_rx_id_resp(struct vlr_subscr *vsub, const uint8_t *mi, size_t mi_len);
+int vlr_subscr_rx_auth_resp(struct vlr_subscr *vsub, bool is_r99, bool is_utran,
+ const uint8_t *res, uint8_t res_len);
+int vlr_subscr_rx_auth_fail(struct vlr_subscr *vsub, const uint8_t *auts);
+int vlr_subscr_tx_auth_fail_rep(struct vlr_subscr *vsub);
+void vlr_subscr_rx_ciph_res(struct vlr_subscr *vsub, struct vlr_ciph_result *res);
+int vlr_subscr_rx_tmsi_reall_compl(struct vlr_subscr *vsub);
+int vlr_subscr_rx_imsi_detach(struct vlr_subscr *vsub);
+void vlr_subscr_conn_timeout(struct vlr_subscr *vsub);
+
+struct vlr_instance *vlr_alloc(void *ctx, const struct vlr_ops *ops);
+int vlr_start(const char *gsup_unit_name, struct vlr_instance *vlr,
+ const char *gsup_server_addr_str, uint16_t gsup_server_port);
+
+/* internal use only */
+
+struct osmo_fsm_inst *sub_pres_vlr_fsm_start(struct osmo_fsm_inst *parent,
+ struct vlr_subscr *vsub,
+ uint32_t term_event);
+struct osmo_fsm_inst *
+upd_hlr_vlr_proc_start(struct osmo_fsm_inst *parent,
+ struct vlr_subscr *vsub,
+ uint32_t parent_event);
+
+struct osmo_fsm_inst *
+lu_compl_vlr_proc_start(struct osmo_fsm_inst *parent,
+ struct vlr_subscr *vsub,
+ void *msc_conn_ref,
+ uint32_t parent_event_success,
+ uint32_t parent_event_failure);
+
+
+const char *vlr_subscr_name(struct vlr_subscr *vsub);
+const char *vlr_subscr_msisdn_or_name(struct vlr_subscr *vsub);
+
+#define vlr_subscr_find_by_imsi(vlr, imsi) \
+ _vlr_subscr_find_by_imsi(vlr, imsi, __BASE_FILE__, __LINE__)
+#define vlr_subscr_find_or_create_by_imsi(vlr, imsi, created) \
+ _vlr_subscr_find_or_create_by_imsi(vlr, imsi, created, \
+ __BASE_FILE__, __LINE__)
+
+#define vlr_subscr_find_by_tmsi(vlr, tmsi) \
+ _vlr_subscr_find_by_tmsi(vlr, tmsi, __BASE_FILE__, __LINE__)
+#define vlr_subscr_find_or_create_by_tmsi(vlr, tmsi, created) \
+ _vlr_subscr_find_or_create_by_tmsi(vlr, tmsi, created, \
+ __BASE_FILE__, __LINE__)
+
+#define vlr_subscr_find_by_msisdn(vlr, msisdn) \
+ _vlr_subscr_find_by_msisdn(vlr, msisdn, __BASE_FILE__, __LINE__)
+
+struct vlr_subscr *_vlr_subscr_find_by_imsi(struct vlr_instance *vlr,
+ const char *imsi,
+ const char *file, int line);
+struct vlr_subscr *_vlr_subscr_find_or_create_by_imsi(struct vlr_instance *vlr,
+ const char *imsi,
+ bool *created,
+ const char *file,
+ int line);
+
+struct vlr_subscr *_vlr_subscr_find_by_tmsi(struct vlr_instance *vlr,
+ uint32_t tmsi,
+ const char *file, int line);
+struct vlr_subscr *_vlr_subscr_find_or_create_by_tmsi(struct vlr_instance *vlr,
+ uint32_t tmsi,
+ bool *created,
+ const char *file,
+ int line);
+
+struct vlr_subscr *_vlr_subscr_find_by_msisdn(struct vlr_instance *vlr,
+ const char *msisdn,
+ const char *file, int line);
+
+#define vlr_subscr_get(sub) _vlr_subscr_get(sub, __BASE_FILE__, __LINE__)
+#define vlr_subscr_put(sub) _vlr_subscr_put(sub, __BASE_FILE__, __LINE__)
+struct vlr_subscr *_vlr_subscr_get(struct vlr_subscr *sub, const char *file, int line);
+struct vlr_subscr *_vlr_subscr_put(struct vlr_subscr *sub, const char *file, int line);
+
+struct vlr_subscr *vlr_subscr_alloc(struct vlr_instance *vlr);
+void vlr_subscr_free(struct vlr_subscr *vsub);
+int vlr_subscr_alloc_tmsi(struct vlr_subscr *vsub);
+
+void vlr_subscr_set_imsi(struct vlr_subscr *vsub, const char *imsi);
+void vlr_subscr_set_imei(struct vlr_subscr *vsub, const char *imei);
+void vlr_subscr_set_imeisv(struct vlr_subscr *vsub, const char *imeisv);
+void vlr_subscr_set_msisdn(struct vlr_subscr *vsub, const char *msisdn);
+
+bool vlr_subscr_matches_imsi(struct vlr_subscr *vsub, const char *imsi);
+bool vlr_subscr_matches_tmsi(struct vlr_subscr *vsub, uint32_t tmsi);
+bool vlr_subscr_matches_msisdn(struct vlr_subscr *vsub, const char *msisdn);
+bool vlr_subscr_matches_imei(struct vlr_subscr *vsub, const char *imei);
+
+uint32_t vlr_timer(struct vlr_instance *vlr, uint32_t timer);
+
+int vlr_subscr_changed(struct vlr_subscr *vsub);
+int vlr_subscr_purge(struct vlr_subscr *vsub);
+void vlr_subscr_cancel(struct vlr_subscr *vsub, enum gsm48_gmm_cause cause);
+
+
+/* Process Acccess Request FSM */
+
+enum vlr_proc_arq_result {
+ VLR_PR_ARQ_RES_NONE,
+ VLR_PR_ARQ_RES_SYSTEM_FAILURE,
+ VLR_PR_ARQ_RES_ILLEGAL_SUBSCR,
+ VLR_PR_ARQ_RES_UNIDENT_SUBSCR,
+ VLR_PR_ARQ_RES_ROAMING_NOTALLOWED,
+ VLR_PR_ARQ_RES_ILLEGAL_EQUIP,
+ VLR_PR_ARQ_RES_UNKNOWN_ERROR,
+ VLR_PR_ARQ_RES_TIMEOUT,
+ VLR_PR_ARQ_RES_PASSED,
+};
+
+extern const struct value_string vlr_proc_arq_result_names[];
+static inline const char *vlr_proc_arq_result_name(enum vlr_proc_arq_result res)
+{
+ return get_value_string(vlr_proc_arq_result_names, res);
+}
+
+enum proc_arq_vlr_event {
+ PR_ARQ_E_START,
+ PR_ARQ_E_ID_IMSI,
+ PR_ARQ_E_AUTH_RES,
+ PR_ARQ_E_CIPH_RES,
+ PR_ARQ_E_UPD_LOC_RES,
+ PR_ARQ_E_TRACE_RES,
+ PR_ARQ_E_IMEI_RES,
+ PR_ARQ_E_PRES_RES,
+ PR_ARQ_E_TMSI_ACK,
+};
+
+enum vlr_parq_type {
+ VLR_PR_ARQ_T_INVALID = 0, /* to guard against unset vars */
+ VLR_PR_ARQ_T_CM_SERV_REQ,
+ VLR_PR_ARQ_T_PAGING_RESP,
+ /* FIXME: differentiate between services of 24.008 10.5.3.3 */
+};
+
+/* Process Access Request (CM SERV REQ / PAGING RESP) */
+void
+vlr_proc_acc_req(struct osmo_fsm_inst *parent,
+ uint32_t parent_event_success,
+ uint32_t parent_event_failure,
+ void *parent_event_data,
+ struct vlr_instance *vlr, void *msc_conn_ref,
+ enum vlr_parq_type type, const uint8_t *mi_lv,
+ const struct osmo_location_area_id *lai,
+ bool authentication_required,
+ enum vlr_ciph ciphering_required,
+ bool is_r99, bool is_utran);
+
+void vlr_parq_conn_timeout(struct osmo_fsm_inst *fi);
+
+void vlr_parq_fsm_init(void);
+
+int vlr_set_ciph_mode(struct vlr_instance *vlr,
+ struct osmo_fsm_inst *fi,
+ void *msc_conn_ref,
+ enum vlr_ciph ciph_mode,
+ bool retrieve_imeisv);
diff --git a/openbsc/src/Makefile.am b/openbsc/src/Makefile.am
index cfad7dfea..c66f9e56b 100644
--- a/openbsc/src/Makefile.am
+++ b/openbsc/src/Makefile.am
@@ -22,6 +22,7 @@ AM_LDFLAGS = \
# Libraries
SUBDIRS = \
libcommon \
+ libvlr \
libmgcp \
libbsc \
libmsc \
diff --git a/openbsc/src/libmsc/subscr_conn.c b/openbsc/src/libmsc/subscr_conn.c
new file mode 100644
index 000000000..91ffe4069
--- /dev/null
+++ b/openbsc/src/libmsc/subscr_conn.c
@@ -0,0 +1,269 @@
+/* MSC subscriber connection implementation */
+
+/*
+ * (C) 2016 by sysmocom s.m.f.c. <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/fsm.h>
+
+#include <openbsc/osmo_msc.h>
+#include <openbsc/vlr.h>
+#include <openbsc/debug.h>
+#include <openbsc/transaction.h>
+
+static const struct value_string subscr_conn_fsm_event_names[] = {
+ OSMO_VALUE_STRING(SUBSCR_CONN_E_INVALID),
+ OSMO_VALUE_STRING(SUBSCR_CONN_E_ACCEPTED),
+ 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 }
+};
+
+const struct value_string subscr_conn_from_names[] = {
+ OSMO_VALUE_STRING(SUBSCR_CONN_FROM_INVALID),
+ OSMO_VALUE_STRING(SUBSCR_CONN_FROM_LU),
+ OSMO_VALUE_STRING(SUBSCR_CONN_FROM_CM_SERVICE_REQ),
+ OSMO_VALUE_STRING(SUBSCR_CONN_FROM_PAGING_RESP),
+ { 0, NULL }
+};
+
+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);
+}
+
+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;
+
+ if (data) {
+ from = *(enum subscr_conn_from*)data;
+ LOGPFSM(fi, "%s\n", subscr_conn_from_name(from));
+ }
+
+ /* 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);
+ break;
+
+ case SUBSCR_CONN_E_MO_CLOSE:
+ case SUBSCR_CONN_E_CN_CLOSE:
+ case SUBSCR_CONN_E_CLOSE_CONF:
+ break;
+
+ default:
+ LOGPFSM(fi, "Unexpected event: %d %s\n",
+ event, osmo_fsm_event_name(fi->fsm, event));
+ 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);
+ }
+
+ /* On failure, discard the conn */
+ if (fi->state != SUBSCR_CONN_S_ACCEPTED) {
+ /* 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");
+ }
+
+ 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;
+
+ if (conn->silent_call)
+ return;
+
+ if (conn->received_cm_service_request)
+ return;
+
+ /* is this needed? */
+ if (conn->subscr && !llist_empty(&conn->subscr->requests))
+ return;
+
+ if (trans_has_conn(conn))
+ return;
+
+ osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0);
+}
+
+static void subscr_conn_fsm_accepted(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ 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_release(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+ 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;
+ }
+
+ 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);
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state subscr_conn_fsm_states[] = {
+ [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),
+ .out_state_mask = S(SUBSCR_CONN_S_ACCEPTED) |
+ S(SUBSCR_CONN_S_RELEASED),
+ .action = subscr_conn_fsm_new,
+ },
+ [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) |
+ 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),
+ .action = subscr_conn_fsm_accepted,
+ },
+ [SUBSCR_CONN_S_RELEASED] = {
+ .name = OSMO_STRINGIFY(SUBSCR_CONN_S_RELEASED),
+ .onenter = subscr_conn_fsm_release,
+ },
+};
+
+static struct osmo_fsm subscr_conn_fsm = {
+ .name = "Subscr_Conn",
+ .states = subscr_conn_fsm_states,
+ .num_states = ARRAY_SIZE(subscr_conn_fsm_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DVLR,
+ .event_names = subscr_conn_fsm_event_names,
+};
+
+int msc_create_conn_fsm(struct gsm_subscriber_connection *conn, const char *id)
+{
+ struct osmo_fsm_inst *fi;
+ OSMO_ASSERT(conn);
+
+ if (conn->conn_fsm) {
+ LOGP(DMM, LOGL_ERROR,
+ "%s: Error: connection already in use\n", id);
+ return -EINVAL;
+ }
+
+ fi = osmo_fsm_inst_alloc(&subscr_conn_fsm, conn, conn, LOGL_DEBUG, id);
+
+ if (!fi) {
+ LOGP(DMM, LOGL_ERROR,
+ "%s: Failed to allocate subscr conn master FSM\n", id);
+ return -ENOMEM;
+ }
+ conn->conn_fsm = fi;
+ return 0;
+}
+
+bool msc_subscr_conn_is_accepted(struct gsm_subscriber_connection *conn)
+{
+ if (!conn)
+ return false;
+ if (!conn->subscr)
+ return false;
+ if (!conn->conn_fsm)
+ return false;
+ if (conn->conn_fsm->state != SUBSCR_CONN_S_ACCEPTED)
+ return false;
+ return true;
+}
+
+void msc_subscr_conn_init(void)
+{
+ osmo_fsm_register(&subscr_conn_fsm);
+}
diff --git a/openbsc/src/libvlr/Makefile.am b/openbsc/src/libvlr/Makefile.am
new file mode 100644
index 000000000..17ad4111f
--- /dev/null
+++ b/openbsc/src/libvlr/Makefile.am
@@ -0,0 +1,19 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) \
+ $(COVERAGE_CFLAGS) $(LIBCRYPTO_CFLAGS)
+
+noinst_HEADERS = \
+ vlr_access_req_fsm.h \
+ vlr_auth_fsm.h \
+ vlr_core.h \
+ vlr_lu_fsm.h \
+ $(NULL)
+
+noinst_LIBRARIES = libvlr.a
+
+libvlr_a_SOURCES = \
+ vlr.c \
+ vlr_access_req_fsm.c \
+ vlr_auth_fsm.c \
+ vlr_lu_fsm.c \
+ $(NULL)
diff --git a/openbsc/src/libvlr/vlr.c b/openbsc/src/libvlr/vlr.c
new file mode 100644
index 000000000..0e0d31c26
--- /dev/null
+++ b/openbsc/src/libvlr/vlr.c
@@ -0,0 +1,1108 @@
+/* Osmocom Visitor Location Register (VLR) code base */
+
+/* (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/gsm/apn.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsup_client.h>
+#include <openbsc/vlr.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_utils.h>
+#include <openbsc/debug.h>
+
+#include <openssl/rand.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <limits.h>
+
+#include "vlr_core.h"
+#include "vlr_auth_fsm.h"
+#include "vlr_lu_fsm.h"
+#include "vlr_access_req_fsm.h"
+
+#define SGSN_SUBSCR_MAX_RETRIES 3
+#define SGSN_SUBSCR_RETRY_INTERVAL 10
+
+/***********************************************************************
+ * Convenience functions
+ ***********************************************************************/
+
+const struct value_string vlr_ciph_names[] = {
+ OSMO_VALUE_STRING(VLR_CIPH_NONE),
+ OSMO_VALUE_STRING(VLR_CIPH_A5_1),
+ OSMO_VALUE_STRING(VLR_CIPH_A5_2),
+ OSMO_VALUE_STRING(VLR_CIPH_A5_3),
+ { 0, NULL }
+};
+
+uint32_t vlr_timer(struct vlr_instance *vlr, uint32_t timer)
+{
+ uint32_t tidx = 0xffffffff;
+
+ switch (timer) {
+ case 3270:
+ tidx = VLR_T_3270;
+ break;
+ case 3260:
+ tidx = VLR_T_3260;
+ break;
+ case 3250:
+ tidx = VLR_T_3250;
+ break;
+ }
+
+ OSMO_ASSERT(tidx < sizeof(vlr->cfg.timer));
+ return vlr->cfg.timer[tidx];
+}
+
+struct vlr_subscr *_vlr_subscr_find_by_imsi(struct vlr_instance *vlr,
+ const char *imsi,
+ const char *file, int line)
+{
+ struct vlr_subscr *vsub;
+
+ if (!imsi || !*imsi)
+ return NULL;
+
+ llist_for_each_entry(vsub, &vlr->subscribers, list) {
+ if (vlr_subscr_matches_imsi(vsub, imsi))
+ return _vlr_subscr_get(vsub, file, line);
+ }
+ return NULL;
+}
+
+struct vlr_subscr *_vlr_subscr_find_by_tmsi(struct vlr_instance *vlr,
+ uint32_t tmsi,
+ const char *file, int line)
+{
+ struct vlr_subscr *vsub;
+
+ if (tmsi == GSM_RESERVED_TMSI)
+ return NULL;
+
+ llist_for_each_entry(vsub, &vlr->subscribers, list) {
+ if (vlr_subscr_matches_tmsi(vsub, tmsi))
+ return _vlr_subscr_get(vsub, file, line);
+ }
+ return NULL;
+}
+
+struct vlr_subscr *_vlr_subscr_find_by_msisdn(struct vlr_instance *vlr,
+ const char *msisdn,
+ const char *file, int line)
+{
+ struct vlr_subscr *vsub;
+
+ if (!msisdn || !*msisdn)
+ return NULL;
+
+ llist_for_each_entry(vsub, &vlr->subscribers, list) {
+ if (vlr_subscr_matches_msisdn(vsub, msisdn))
+ return _vlr_subscr_get(vsub, file, line);
+ }
+ return NULL;
+}
+
+/* Transmit GSUP message to HLR */
+static int vlr_tx_gsup_message(struct vlr_instance *vlr,
+ struct osmo_gsup_message *gsup_msg)
+{
+ struct msgb *msg = gsup_client_msgb_alloc();
+
+ osmo_gsup_encode(msg, gsup_msg);
+
+ if (!vlr->gsup_client) {
+ LOGP(DVLR, LOGL_NOTICE, "GSUP link is down, cannot "
+ "send GSUP: %s\n", msgb_hexdump(msg));
+ msgb_free(msg);
+ return -ENOTSUP;
+ }
+
+ LOGP(DVLR, LOGL_DEBUG, "GSUP tx: %s\n",
+ osmo_hexdump_nospc(msg->data, msg->len));
+
+ return gsup_client_send(vlr->gsup_client, msg);
+}
+
+/* Transmit GSUP message for subscriber to HLR, using IMSI from subscriber */
+static int vlr_subscr_tx_gsup_message(struct vlr_subscr *vsub,
+ struct osmo_gsup_message *gsup_msg)
+{
+ struct vlr_instance *vlr = vsub->vlr;
+
+ if (strlen(gsup_msg->imsi) == 0)
+ osmo_strlcpy(gsup_msg->imsi, vsub->imsi, sizeof(gsup_msg->imsi));
+
+ return vlr_tx_gsup_message(vlr, gsup_msg);
+}
+
+/* Transmit GSUP error in response to original message */
+static int vlr_tx_gsup_error_reply(struct vlr_instance *vlr,
+ struct osmo_gsup_message *gsup_orig,
+ enum gsm48_gmm_cause cause)
+{
+ struct osmo_gsup_message gsup_reply = {0};
+
+ osmo_strlcpy(gsup_reply.imsi, gsup_orig->imsi, sizeof(gsup_reply.imsi));
+ gsup_reply.cause = cause;
+ gsup_reply.message_type =
+ OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type);
+
+ return vlr_tx_gsup_message(vlr, &gsup_reply);
+}
+
+struct vlr_subscr *_vlr_subscr_get(struct vlr_subscr *sub, const char *file, int line)
+{
+ if (!sub)
+ return NULL;
+ OSMO_ASSERT(sub->use_count < INT_MAX);
+ sub->use_count++;
+ LOGPSRC(DREF, LOGL_DEBUG, file, line,
+ "VLR subscr %s usage increases to: %d\n",
+ vlr_subscr_name(sub), sub->use_count);
+ return sub;
+}
+
+struct vlr_subscr *_vlr_subscr_put(struct vlr_subscr *sub, const char *file, int line)
+{
+ if (!sub)
+ return NULL;
+ sub->use_count--;
+ LOGPSRC(DREF, sub->use_count >= 0? LOGL_DEBUG : LOGL_ERROR,
+ file, line,
+ "VLR subscr %s usage decreases to: %d\n",
+ vlr_subscr_name(sub), sub->use_count);
+ if (sub->use_count <= 0)
+ vlr_subscr_free(sub);
+ return NULL;
+}
+
+/* Allocate a new subscriber and insert it into list */
+static struct vlr_subscr *_vlr_subscr_alloc(struct vlr_instance *vlr)
+{
+ struct vlr_subscr *vsub;
+ int i;
+
+ vsub = talloc_zero(vlr, struct vlr_subscr);
+ vsub->vlr = vlr;
+ vsub->tmsi = GSM_RESERVED_TMSI;
+ vsub->tmsi_new = GSM_RESERVED_TMSI;
+
+ for (i = 0; i < ARRAY_SIZE(vsub->auth_tuples); i++)
+ vsub->auth_tuples[i].key_seq = GSM_KEY_SEQ_INVAL;
+
+ INIT_LLIST_HEAD(&vsub->cs.requests);
+ INIT_LLIST_HEAD(&vsub->ps.pdp_list);
+
+ llist_add_tail(&vsub->list, &vlr->subscribers);
+ return vsub;
+}
+
+struct vlr_subscr *vlr_subscr_alloc(struct vlr_instance *vlr)
+{
+ return vlr_subscr_get(_vlr_subscr_alloc(vlr));
+}
+
+/* Send a GSUP Purge MS request.
+ * TODO: this should be sent to the *previous* VLR when this VLR is "taking"
+ * this subscriber, not to the HLR? */
+int vlr_subscr_purge(struct vlr_subscr *vsub)
+{
+ struct osmo_gsup_message gsup_msg = {0};
+
+ gsup_msg.message_type = OSMO_GSUP_MSGT_PURGE_MS_REQUEST;
+
+ /* provide HLR number in case we know it */
+ gsup_msg.hlr_enc_len = vsub->hlr.len;
+ gsup_msg.hlr_enc = vsub->hlr.buf;
+
+ return vlr_subscr_tx_gsup_message(vsub, &gsup_msg);
+}
+
+void vlr_subscr_cancel(struct vlr_subscr *vsub, enum gsm48_gmm_cause 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);
+ }
+
+ if (vsub->proc_arq_fsm)
+ osmo_fsm_inst_term(vsub->proc_arq_fsm, OSMO_FSM_TERM_ERROR, 0);
+}
+
+/* Call vlr_subscr_cancel(), then completely drop the entry from the VLR */
+void vlr_subscr_free(struct vlr_subscr *vsub)
+{
+ llist_del(&vsub->list);
+ DEBUGP(DREF, "freeing VLR subscr %s\n", vlr_subscr_name(vsub));
+ talloc_free(vsub);
+}
+
+/* Generate a new TMSI and store in vsub->tmsi_new.
+ * Search all known subscribers to ensure that the TMSI is unique. */
+int vlr_subscr_alloc_tmsi(struct vlr_subscr *vsub)
+{
+ struct vlr_instance *vlr = vsub->vlr;
+ uint32_t tmsi;
+ int tried;
+
+ for (tried = 0; tried < 100; tried++) {
+ if (RAND_bytes((uint8_t *) &tmsi, sizeof(tmsi)) != 1) {
+ LOGP(DVLR, LOGL_ERROR, "RAND_bytes failed\n");
+ return -1;
+ }
+ /* throw the dice again, if the TSMI doesn't fit */
+ if (tmsi == GSM_RESERVED_TMSI)
+ continue;
+
+ /* Section 2.4 of 23.003: MSC has two MSB 00/01/10, SGSN 11 */
+ if (vlr->cfg.is_ps) {
+ /* SGSN */
+ tmsi |= 0xC000000;
+ } else {
+ /* MSC */
+ if ((tmsi & 0xC0000000) == 0xC0000000)
+ tmsi &= ~0xC0000000;
+ }
+
+ /* If this TMSI is already in use, try another one. */
+ if (vlr_subscr_find_by_tmsi(vlr, tmsi))
+ continue;
+
+ vsub->tmsi_new = tmsi;
+ return 0;
+ }
+
+ LOGP(DVLR, LOGL_ERROR, "subscr %s: unable to generate valid TMSI"
+ " after %d tries\n", vlr_subscr_name(vsub), tried);
+ return -1;
+}
+
+/* Find subscriber by IMSI, or create new subscriber if not found.
+ * \param[in] vlr VLR instace.
+ * \param[in] imsi IMSI string.
+ * \param[out] created if non-NULL, returns whether a new entry was created. */
+struct vlr_subscr *_vlr_subscr_find_or_create_by_imsi(struct vlr_instance *vlr,
+ const char *imsi,
+ bool *created,
+ const char *file,
+ int line)
+{
+ struct vlr_subscr *vsub;
+ vsub = _vlr_subscr_find_by_imsi(vlr, imsi, file, line);
+ if (vsub) {
+ if (created)
+ *created = false;
+ return vsub;
+ }
+
+ vsub = _vlr_subscr_get(_vlr_subscr_alloc(vlr), file, line);
+ if (!vsub)
+ return NULL;
+ vlr_subscr_set_imsi(vsub, imsi);
+ LOGP(DVLR, LOGL_INFO, "New subscr, IMSI: %s\n", vsub->imsi);
+ if (created)
+ *created = true;
+ return vsub;
+}
+
+/* Find subscriber by TMSI, or create new subscriber if not found.
+ * \param[in] vlr VLR instace.
+ * \param[in] tmsi TMSI.
+ * \param[out] created if non-NULL, returns whether a new entry was created. */
+struct vlr_subscr *_vlr_subscr_find_or_create_by_tmsi(struct vlr_instance *vlr,
+ uint32_t tmsi,
+ bool *created,
+ const char *file,
+ int line)
+{
+ struct vlr_subscr *vsub;
+ vsub = _vlr_subscr_find_by_tmsi(vlr, tmsi, file, line);
+ if (vsub) {
+ if (created)
+ *created = false;
+ return vsub;
+ }
+
+ vsub = _vlr_subscr_get(_vlr_subscr_alloc(vlr), file, line);
+ if (!vsub)
+ return NULL;
+ vsub->tmsi = tmsi;
+ LOGP(DVLR, LOGL_INFO, "New subscr, TMSI: 0x%08x\n", vsub->tmsi);
+ if (created)
+ *created = true;
+ return vsub;
+}
+
+void vlr_subscr_set_imsi(struct vlr_subscr *vsub, const char *imsi)
+{
+ if (!vsub)
+ return;
+ osmo_strlcpy(vsub->imsi, imsi, sizeof(vsub->imsi));
+ vsub->id = atoll(vsub->imsi);
+ DEBUGP(DVLR, "set IMSI on subscriber; IMSI=%s id=%llu\n",
+ vsub->imsi, vsub->id);
+}
+
+void vlr_subscr_set_imei(struct vlr_subscr *vsub, const char *imei)
+{
+ if (!vsub)
+ return;
+ osmo_strlcpy(vsub->imei, imei, sizeof(vsub->imei));
+ DEBUGP(DVLR, "set IMEI on subscriber; IMSI=%s IMEI=%s\n",
+ vsub->imsi, vsub->imei);
+}
+
+void vlr_subscr_set_imeisv(struct vlr_subscr *vsub, const char *imeisv)
+{
+ if (!vsub)
+ return;
+ osmo_strlcpy(vsub->imeisv, imeisv, sizeof(vsub->imeisv));
+ DEBUGP(DVLR, "set IMEISV on subscriber; IMSI=%s IMEISV=%s\n",
+ vsub->imsi, vsub->imeisv);
+}
+
+/* Safely copy the given MSISDN string to vsub->msisdn */
+void vlr_subscr_set_msisdn(struct vlr_subscr *vsub, const char *msisdn)
+{
+ if (!vsub)
+ return;
+ osmo_strlcpy(vsub->msisdn, msisdn, sizeof(vsub->msisdn));
+ DEBUGP(DVLR, "set MSISDN on subscriber; IMSI=%s MSISDN=%s\n",
+ vsub->imsi, vsub->msisdn);
+}
+
+bool vlr_subscr_matches_imsi(struct vlr_subscr *vsub, const char *imsi)
+{
+ return vsub && imsi && vsub->imsi[0] && !strcmp(vsub->imsi, imsi);
+}
+
+bool vlr_subscr_matches_tmsi(struct vlr_subscr *vsub, uint32_t tmsi)
+{
+ return vsub && tmsi != GSM_RESERVED_TMSI
+ && (vsub->tmsi == tmsi || vsub->tmsi_new == tmsi);
+}
+
+bool vlr_subscr_matches_msisdn(struct vlr_subscr *vsub, const char *msisdn)
+{
+ return vsub && msisdn && vsub->msisdn[0]
+ && !strcmp(vsub->msisdn, msisdn);
+}
+
+bool vlr_subscr_matches_imei(struct vlr_subscr *vsub, const char *imei)
+{
+ return vsub && imei && vsub->imei[0]
+ && !strcmp(vsub->imei, imei);
+}
+
+/* Send updated subscriber information to HLR */
+int vlr_subscr_changed(struct vlr_subscr *vsub)
+{
+ /* FIXME */
+ LOGP(DVLR, LOGL_ERROR, "Not implemented: %s\n", __func__);
+ return 0;
+}
+
+/***********************************************************************
+ * PDP context data
+ ***********************************************************************/
+
+struct sgsn_subscriber_pdp_data *
+vlr_subscr_pdp_data_alloc(struct vlr_subscr *vsub)
+{
+ struct sgsn_subscriber_pdp_data* pdata;
+
+ pdata = talloc_zero(vsub, struct sgsn_subscriber_pdp_data);
+
+ llist_add_tail(&pdata->list, &vsub->ps.pdp_list);
+
+ return pdata;
+}
+
+static int vlr_subscr_pdp_data_clear(struct vlr_subscr *vsub)
+{
+ struct sgsn_subscriber_pdp_data *pdp, *pdp2;
+ int count = 0;
+
+ llist_for_each_entry_safe(pdp, pdp2, &vsub->ps.pdp_list, list) {
+ llist_del(&pdp->list);
+ talloc_free(pdp);
+ count += 1;
+ }
+
+ return count;
+}
+
+static struct sgsn_subscriber_pdp_data *
+vlr_subscr_pdp_data_get_by_id(struct vlr_subscr *vsub, unsigned context_id)
+{
+ struct sgsn_subscriber_pdp_data *pdp;
+
+ llist_for_each_entry(pdp, &vsub->ps.pdp_list, list) {
+ if (pdp->context_id == context_id)
+ return pdp;
+ }
+
+ return NULL;
+}
+
+/***********************************************************************
+ * Actual Implementation
+ ***********************************************************************/
+
+static int vlr_rx_gsup_unknown_imsi(struct vlr_instance *vlr,
+ struct osmo_gsup_message *gsup_msg)
+{
+ if (OSMO_GSUP_IS_MSGT_REQUEST(gsup_msg->message_type)) {
+ vlr_tx_gsup_error_reply(vlr, gsup_msg,
+ GMM_CAUSE_IMSI_UNKNOWN);
+ LOGP(DVLR, LOGL_NOTICE,
+ "Unknown IMSI %s, discarding GSUP request "
+ "of type 0x%02x\n",
+ gsup_msg->imsi, gsup_msg->message_type);
+ } else if (OSMO_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) {
+ LOGP(DVLR, LOGL_NOTICE,
+ "Unknown IMSI %s, discarding GSUP error "
+ "of type 0x%02x, cause '%s' (%d)\n",
+ gsup_msg->imsi, gsup_msg->message_type,
+ get_value_string(gsm48_gmm_cause_names, gsup_msg->cause),
+ gsup_msg->cause);
+ } else {
+ LOGP(DVLR, LOGL_NOTICE,
+ "Unknown IMSI %s, discarding GSUP response "
+ "of type 0x%02x\n",
+ gsup_msg->imsi, gsup_msg->message_type);
+ }
+
+ return -GMM_CAUSE_IMSI_UNKNOWN;
+}
+
+static int vlr_rx_gsup_purge_no_subscr(struct vlr_instance *vlr,
+ struct osmo_gsup_message *gsup_msg)
+{
+ if (OSMO_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) {
+ LOGGSUPP(LOGL_NOTICE, gsup_msg,
+ "Purge MS has failed with cause '%s' (%d)\n",
+ get_value_string(gsm48_gmm_cause_names, gsup_msg->cause),
+ gsup_msg->cause);
+ return -gsup_msg->cause;
+ }
+ LOGGSUPP(LOGL_INFO, gsup_msg, "Completing purge MS\n");
+ return 0;
+}
+
+/* VLR internal call to request UpdateLocation from HLR */
+int vlr_subscr_req_lu(struct vlr_subscr *vsub, bool is_ps)
+{
+ struct osmo_gsup_message gsup_msg = {0};
+ int rc;
+
+ gsup_msg.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST;
+ rc = vlr_subscr_tx_gsup_message(vsub, &gsup_msg);
+
+ return rc;
+}
+
+/* VLR internal call to request tuples from HLR */
+int vlr_subscr_req_sai(struct vlr_subscr *vsub,
+ const uint8_t *auts, const uint8_t *auts_rand)
+{
+ struct osmo_gsup_message gsup_msg = {0};
+
+ gsup_msg.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST;
+ gsup_msg.auts = auts;
+ gsup_msg.rand = auts_rand;
+
+ return vlr_subscr_tx_gsup_message(vsub, &gsup_msg);
+}
+
+/* Tell HLR that authentication failure occurred */
+int vlr_subscr_tx_auth_fail_rep(struct vlr_subscr *vsub)
+{
+ struct osmo_gsup_message gsup_msg = {0};
+
+ gsup_msg.message_type = OSMO_GSUP_MSGT_AUTH_FAIL_REPORT;
+ osmo_strlcpy(gsup_msg.imsi, vsub->imsi, sizeof(gsup_msg.imsi));
+ return vlr_tx_gsup_message(vsub->vlr, &gsup_msg);
+}
+
+/* Update the subscriber with GSUP-received auth tuples */
+void vlr_subscr_update_tuples(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ unsigned int i;
+ unsigned int got_tuples;
+
+ if (gsup->num_auth_vectors) {
+ memset(&vsub->auth_tuples, 0, sizeof(vsub->auth_tuples));
+ for (i = 0; i < ARRAY_SIZE(vsub->auth_tuples); i++)
+ vsub->auth_tuples[i].key_seq = GSM_KEY_SEQ_INVAL;
+ }
+
+ got_tuples = 0;
+ for (i = 0; i < gsup->num_auth_vectors; i++) {
+ size_t key_seq = i;
+
+ if (key_seq >= ARRAY_SIZE(vsub->auth_tuples)) {
+ LOGVSUBP(LOGL_NOTICE, vsub,
+ "Skipping auth tuple wih invalid cksn %zu\n",
+ key_seq);
+ continue;
+ }
+ vsub->auth_tuples[i].vec = gsup->auth_vectors[i];
+ vsub->auth_tuples[i].key_seq = key_seq;
+ got_tuples ++;
+ }
+
+ LOGVSUBP(LOGL_DEBUG, vsub, "Received %u auth tuples\n", got_tuples);
+
+ if (!got_tuples) {
+ /* FIXME what now? */
+ // vlr_subscr_cancel(vsub, GMM_CAUSE_GSM_AUTH_UNACCEPT); ?
+ }
+
+ /* New tuples means last_tuple becomes invalid */
+ vsub->last_tuple = NULL;
+}
+
+/* Handle SendAuthInfo Result/Error from HLR */
+static int vlr_subscr_handle_sai_res(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ struct osmo_fsm_inst *auth_fi = vsub->auth_fsm;
+ void *data = (void *) gsup;
+
+ switch (gsup->message_type) {
+ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT:
+ osmo_fsm_inst_dispatch(auth_fi, VLR_AUTH_E_HLR_SAI_ACK, data);
+ break;
+ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR:
+ osmo_fsm_inst_dispatch(auth_fi, VLR_AUTH_E_HLR_SAI_NACK, data);
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+static int decode_bcd_number_safe(char *output, int output_len,
+ const uint8_t *bcd_lv, int input_len,
+ int h_len)
+{
+ uint8_t len;
+ OSMO_ASSERT(output_len >= 1);
+ *output = '\0';
+ if (input_len < 1)
+ return -EIO;
+ len = bcd_lv[0];
+ if (input_len < len)
+ return -EIO;
+ return gsm48_decode_bcd_number(output, output_len, bcd_lv, h_len);
+}
+
+static void vlr_subscr_gsup_insert_data(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup_msg)
+{
+ unsigned idx;
+ int rc;
+
+ if (gsup_msg->msisdn_enc) {
+ decode_bcd_number_safe(vsub->msisdn, sizeof(vsub->msisdn),
+ gsup_msg->msisdn_enc,
+ gsup_msg->msisdn_enc_len, 0);
+ LOGP(DVLR, LOGL_DEBUG, "IMSI:%s has MSISDN:%s\n",
+ vsub->imsi, vsub->msisdn);
+ }
+
+ if (gsup_msg->hlr_enc) {
+ if (gsup_msg->hlr_enc_len > sizeof(vsub->hlr.buf)) {
+ LOGP(DVLR, LOGL_ERROR, "HLR-Number too long (%zu)\n",
+ gsup_msg->hlr_enc_len);
+ vsub->hlr.len = 0;
+ } else {
+ memcpy(vsub->hlr.buf, gsup_msg->hlr_enc,
+ gsup_msg->hlr_enc_len);
+ vsub->hlr.len = gsup_msg->hlr_enc_len;
+ }
+ }
+
+ if (gsup_msg->pdp_info_compl) {
+ rc = vlr_subscr_pdp_data_clear(vsub);
+ if (rc > 0)
+ LOGP(DVLR, LOGL_INFO, "Cleared existing PDP info\n");
+ }
+
+ for (idx = 0; idx < gsup_msg->num_pdp_infos; idx++) {
+ const struct osmo_gsup_pdp_info *pdp_info = &gsup_msg->pdp_infos[idx];
+ size_t ctx_id = pdp_info->context_id;
+ struct sgsn_subscriber_pdp_data *pdp_data;
+
+ if (pdp_info->apn_enc_len >= sizeof(pdp_data->apn_str)-1) {
+ LOGVSUBP(LOGL_ERROR, vsub,
+ "APN too long, context id = %zu, APN = %s\n",
+ ctx_id, osmo_hexdump(pdp_info->apn_enc,
+ pdp_info->apn_enc_len));
+ continue;
+ }
+
+ if (pdp_info->qos_enc_len > sizeof(pdp_data->qos_subscribed)) {
+ LOGVSUBP(LOGL_ERROR, vsub,
+ "QoS info too long (%zu)\n",
+ pdp_info->qos_enc_len);
+ continue;
+ }
+
+ LOGVSUBP(LOGL_INFO, vsub,
+ "Will set PDP info, context id = %zu, APN = %s\n",
+ ctx_id, osmo_hexdump(pdp_info->apn_enc, pdp_info->apn_enc_len));
+
+ /* Set PDP info [ctx_id] */
+ pdp_data = vlr_subscr_pdp_data_get_by_id(vsub, ctx_id);
+ if (!pdp_data) {
+ pdp_data = vlr_subscr_pdp_data_alloc(vsub);
+ pdp_data->context_id = ctx_id;
+ }
+
+ OSMO_ASSERT(pdp_data != NULL);
+ pdp_data->pdp_type = pdp_info->pdp_type;
+ osmo_apn_to_str(pdp_data->apn_str,
+ pdp_info->apn_enc, pdp_info->apn_enc_len);
+ memcpy(pdp_data->qos_subscribed, pdp_info->qos_enc, pdp_info->qos_enc_len);
+ pdp_data->qos_subscribed_len = pdp_info->qos_enc_len;
+ }
+}
+
+
+/* Handle InsertSubscrData Result from HLR */
+static int vlr_subscr_handle_isd_req(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ struct osmo_gsup_message gsup_reply = {0};
+
+ vlr_subscr_gsup_insert_data(vsub, gsup);
+ vsub->vlr->ops.subscr_update(vsub);
+
+ gsup_reply.message_type = OSMO_GSUP_MSGT_INSERT_DATA_RESULT;
+ return vlr_subscr_tx_gsup_message(vsub, &gsup_reply);
+}
+
+/* Handle UpdateLocation Result from HLR */
+static int vlr_subscr_handle_lu_res(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ if (!vsub->lu_fsm) {
+ LOGVSUBP(LOGL_ERROR, vsub, "Rx GSUP LU Result "
+ "without LU in progress\n");
+ return -ENODEV;
+ }
+
+ /* contrary to MAP, we allow piggy-backing subscriber data onto the
+ * UPDATE LOCATION RESULT, and don't mandate the use of a separate
+ * nested INSERT SUBSCRIBER DATA transaction */
+ vlr_subscr_gsup_insert_data(vsub, gsup);
+
+ osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_HLR_LU_RES, NULL);
+
+ return 0;
+}
+
+/* Handle UpdateLocation Result from HLR */
+static int vlr_subscr_handle_lu_err(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup)
+{
+ if (!vsub->lu_fsm) {
+ LOGVSUBP(LOGL_ERROR, vsub, "Rx GSUP LU Error "
+ "without LU in progress\n");
+ return -ENODEV;
+ }
+
+ LOGVSUBP(LOGL_DEBUG, vsub, "UpdateLocation failed; gmm_cause: %s\n",
+ get_value_string(gsm48_gmm_cause_names, gsup->cause));
+
+ osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_HLR_LU_RES,
+ (void *)&gsup->cause);
+
+ return 0;
+}
+
+/* Handle LOCATION CANCEL request from HLR */
+static int vlr_subscr_handle_cancel_req(struct vlr_subscr *vsub,
+ struct osmo_gsup_message *gsup_msg)
+{
+ struct osmo_gsup_message gsup_reply = {0};
+ int is_update_procedure = !gsup_msg->cancel_type ||
+ gsup_msg->cancel_type == OSMO_GSUP_CANCEL_TYPE_UPDATE;
+
+ LOGVSUBP(LOGL_INFO, vsub, "Cancelling MS subscriber (%s)\n",
+ is_update_procedure ?
+ "update procedure" : "subscription withdraw");
+
+ gsup_reply.message_type = OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT;
+ vlr_subscr_tx_gsup_message(vsub, &gsup_reply);
+
+ vlr_subscr_cancel(vsub, gsup_msg->cause);
+
+ return 0;
+}
+
+/* Incoming handler for GSUP from HLR.
+ * Keep this function non-static for direct invocation by unit tests. */
+int vlr_gsupc_read_cb(struct gsup_client *gsupc, struct msgb *msg)
+{
+ struct vlr_instance *vlr = (struct vlr_instance *) gsupc->data;
+ struct vlr_subscr *vsub;
+ struct osmo_gsup_message gsup;
+ int rc;
+
+ DEBUGP(DVLR, "GSUP rx %u: %s\n", msgb_l2len(msg),
+ osmo_hexdump_nospc(msgb_l2(msg), msgb_l2len(msg)));
+
+ rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
+ if (rc < 0) {
+ LOGP(DVLR, LOGL_ERROR,
+ "decoding GSUP message fails with error '%s' (%d)\n",
+ get_value_string(gsm48_gmm_cause_names, -rc), -rc);
+ return rc;
+ }
+
+ if (!gsup.imsi[0]) {
+ LOGP(DVLR, LOGL_ERROR, "Missing IMSI in GSUP message\n");
+ if (OSMO_GSUP_IS_MSGT_REQUEST(gsup.message_type))
+ vlr_tx_gsup_error_reply(vlr, &gsup,
+ GMM_CAUSE_INV_MAND_INFO);
+ return -GMM_CAUSE_INV_MAND_INFO;
+ }
+
+ vsub = vlr_subscr_find_by_imsi(vlr, gsup.imsi);
+ if (!vsub) {
+ switch (gsup.message_type) {
+ case OSMO_GSUP_MSGT_PURGE_MS_RESULT:
+ case OSMO_GSUP_MSGT_PURGE_MS_ERROR:
+ return vlr_rx_gsup_purge_no_subscr(vlr, &gsup);
+ default:
+ return vlr_rx_gsup_unknown_imsi(vlr, &gsup);
+ }
+ }
+
+ switch (gsup.message_type) {
+ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT:
+ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR:
+ rc = vlr_subscr_handle_sai_res(vsub, &gsup);
+ break;
+ case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST:
+ rc = vlr_subscr_handle_isd_req(vsub, &gsup);
+ break;
+ case OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST:
+ rc = vlr_subscr_handle_cancel_req(vsub, &gsup);
+ break;
+ case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT:
+ rc = vlr_subscr_handle_lu_res(vsub, &gsup);
+ break;
+ case OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR:
+ rc = vlr_subscr_handle_lu_err(vsub, &gsup);
+ break;
+ case OSMO_GSUP_MSGT_PURGE_MS_ERROR:
+ case OSMO_GSUP_MSGT_PURGE_MS_RESULT:
+ case OSMO_GSUP_MSGT_DELETE_DATA_REQUEST:
+ LOGVSUBP(LOGL_ERROR, vsub,
+ "Rx GSUP msg_type=%d not yet implemented\n",
+ gsup.message_type);
+ rc = -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL;
+ break;
+ default:
+ LOGVSUBP(LOGL_ERROR, vsub,
+ "Rx GSUP msg_type=%d not valid at VLR/SGSN side\n",
+ gsup.message_type);
+ rc = -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL;
+ break;
+ }
+
+ vlr_subscr_put(vsub);
+ return rc;
+}
+
+/* MSC->VLR: Subscriber has provided IDENTITY RESPONSE */
+int vlr_subscr_rx_id_resp(struct vlr_subscr *vsub,
+ const uint8_t *mi, size_t mi_len)
+{
+ char mi_string[GSM48_MI_SIZE];
+ uint8_t mi_type = mi[0] & GSM_MI_TYPE_MASK;
+
+ gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len);
+
+ /* update the vlr_subscr with the given identity */
+ switch (mi_type) {
+ case GSM_MI_TYPE_IMSI:
+ if (vsub->imsi[0]
+ && !vlr_subscr_matches_imsi(vsub, mi_string)) {
+ LOGVSUBP(LOGL_ERROR, vsub, "IMSI in ID RESP differs:"
+ " %s\n", mi_string);
+ } else
+ vlr_subscr_set_imsi(vsub, mi_string);
+ break;
+ case GSM_MI_TYPE_IMEI:
+ vlr_subscr_set_imei(vsub, mi_string);
+ break;
+ case GSM_MI_TYPE_IMEISV:
+ vlr_subscr_set_imeisv(vsub, mi_string);
+ break;
+ }
+
+ if (vsub->auth_fsm) {
+ switch (mi_type) {
+ case GSM_MI_TYPE_IMSI:
+ osmo_fsm_inst_dispatch(vsub->auth_fsm,
+ VLR_AUTH_E_MS_ID_IMSI, mi_string);
+ break;
+ }
+ }
+
+ if (vsub->lu_fsm) {
+ uint32_t event = 0;
+ switch (mi_type) {
+ case GSM_MI_TYPE_IMSI:
+ event = VLR_ULA_E_ID_IMSI;
+ break;
+ case GSM_MI_TYPE_IMEI:
+ event = VLR_ULA_E_ID_IMEI;
+ break;
+ case GSM_MI_TYPE_IMEISV:
+ event = VLR_ULA_E_ID_IMEISV;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ break;
+ }
+ osmo_fsm_inst_dispatch(vsub->lu_fsm, event, mi_string);
+ } else {
+ LOGVSUBP(LOGL_NOTICE, vsub, "gratuitous ID RESPONSE?!?\n");
+ }
+
+ return 0;
+}
+
+/* MSC->VLR: Subscriber has provided IDENTITY RESPONSE */
+int vlr_subscr_rx_tmsi_reall_compl(struct vlr_subscr *vsub)
+{
+ if (vsub->lu_fsm) {
+ return osmo_fsm_inst_dispatch(vsub->lu_fsm,
+ VLR_ULA_E_NEW_TMSI_ACK, NULL);
+ } else if (vsub->proc_arq_fsm) {
+ return osmo_fsm_inst_dispatch(vsub->proc_arq_fsm,
+ PR_ARQ_E_TMSI_ACK, NULL);
+ } else {
+ LOGVSUBP(LOGL_NOTICE, vsub,
+ "gratuitous TMSI REALLOC COMPL");
+ return -EINVAL;
+ }
+}
+
+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);
+
+ vsub->imsi_detached_flag = true;
+ if (vsub->lu_complete) {
+ vsub->lu_complete = false;
+ /* balancing the get from vlr_lu_compl_fsm_success() */
+ vlr_subscr_put(vsub);
+ }
+ return 0;
+}
+
+/* Tear down any running FSMs due to MSC connection timeout.
+ * Visit all vsub->*_fsm pointers and give them a queue to send a final reject
+ * message before the entire connection is torn down.
+ * \param[in] vsub subscriber to tear down
+ */
+void vlr_subscr_conn_timeout(struct vlr_subscr *vsub)
+{
+ if (!vsub)
+ return;
+
+ vlr_loc_update_conn_timeout(vsub->lu_fsm);
+ vlr_parq_conn_timeout(vsub->proc_arq_fsm);
+}
+
+struct vlr_instance *vlr_alloc(void *ctx, const struct vlr_ops *ops)
+{
+ struct vlr_instance *vlr = talloc_zero(ctx, struct vlr_instance);
+ OSMO_ASSERT(vlr);
+ OSMO_ASSERT(ops->tx_auth_req);
+ OSMO_ASSERT(ops->tx_auth_rej);
+ OSMO_ASSERT(ops->tx_id_req);
+ OSMO_ASSERT(ops->tx_lu_acc);
+ OSMO_ASSERT(ops->tx_lu_rej);
+ OSMO_ASSERT(ops->tx_cm_serv_acc);
+ OSMO_ASSERT(ops->tx_cm_serv_rej);
+ OSMO_ASSERT(ops->set_ciph_mode);
+ OSMO_ASSERT(ops->subscr_update);
+ OSMO_ASSERT(ops->subscr_assoc);
+
+ INIT_LLIST_HEAD(&vlr->subscribers);
+ INIT_LLIST_HEAD(&vlr->operations);
+ memcpy(&vlr->ops, ops, sizeof(vlr->ops));
+
+ /* osmo_auth_fsm.c */
+ osmo_fsm_register(&vlr_auth_fsm);
+ /* osmo_lu_fsm.c */
+ vlr_lu_fsm_init();
+ /* vlr_access_request_fsm.c */
+ vlr_parq_fsm_init();
+
+ return vlr;
+}
+
+int vlr_start(const char *gsup_unit_name, struct vlr_instance *vlr,
+ const char *gsup_server_addr_str, uint16_t gsup_server_port)
+{
+ OSMO_ASSERT(vlr);
+
+ vlr->gsup_client = gsup_client_create(gsup_unit_name,
+ gsup_server_addr_str,
+ gsup_server_port,
+ &vlr_gsupc_read_cb, NULL);
+ if (!vlr->gsup_client)
+ return -ENOMEM;
+ vlr->gsup_client->data = vlr;
+
+ return 0;
+}
+
+/* MSC->VLR: Subscribre has disconnected */
+int vlr_subscr_disconnected(struct vlr_subscr *vsub)
+{
+ /* This corresponds to a MAP-ABORT from MSC->VLR on a classic B
+ * interface */
+ osmo_fsm_inst_term(vsub->lu_fsm, OSMO_FSM_TERM_REQUEST, NULL);
+ osmo_fsm_inst_term(vsub->auth_fsm, OSMO_FSM_TERM_REQUEST, NULL);
+ vsub->msc_conn_ref = NULL;
+
+ return 0;
+}
+
+/* MSC->VLR: Receive Authentication Failure from Subscriber */
+int vlr_subscr_rx_auth_fail(struct vlr_subscr *vsub, const uint8_t *auts)
+{
+ struct vlr_auth_resp_par par = {0};
+ par.auts = auts;
+
+ osmo_fsm_inst_dispatch(vsub->auth_fsm, VLR_AUTH_E_MS_AUTH_FAIL, &par);
+ return 0;
+}
+
+/* MSC->VLR: Receive Authentication Response from MS
+ * \returns 1 in case of success, 0 in case of delay, -1 on auth error */
+int vlr_subscr_rx_auth_resp(struct vlr_subscr *vsub, bool is_r99,
+ bool is_utran, const uint8_t *res, uint8_t res_len)
+{
+ struct osmo_fsm_inst *auth_fi = vsub->auth_fsm;
+ struct vlr_auth_resp_par par;
+
+ par.is_r99 = is_r99;
+ par.is_utran = is_utran;
+ par.res = res;
+ par.res_len = res_len;
+ osmo_fsm_inst_dispatch(auth_fi, VLR_AUTH_E_MS_AUTH_RESP, (void *) &par);
+
+ return 0;
+}
+
+/* MSC->VLR: Receive result of Ciphering Mode Command from MS */
+void vlr_subscr_rx_ciph_res(struct vlr_subscr *vsub, struct vlr_ciph_result *res)
+{
+ if (vsub->lu_fsm && vsub->lu_fsm->state == VLR_ULA_S_WAIT_CIPH)
+ osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_CIPH_RES, res);
+ if (vsub->proc_arq_fsm
+ && vsub->proc_arq_fsm->state == PR_ARQ_S_WAIT_CIPH)
+ osmo_fsm_inst_dispatch(vsub->proc_arq_fsm, PR_ARQ_E_CIPH_RES,
+ res);
+}
+
+/* Internal evaluation of requested ciphering mode.
+ * Send set_ciph_mode() to MSC depending on the ciph_mode argument.
+ * \param[in] vlr VLR instance.
+ * \param[in] fi Calling FSM instance, for logging.
+ * \param[in] msc_conn_ref MSC conn to send to.
+ * \param[in] ciph_mode Ciphering config, to decide whether to do ciphering.
+ * \returns 0 if no ciphering is needed or message was sent successfully,
+ * or a negative value if ciph_mode is invalid or sending failed.
+ */
+int vlr_set_ciph_mode(struct vlr_instance *vlr,
+ struct osmo_fsm_inst *fi,
+ void *msc_conn_ref,
+ enum vlr_ciph ciph_mode,
+ bool retrieve_imeisv)
+{
+ switch (ciph_mode) {
+ case VLR_CIPH_NONE:
+ return 0;
+
+ case VLR_CIPH_A5_1:
+ case VLR_CIPH_A5_3:
+ return vlr->ops.set_ciph_mode(msc_conn_ref,
+ ciph_mode,
+ retrieve_imeisv);
+
+ case VLR_CIPH_A5_2:
+ /* TODO policy by user config? */
+ LOGPFSML(fi, LOGL_ERROR, "A5/2 ciphering is not allowed\n");
+ return -EINVAL;
+
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "unknown ciphering value: %d\n",
+ ciph_mode);
+ return -EINVAL;
+ }
+}
+
+void log_set_filter_vlr_subscr(struct log_target *target,
+ struct vlr_subscr *vlr_subscr)
+{
+ struct vlr_subscr **fsub = (void*)&target->filter_data[LOG_FLT_VLR_SUBSCR];
+
+ /* free the old data */
+ if (*fsub) {
+ vlr_subscr_put(*fsub);
+ *fsub = NULL;
+ }
+
+ if (vlr_subscr) {
+ target->filter_map |= (1 << LOG_FLT_VLR_SUBSCR);
+ *fsub = vlr_subscr_get(vlr_subscr);
+ } else
+ target->filter_map &= ~(1 << LOG_FLT_VLR_SUBSCR);
+}
diff --git a/openbsc/src/libvlr/vlr_access_req_fsm.c b/openbsc/src/libvlr/vlr_access_req_fsm.c
new file mode 100644
index 000000000..241478f0c
--- /dev/null
+++ b/openbsc/src/libvlr/vlr_access_req_fsm.c
@@ -0,0 +1,776 @@
+/* Osmocom Visitor Location Register (VLR): Access Request FSMs */
+
+/* (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/gsm/gsup.h>
+#include <openbsc/vlr.h>
+#include <openbsc/debug.h>
+
+#include "vlr_core.h"
+#include "vlr_auth_fsm.h"
+#include "vlr_lu_fsm.h"
+#include "vlr_access_req_fsm.h"
+
+#define S(x) (1 << (x))
+
+/***********************************************************************
+ * 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),
+ OSMO_VALUE_STRING(PR_ARQ_E_AUTH_RES),
+ OSMO_VALUE_STRING(PR_ARQ_E_CIPH_RES),
+ OSMO_VALUE_STRING(PR_ARQ_E_UPD_LOC_RES),
+ OSMO_VALUE_STRING(PR_ARQ_E_TRACE_RES),
+ OSMO_VALUE_STRING(PR_ARQ_E_IMEI_RES),
+ OSMO_VALUE_STRING(PR_ARQ_E_PRES_RES),
+ OSMO_VALUE_STRING(PR_ARQ_E_TMSI_ACK),
+ { 0, NULL }
+};
+
+struct proc_arq_priv {
+ struct vlr_instance *vlr;
+ struct vlr_subscr *vsub;
+ void *msc_conn_ref;
+ struct osmo_fsm_inst *ul_child_fsm;
+ struct osmo_fsm_inst *sub_pres_vlr_fsm;
+ uint32_t parent_event_success;
+ uint32_t parent_event_failure;
+ void *parent_event_data;
+
+ enum vlr_parq_type type;
+ enum vlr_proc_arq_result result;
+ bool by_tmsi;
+ char imsi[16];
+ uint32_t tmsi;
+ struct osmo_location_area_id lai;
+ bool authentication_required;
+ enum vlr_ciph ciphering_required;
+ bool is_r99;
+ bool is_utran;
+ bool implicitly_accepted_parq_by_ciphering_cmd;
+};
+
+static void assoc_par_with_subscr(struct osmo_fsm_inst *fi, struct vlr_subscr *vsub)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_instance *vlr = par->vlr;
+
+ vsub->msc_conn_ref = par->msc_conn_ref;
+ par->vsub = vsub;
+ /* Tell MSC to associate this subscriber with the given
+ * connection */
+ vlr->ops.subscr_assoc(par->msc_conn_ref, par->vsub);
+}
+
+#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,
+ 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;
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_DONE, 0, 0);
+}
+
+static void proc_arq_vlr_dispatch_result(struct osmo_fsm_inst *fi,
+ uint32_t prev_state)
+{
+ 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));
+
+ success = (par->result == VLR_PR_ARQ_RES_PASSED);
+
+ /* It would be logical to first dispatch the success event to the
+ * parent FSM, but that could start actions that send messages to the
+ * MS. Rather send the CM Service Accept message first and then signal
+ * success. Since messages are handled synchronously, the success event
+ * will be processed before we handle new incoming data from the MS. */
+
+ if (par->type == VLR_PR_ARQ_T_CM_SERV_REQ) {
+ if (success
+ && !par->implicitly_accepted_parq_by_ciphering_cmd) {
+ rc = par->vlr->ops.tx_cm_serv_acc(par->msc_conn_ref);
+ if (rc) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "Failed to send CM Service Accept\n");
+ success = false;
+ }
+ }
+ if (!success) {
+ rc = par->vlr->ops.tx_cm_serv_rej(par->msc_conn_ref,
+ par->result);
+ if (rc)
+ LOGPFSML(fi, LOGL_ERROR,
+ "Failed to send CM Service Reject\n");
+ }
+ }
+
+ /* For VLR_PR_ARQ_T_PAGING_RESP, there is nothing to send. The conn_fsm
+ * will start handling pending paging transactions. */
+
+ if (!fi->proc.parent) {
+ LOGPFSML(fi, LOGL_ERROR, "No parent FSM");
+ return;
+ }
+ osmo_fsm_inst_dispatch(fi->proc.parent,
+ success ? par->parent_event_success
+ : par->parent_event_failure,
+ par->parent_event_data);
+}
+
+void proc_arq_vlr_cleanup(struct osmo_fsm_inst *fi,
+ enum osmo_fsm_term_cause cause)
+{
+ struct proc_arq_priv *par = fi->priv;
+ if (par->vsub && par->vsub->proc_arq_fsm == fi)
+ par->vsub->proc_arq_fsm = NULL;
+}
+
+static void _proc_arq_vlr_post_imei(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscr *vsub = par->vsub;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ /* TODO: Identity := IMSI */
+ if (0 /* TODO: TMSI reallocation at access: vlr->cfg.alloc_tmsi_arq */) {
+ vlr_subscr_alloc_tmsi(vsub);
+ /* TODO: forward TMSI to MS, wait for TMSI
+ * REALLOC COMPLETE */
+ /* TODO: Freeze old TMSI */
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_TMSI_ACK, 0, 0);
+ return;
+ }
+
+ proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_PASSED);
+}
+
+static void _proc_arq_vlr_post_trace(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscr *vsub = par->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ /* Node 3 */
+ if (0 /* IMEI check required */) {
+ /* Chck_IMEI_VLR */
+ vlr->ops.tx_id_req(par->msc_conn_ref, GSM_MI_TYPE_IMEI);
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_CHECK_IMEI,
+ vlr_timer(vlr, 3270), 3270);
+ } else
+ _proc_arq_vlr_post_imei(fi);
+}
+
+/* After Subscriber_Present_VLR */
+static void _proc_arq_vlr_post_pres(struct osmo_fsm_inst *fi)
+{
+ LOGPFSM(fi, "%s()\n", __func__);
+ if (0 /* TODO: tracing required */) {
+ /* TODO: Trace_Subscriber_Activity_VLR */
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_TRACE_SUB, 0, 0);
+ }
+ _proc_arq_vlr_post_trace(fi);
+}
+
+/* After Update_Location_Child_VLR */
+static void _proc_arq_vlr_node2_post_vlr(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscr *vsub = par->vsub;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ if (!vsub->sub_dataconf_by_hlr_ind) {
+ /* Set User Error: Unidentified Subscriber */
+ proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_UNIDENT_SUBSCR);
+ return;
+ }
+ 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);
+ return;
+ }
+ vsub->imsi_detached_flag = false;
+ if (vsub->ms_not_reachable_flag) {
+ /* Start Subscriber_Present_VLR */
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_SUB_PRES, 0, 0);
+ par->sub_pres_vlr_fsm = sub_pres_vlr_fsm_start(fi, vsub,
+ PR_ARQ_E_PRES_RES);
+ return;
+ }
+ _proc_arq_vlr_post_pres(fi);
+}
+
+static void _proc_arq_vlr_node2_post_ciph(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscr *vsub = par->vsub;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ vsub->conf_by_radio_contact_ind = true;
+ if (vsub->loc_conf_in_hlr_ind == false) {
+ /* start Update_Location_Child_VLR. WE use
+ * Update_HLR_VLR instead, the differences appear
+ * insignificant for now. */
+ par->ul_child_fsm = upd_hlr_vlr_proc_start(fi, vsub,
+ PR_ARQ_E_UPD_LOC_RES);
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_UPD_LOC_CHILD, 0, 0);
+ return;
+ }
+ _proc_arq_vlr_node2_post_vlr(fi);
+}
+
+static bool is_ciph_required(struct proc_arq_priv *par)
+{
+ return par->ciphering_required != VLR_CIPH_NONE;
+}
+
+static void _proc_arq_vlr_node2(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscr *vsub = par->vsub;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ if (!is_ciph_required(par)) {
+ _proc_arq_vlr_node2_post_ciph(fi);
+ return;
+ }
+
+ if (vlr_set_ciph_mode(vsub->vlr, fi, par->msc_conn_ref,
+ par->ciphering_required,
+ vsub->vlr->cfg.retrieve_imeisv)) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "Failed to send Ciphering Mode Command\n");
+ proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_SYSTEM_FAILURE);
+ return;
+ }
+
+ par->implicitly_accepted_parq_by_ciphering_cmd = true;
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_CIPH, 0, 0);
+}
+
+static bool is_auth_required(struct proc_arq_priv *par)
+{
+ /* The cases where the authentication procedure should be used
+ * are defined in 3GPP TS 33.102 */
+ /* For now we use a default value passed in to vlr_lu_fsm(). */
+ return par->authentication_required
+ || (par->ciphering_required != VLR_CIPH_NONE);
+}
+
+/* after the IMSI is known */
+static void proc_arq_vlr_fn_post_imsi(struct osmo_fsm_inst *fi)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscr *vsub = par->vsub;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ OSMO_ASSERT(vsub);
+
+ /* TODO: Identity IMEI -> System Failure */
+ if (is_auth_required(par)) {
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_AUTH,
+ 0, 0);
+ vsub->auth_fsm = auth_fsm_start(vsub, fi->log_level, fi,
+ PR_ARQ_E_AUTH_RES,
+ par->is_r99,
+ par->is_utran);
+ } else {
+ _proc_arq_vlr_node2(fi);
+ }
+}
+
+static void proc_arq_vlr_fn_init(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_instance *vlr = par->vlr;
+ struct vlr_subscr *vsub = NULL;
+
+ OSMO_ASSERT(event == PR_ARQ_E_START);
+
+ /* Obtain_Identity_VLR */
+ if (!par->by_tmsi) {
+ /* IMSI was included */
+ vsub = vlr_subscr_find_by_imsi(par->vlr, par->imsi);
+ } else {
+ /* TMSI was included */
+ vsub = vlr_subscr_find_by_tmsi(par->vlr, par->tmsi);
+ }
+ if (vsub) {
+ if (vsub->proc_arq_fsm && fi != vsub->proc_arq_fsm) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "Another proc_arq_fsm is already"
+ " associated with subscr %s,"
+ " terminating the other FSM.\n",
+ vlr_subscr_name(vsub));
+ proc_arq_fsm_done(vsub->proc_arq_fsm,
+ VLR_PR_ARQ_RES_SYSTEM_FAILURE);
+ }
+ vsub->proc_arq_fsm = fi;
+ assoc_par_with_subscr(fi, vsub);
+ proc_arq_vlr_fn_post_imsi(fi);
+ vlr_subscr_put(vsub);
+ return;
+ }
+ /* No VSUB could be resolved. What now? */
+
+ 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);
+ return;
+ } else {
+ /* TMSI was included, are we permitted to use it? */
+ if (vlr->cfg.parq_retrieve_imsi) {
+ /* Obtain_IMSI_VLR */
+ osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_OBTAIN_IMSI,
+ vlr_timer(vlr, 3270), 3270);
+ return;
+ } else {
+ /* Set User Error: Unidentified Subscriber */
+ proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_UNIDENT_SUBSCR);
+ return;
+ }
+ }
+}
+
+/* ID REQ(IMSI) has returned */
+static void proc_arq_vlr_fn_w_obt_imsi(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_instance *vlr = par->vlr;
+ struct vlr_subscr *vsub;
+
+ OSMO_ASSERT(event == PR_ARQ_E_ID_IMSI);
+
+ 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);
+ return;
+ }
+ assoc_par_with_subscr(fi, vsub);
+ proc_arq_vlr_fn_post_imsi(fi);
+ vlr_subscr_put(vsub);
+}
+
+/* Authenticate_VLR has completed */
+static void proc_arq_vlr_fn_w_auth(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ enum vlr_auth_fsm_result *res = data;
+ enum vlr_proc_arq_result ret = VLR_PR_ARQ_RES_UNKNOWN_ERROR;
+
+ OSMO_ASSERT(res);
+ LOGPFSM(fi, "got %s\n", vlr_auth_fsm_result_name(*res));
+
+ OSMO_ASSERT(event == PR_ARQ_E_AUTH_RES);
+
+ if (res) {
+ switch (*res) {
+ case VLR_AUTH_RES_PASSED:
+ /* Node 2 */
+ _proc_arq_vlr_node2(fi);
+ 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;
+ }
+ }
+ /* send process_access_req response to caller */
+ /* enter error state */
+ proc_arq_fsm_done(fi, ret);
+}
+
+static void proc_arq_vlr_fn_w_ciph(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ struct proc_arq_priv *par = fi->priv;
+ struct vlr_subscr *vsub = par->vsub;
+ struct vlr_ciph_result res = { .cause = VLR_CIPH_REJECT };
+
+ OSMO_ASSERT(event == PR_ARQ_E_CIPH_RES);
+
+ if (!data)
+ LOGPFSML(fi, LOGL_ERROR, "invalid ciphering result: NULL\n");
+ else
+ res = *(struct vlr_ciph_result*)data;
+
+ switch (res.cause) {
+ case VLR_CIPH_COMPL:
+ break;
+ case VLR_CIPH_REJECT:
+ LOGPFSM(fi, "ciphering rejected\n");
+ proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_ILLEGAL_SUBSCR);
+ return;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "invalid ciphering result: %d\n",
+ res.cause);
+ proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_ILLEGAL_SUBSCR);
+ return;
+ }
+
+
+ if (res.imeisv) {
+ LOGPFSM(fi, "got IMEISV: %s\n", res.imeisv);
+ vlr_subscr_set_imeisv(vsub, res.imeisv);
+ }
+ _proc_arq_vlr_node2_post_ciph(fi);
+}
+
+/* Update_Location_Child_VLR has completed */
+static void proc_arq_vlr_fn_w_upd_loc(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ OSMO_ASSERT(event == PR_ARQ_E_UPD_LOC_RES);
+
+ _proc_arq_vlr_node2_post_vlr(fi);
+}
+
+/* Subscriber_Present_VLR has completed */
+static void proc_arq_vlr_fn_w_pres(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ OSMO_ASSERT(event == PR_ARQ_E_PRES_RES);
+
+ _proc_arq_vlr_post_pres(fi);
+}
+
+static void proc_arq_vlr_fn_w_trace(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ OSMO_ASSERT(event == PR_ARQ_E_TRACE_RES);
+
+ _proc_arq_vlr_post_trace(fi);
+}
+
+/* we have received the ID RESPONSE (IMEI) */
+static void proc_arq_vlr_fn_w_imei(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ OSMO_ASSERT(event == PR_ARQ_E_IMEI_RES);
+
+ _proc_arq_vlr_post_imei(fi);
+}
+
+/* MSC tells us that MS has acknowleded TMSI re-allocation */
+static void proc_arq_vlr_fn_w_tmsi(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ OSMO_ASSERT(event == PR_ARQ_E_TMSI_ACK);
+
+ /* FIXME: check confirmation? unfreeze? */
+ proc_arq_fsm_done(fi, VLR_PR_ARQ_RES_PASSED);
+}
+
+static const struct osmo_fsm_state proc_arq_vlr_states[] = {
+ [PR_ARQ_S_INIT] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_INIT),
+ .in_event_mask = S(PR_ARQ_E_START),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_OBTAIN_IMSI) |
+ S(PR_ARQ_S_WAIT_AUTH) |
+ S(PR_ARQ_S_WAIT_UPD_LOC_CHILD) |
+ S(PR_ARQ_S_WAIT_SUB_PRES) |
+ S(PR_ARQ_S_WAIT_TRACE_SUB) |
+ S(PR_ARQ_S_WAIT_CHECK_IMEI) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_init,
+ },
+ [PR_ARQ_S_WAIT_OBTAIN_IMSI] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_OBTAIN_IMSI),
+ .in_event_mask = S(PR_ARQ_E_ID_IMSI),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_AUTH) |
+ S(PR_ARQ_S_WAIT_UPD_LOC_CHILD) |
+ S(PR_ARQ_S_WAIT_SUB_PRES) |
+ S(PR_ARQ_S_WAIT_TRACE_SUB) |
+ S(PR_ARQ_S_WAIT_CHECK_IMEI) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_w_obt_imsi,
+ },
+ [PR_ARQ_S_WAIT_AUTH] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_AUTH),
+ .in_event_mask = S(PR_ARQ_E_AUTH_RES),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_CIPH) |
+ S(PR_ARQ_S_WAIT_UPD_LOC_CHILD) |
+ S(PR_ARQ_S_WAIT_SUB_PRES) |
+ S(PR_ARQ_S_WAIT_TRACE_SUB) |
+ S(PR_ARQ_S_WAIT_CHECK_IMEI) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_w_auth,
+ },
+ [PR_ARQ_S_WAIT_CIPH] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_CIPH),
+ .in_event_mask = S(PR_ARQ_E_CIPH_RES),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_UPD_LOC_CHILD) |
+ S(PR_ARQ_S_WAIT_SUB_PRES) |
+ S(PR_ARQ_S_WAIT_TRACE_SUB) |
+ S(PR_ARQ_S_WAIT_CHECK_IMEI) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_w_ciph,
+ },
+ [PR_ARQ_S_WAIT_UPD_LOC_CHILD] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_UPD_LOC_CHILD),
+ .in_event_mask = S(PR_ARQ_E_UPD_LOC_RES),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_SUB_PRES) |
+ S(PR_ARQ_S_WAIT_TRACE_SUB) |
+ S(PR_ARQ_S_WAIT_CHECK_IMEI) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_w_upd_loc,
+ },
+ [PR_ARQ_S_WAIT_SUB_PRES] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_SUB_PRES),
+ .in_event_mask = S(PR_ARQ_E_PRES_RES),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_TRACE_SUB) |
+ S(PR_ARQ_S_WAIT_CHECK_IMEI) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_w_pres,
+ },
+ [PR_ARQ_S_WAIT_TRACE_SUB] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_TRACE_SUB),
+ .in_event_mask = S(PR_ARQ_E_TRACE_RES),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_CHECK_IMEI) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_w_trace,
+ },
+ [PR_ARQ_S_WAIT_CHECK_IMEI] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_CHECK_IMEI),
+ .in_event_mask = S(PR_ARQ_E_IMEI_RES),
+ .out_state_mask = S(PR_ARQ_S_DONE) |
+ S(PR_ARQ_S_WAIT_TMSI_ACK),
+ .action = proc_arq_vlr_fn_w_imei,
+ },
+ [PR_ARQ_S_WAIT_TMSI_ACK] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_TMSI_ACK),
+ .in_event_mask = S(PR_ARQ_E_TMSI_ACK),
+ .out_state_mask = S(PR_ARQ_S_DONE),
+ .action = proc_arq_vlr_fn_w_tmsi,
+ },
+ [PR_ARQ_S_DONE] = {
+ .name = OSMO_STRINGIFY(PR_ARQ_S_DONE),
+ .onenter = proc_arq_vlr_dispatch_result,
+ },
+};
+
+static struct osmo_fsm proc_arq_vlr_fsm = {
+ .name = "Process_Access_Request_VLR",
+ .states = proc_arq_vlr_states,
+ .num_states = ARRAY_SIZE(proc_arq_vlr_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DVLR,
+ .event_names = proc_arq_vlr_event_names,
+ .cleanup = proc_arq_vlr_cleanup,
+};
+
+void
+vlr_proc_acc_req(struct osmo_fsm_inst *parent,
+ uint32_t parent_event_success,
+ uint32_t parent_event_failure,
+ void *parent_event_data,
+ struct vlr_instance *vlr, void *msc_conn_ref,
+ enum vlr_parq_type type, const uint8_t *mi_lv,
+ const struct osmo_location_area_id *lai,
+ bool authentication_required,
+ enum vlr_ciph ciphering_required,
+ bool is_r99, bool is_utran)
+{
+ struct osmo_fsm_inst *fi;
+ struct proc_arq_priv *par;
+ char mi_string[GSM48_MI_SIZE];
+ uint8_t mi_type;
+
+ fi = osmo_fsm_inst_alloc_child(&proc_arq_vlr_fsm, parent,
+ parent_event_failure);
+ if (!fi)
+ return;
+
+ par = talloc_zero(fi, struct proc_arq_priv);
+ fi->priv = par;
+ par->vlr = vlr;
+ par->msc_conn_ref = msc_conn_ref;
+ par->type = type;
+ par->lai = *lai;
+ par->parent_event_success = parent_event_success;
+ par->parent_event_failure = parent_event_failure;
+ par->parent_event_data = parent_event_data;
+ par->authentication_required = authentication_required;
+ par->ciphering_required = ciphering_required;
+ par->is_r99 = is_r99;
+ par->is_utran = is_utran;
+
+ LOGPFSM(fi, "rev=%s net=%s%s%s\n",
+ is_r99 ? "R99" : "GSM",
+ is_utran ? "UTRAN" : "GERAN",
+ (authentication_required || ciphering_required)?
+ " Auth" : " (no Auth)",
+ (authentication_required || ciphering_required)?
+ (ciphering_required? "+Ciph" : " (no Ciph)")
+ : "");
+
+ gsm48_mi_to_string(mi_string, sizeof(mi_string), mi_lv+1, mi_lv[0]);
+ mi_type = mi_lv[1] & GSM_MI_TYPE_MASK;
+ switch (mi_type) {
+ case GSM_MI_TYPE_IMSI:
+ strncpy(par->imsi, mi_string, sizeof(par->imsi)-1);
+ par->imsi[sizeof(par->imsi)-1] = '\0';
+ par->by_tmsi = false;
+ break;
+ case GSM_MI_TYPE_TMSI:
+ par->by_tmsi = true;
+ par->tmsi = osmo_load32be(mi_lv+2);
+ break;
+ 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);
+ return;
+ }
+
+ osmo_fsm_inst_dispatch(fi, PR_ARQ_E_START, NULL);
+}
+
+/* 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)
+{
+ 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);
+}
+
+
+#if 0
+/***********************************************************************
+ * Update_Location_Child_VLR, TS 29.002 Chapter 25.4.4
+ ***********************************************************************/
+
+enum upd_loc_child_vlr_state {
+ ULC_S_IDLE,
+ ULC_S_WAIT_HLR_RESP,
+ ULC_S_DONE,
+};
+
+enum upd_loc_child_vlr_event {
+ ULC_E_START,
+};
+
+static const struct value_string upd_loc_child_vlr_event_names[] = {
+ { ULC_E_START, "START" },
+ { 0, NULL }
+};
+
+static void upd_loc_child_f_idle(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ OSMO_ASSERT(event == ULC_E_START);
+
+ /* send update location */
+}
+
+static void upd_loc_child_f_w_hlr(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+}
+
+static const struct osmo_fsm_state upd_loc_child_vlr_states[] = {
+ [ULC_S_IDLE] = {
+ .in_event_mask = ,
+ .out_state_mask = S(ULC_S_WAIT_HLR_RESP) |
+ S(ULC_S_DONE),
+ .name = "IDLE",
+ .action = upd_loc_child_f_idle,
+ },
+ [ULC_S_WAIT_HLR_RESP] = {
+ .in_event_mask = ,
+ .out_state_mask = S(ULC_S_DONE),
+ .name = "WAIT-HLR-RESP",
+ .action = upd_loc_child_f_w_hlr,
+ },
+ [ULC_S_DONE] = {
+ .name = "DONE",
+ },
+};
+
+static struct osmo_fsm upd_loc_child_vlr_fsm = {
+ .name = "Update_Location_Child_VLR",
+ .states = upd_loc_child_vlr_states,
+ .num_states = ARRAY_SIZE(upd_loc_child_vlr_states),
+ .log_subsys = DVLR,
+ .event_names = upd_loc_child_vlr_event_names,
+};
+#endif
+
+void vlr_parq_fsm_init(void)
+{
+ //osmo_fsm_register(&upd_loc_child_vlr_fsm);
+ osmo_fsm_register(&proc_arq_vlr_fsm);
+}
diff --git a/openbsc/src/libvlr/vlr_access_req_fsm.h b/openbsc/src/libvlr/vlr_access_req_fsm.h
new file mode 100644
index 000000000..8386da6f2
--- /dev/null
+++ b/openbsc/src/libvlr/vlr_access_req_fsm.h
@@ -0,0 +1,17 @@
+#pragma once
+
+enum proc_arq_vlr_state {
+ PR_ARQ_S_INIT,
+ /* Waiting for Obtain_Identity_VLR (IMSI) result */
+ PR_ARQ_S_WAIT_OBTAIN_IMSI,
+ /* Waiting for Authenticate_VLR result */
+ PR_ARQ_S_WAIT_AUTH,
+ PR_ARQ_S_WAIT_CIPH,
+ PR_ARQ_S_WAIT_UPD_LOC_CHILD,
+ PR_ARQ_S_WAIT_SUB_PRES,
+ PR_ARQ_S_WAIT_TRACE_SUB,
+ PR_ARQ_S_WAIT_CHECK_IMEI,
+ PR_ARQ_S_WAIT_TMSI_ACK,
+ PR_ARQ_S_WAIT_CECK_CONF,
+ PR_ARQ_S_DONE,
+};
diff --git a/openbsc/src/libvlr/vlr_auth_fsm.c b/openbsc/src/libvlr/vlr_auth_fsm.c
new file mode 100644
index 000000000..0eb86e749
--- /dev/null
+++ b/openbsc/src/libvlr/vlr_auth_fsm.c
@@ -0,0 +1,605 @@
+/* Osmocom Visitor Location Register (VLR) Autentication FSM */
+
+/* (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsup.h>
+#include <openbsc/vlr.h>
+#include <openbsc/debug.h>
+
+#include "vlr_core.h"
+#include "vlr_auth_fsm.h"
+
+#define S(x) (1 << (x))
+
+static const struct value_string fsm_auth_event_names[] = {
+ OSMO_VALUE_STRING(VLR_AUTH_E_START),
+ OSMO_VALUE_STRING(VLR_AUTH_E_HLR_SAI_ACK),
+ OSMO_VALUE_STRING(VLR_AUTH_E_HLR_SAI_NACK),
+ OSMO_VALUE_STRING(VLR_AUTH_E_HLR_SAI_ABORT),
+ OSMO_VALUE_STRING(VLR_AUTH_E_MS_AUTH_RESP),
+ OSMO_VALUE_STRING(VLR_AUTH_E_MS_AUTH_FAIL),
+ OSMO_VALUE_STRING(VLR_AUTH_E_MS_ID_IMSI),
+ { 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;
+ bool by_imsi;
+ bool is_r99;
+ bool is_utran;
+ bool auth_requested;
+
+ int auth_tuple_max_use_count; /* see vlr->cfg instead */
+};
+
+/***********************************************************************
+ * Utility functions
+ ***********************************************************************/
+
+/* Always use either vlr_subscr_get_auth_tuple() or vlr_subscr_has_auth_tuple()
+ * instead, to ensure proper use count.
+ * Return an auth tuple with the lowest use_count among the auth tuples. If
+ * max_use_count >= 0, return NULL if all available auth tuples have a use
+ * count > max_use_count. If max_use_count is negative, return a currently
+ * least used auth tuple without enforcing a maximum use count. If there are
+ * no auth tuples, return NULL.
+ */
+static struct gsm_auth_tuple *
+_vlr_subscr_next_auth_tuple(struct vlr_subscr *vsub, int max_use_count)
+{
+ unsigned int count;
+ unsigned int idx;
+ struct gsm_auth_tuple *at = NULL;
+ unsigned int key_seq = GSM_KEY_SEQ_INVAL;
+
+ if (!vsub)
+ return NULL;
+
+ if (vsub->last_tuple)
+ key_seq = vsub->last_tuple->key_seq;
+
+ if (key_seq == GSM_KEY_SEQ_INVAL)
+ /* Start with 0 after increment modulo array size */
+ idx = ARRAY_SIZE(vsub->auth_tuples) - 1;
+ else
+ idx = key_seq;
+
+ for (count = ARRAY_SIZE(vsub->auth_tuples); count > 0; count--) {
+ idx = (idx + 1) % ARRAY_SIZE(vsub->auth_tuples);
+
+ if (vsub->auth_tuples[idx].key_seq == GSM_KEY_SEQ_INVAL)
+ continue;
+
+ if (!at || vsub->auth_tuples[idx].use_count < at->use_count)
+ at = &vsub->auth_tuples[idx];
+ }
+
+ if (!at || (max_use_count >= 0 && at->use_count > max_use_count))
+ return NULL;
+
+ return at;
+}
+
+/* Return an auth tuple and increment its use count. */
+static struct gsm_auth_tuple *
+vlr_subscr_get_auth_tuple(struct vlr_subscr *vsub, int max_use_count)
+{
+ struct gsm_auth_tuple *at = _vlr_subscr_next_auth_tuple(vsub,
+ max_use_count);
+ if (!at)
+ return NULL;
+ at->use_count++;
+ return at;
+}
+
+/* Return whether an auth tuple with the given max_use_count is available. */
+static bool vlr_subscr_has_auth_tuple(struct vlr_subscr *vsub,
+ int max_use_count)
+{
+ return _vlr_subscr_next_auth_tuple(vsub, max_use_count) != NULL;
+}
+
+static bool check_auth_resp(struct vlr_subscr *vsub, bool is_r99,
+ bool is_utran, const uint8_t *res,
+ uint8_t res_len)
+{
+ struct gsm_auth_tuple *at = vsub->last_tuple;
+ struct osmo_auth_vector *vec = &at->vec;
+ bool check_umts;
+ OSMO_ASSERT(at);
+
+ LOGVSUBP(LOGL_DEBUG, vsub, "received res: %s\n",
+ osmo_hexdump(res, res_len));
+
+ /* RES must be present and at least 32bit */
+ if (!res || res_len < sizeof(vec->sres)) {
+ LOGVSUBP(LOGL_NOTICE, vsub, "AUTH RES missing or too short "
+ "(%u)\n", res_len);
+ goto out_false;
+ }
+
+ check_umts = false;
+ if (is_r99 && (vec->auth_types & OSMO_AUTH_TYPE_UMTS)) {
+ check_umts = true;
+ /* We have a R99 capable UE and have a UMTS AKA capable USIM.
+ * However, the ME may still choose to only perform GSM AKA, as
+ * long as the bearer is GERAN */
+ if (res_len != vec->res_len) {
+ if (is_utran) {
+ LOGVSUBP(LOGL_NOTICE, vsub,
+ "AUTH via UTRAN but "
+ "res_len(%u) != vec->res_len(%u)\n",
+ res_len, vec->res_len);
+ goto out_false;
+ }
+ check_umts = false;
+ }
+ }
+
+ if (check_umts) {
+ if (res_len != vec->res_len
+ || memcmp(res, vec->res, res_len)) {
+ LOGVSUBP(LOGL_INFO, vsub, "UMTS AUTH failure:"
+ " mismatching res (expected res=%s)\n",
+ osmo_hexdump(vec->res, vec->res_len));
+ goto out_false;
+ }
+
+ LOGVSUBP(LOGL_INFO, vsub, "AUTH established UMTS security"
+ " context\n");
+ vsub->sec_ctx = VLR_SEC_CTX_UMTS;
+ return true;
+ } else {
+ if (res_len != sizeof(vec->sres)
+ || memcmp(res, vec->sres, sizeof(vec->sres))) {
+ LOGVSUBP(LOGL_INFO, vsub, "GSM AUTH failure:"
+ " mismatching sres (expected sres=%s)\n",
+ osmo_hexdump(vec->sres, sizeof(vec->sres)));
+ goto out_false;
+ }
+
+ LOGVSUBP(LOGL_INFO, vsub, "AUTH established GSM security"
+ " context\n");
+ vsub->sec_ctx = VLR_SEC_CTX_GSM;
+ return true;
+ }
+
+out_false:
+ vsub->sec_ctx = VLR_SEC_CTX_NONE;
+ return false;
+}
+
+static void auth_fsm_onenter_failed(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+
+ /* If authentication hasn't even started, e.g. the HLR sent no auth
+ * info, then we also don't need to tell the HLR about an auth failure.
+ */
+ if (afp->auth_requested)
+ vlr_subscr_tx_auth_fail_rep(vsub);
+}
+
+static bool is_umts_auth(struct auth_fsm_priv *afp,
+ uint32_t auth_types)
+{
+ if (!afp->is_r99)
+ return false;
+ if (!(auth_types & OSMO_AUTH_TYPE_UMTS))
+ return false;
+ return true;
+}
+
+/* Terminate the Auth FSM Instance and notify parent */
+static void auth_fsm_term(struct osmo_fsm_inst *fi, enum vlr_auth_fsm_result res)
+{
+ 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));
+
+ /* Do one final state transition (mostly for logging purpose) */
+ if (res == VLR_AUTH_RES_PASSED)
+ 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);
+ vsub->auth_fsm = NULL;
+}
+
+/* back-end function transmitting authentication. Caller ensures we have valid
+ * tuple */
+static int _vlr_subscr_authenticate(struct osmo_fsm_inst *fi)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+ struct gsm_auth_tuple *at;
+
+ /* Caller ensures we have vectors available */
+ at = vlr_subscr_get_auth_tuple(vsub, afp->auth_tuple_max_use_count);
+ if (!at) {
+ 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);
+ return -1;
+ }
+
+ LOGPFSM(fi, "got auth tuple: use_count=%d key_seq=%d\n",
+ at->use_count, at->key_seq);
+
+ OSMO_ASSERT(at);
+
+ /* Transmit auth req to subscriber */
+ afp->auth_requested = true;
+ vsub->last_tuple = at;
+ vsub->vlr->ops.tx_auth_req(vsub->msc_conn_ref, at,
+ is_umts_auth(afp, at->vec.auth_types));
+ return 0;
+}
+
+/***********************************************************************
+ * FSM State Action functions
+ ***********************************************************************/
+
+/* Initial State of TS 23.018 AUT_VLR */
+static void auth_fsm_needs_auth(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+
+ OSMO_ASSERT(event == VLR_AUTH_E_START);
+
+ /* Start off with the default max_use_count, possibly change that if we
+ * need to re-use an old tuple. */
+ afp->auth_tuple_max_use_count = vsub->vlr->cfg.auth_tuple_max_use_count;
+
+ /* Check if we have vectors available */
+ if (!vlr_subscr_has_auth_tuple(vsub, afp->auth_tuple_max_use_count)) {
+ /* Obtain_Authentication_Sets_VLR */
+ vlr_subscr_req_sai(vsub, NULL, NULL);
+ osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_NEEDS_AUTH_WAIT_AI,
+ GSM_29002_TIMER_M, 0);
+ } else {
+ /* go straight ahead with sending auth request */
+ osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_RESP,
+ vlr_timer(vsub->vlr, 3260), 3260);
+ _vlr_subscr_authenticate(fi);
+ }
+}
+
+/* Waiting for Authentication Info from HLR */
+static void auth_fsm_wait_ai(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+ struct osmo_gsup_message *gsup = data;
+
+ if (event == VLR_AUTH_E_HLR_SAI_NACK)
+ LOGPFSM(fi, "GSUP: rx Auth Info Error cause: %d: %s\n",
+ gsup->cause,
+ get_value_string(gsm48_gmm_cause_names, gsup->cause));
+
+ /* We are in what corresponds to the
+ * Wait_For_Authentication_Sets state of TS 23.018 OAS_VLR */
+ if ((event == VLR_AUTH_E_HLR_SAI_ACK && !gsup->num_auth_vectors)
+ || (event == VLR_AUTH_E_HLR_SAI_NACK &&
+ gsup->cause != GMM_CAUSE_IMSI_UNKNOWN)
+ || (event == VLR_AUTH_E_HLR_SAI_ABORT)) {
+ if (vsub->vlr->cfg.auth_reuse_old_sets_on_error
+ && vlr_subscr_has_auth_tuple(vsub, -1)) {
+ /* To re-use an old tuple, disable the max_use_count
+ * constraint. */
+ afp->auth_tuple_max_use_count = -1;
+ goto pass;
+ }
+ /* result = procedure error */
+ auth_fsm_term(fi, VLR_AUTH_RES_PROC_ERR);
+ return;
+ }
+
+ switch (event) {
+ case VLR_AUTH_E_HLR_SAI_ACK:
+ vlr_subscr_update_tuples(vsub, gsup);
+ goto pass;
+ break;
+ 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);
+ break;
+ }
+
+ return;
+pass:
+ osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_RESP,
+ vlr_timer(vsub->vlr, 3260), 3260);
+ _vlr_subscr_authenticate(fi);
+}
+
+/* Waiting for Authentication Response from MS */
+static void auth_fsm_wait_auth_resp(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+ struct vlr_auth_resp_par *par = data;
+ int rc;
+
+ switch (event) {
+ case VLR_AUTH_E_MS_AUTH_RESP:
+ rc = check_auth_resp(vsub, par->is_r99, par->is_utran,
+ par->res, par->res_len);
+ if (rc == false) {
+ if (!afp->by_imsi) {
+ vlr->ops.tx_id_req(vsub->msc_conn_ref,
+ GSM_MI_TYPE_IMSI);
+ osmo_fsm_inst_state_chg(fi,
+ VLR_SUB_AS_WAIT_ID_IMSI,
+ vlr_timer(vlr, 3270), 3270);
+ } else {
+ auth_fsm_term(fi, VLR_AUTH_RES_AUTH_FAILED);
+ }
+ } else {
+ auth_fsm_term(fi, VLR_AUTH_RES_PASSED);
+ }
+ break;
+ case VLR_AUTH_E_MS_AUTH_FAIL:
+ if (par->auts) {
+ /* First failure, start re-sync attempt */
+ vlr_subscr_req_sai(vsub, par->auts,
+ vsub->last_tuple->vec.rand);
+ osmo_fsm_inst_state_chg(fi,
+ VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC,
+ GSM_29002_TIMER_M, 0);
+ } else
+ auth_fsm_term(fi, VLR_AUTH_RES_AUTH_FAILED);
+ break;
+ }
+}
+
+/* Waiting for Authentication Info from HLR (resync case) */
+static void auth_fsm_wait_ai_resync(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+ struct osmo_gsup_message *gsup = data;
+
+ /* We are in what corresponds to the
+ * Wait_For_Authentication_Sets state of TS 23.018 OAS_VLR */
+ if ((event == VLR_AUTH_E_HLR_SAI_ACK && !gsup->num_auth_vectors) ||
+ (event == VLR_AUTH_E_HLR_SAI_NACK &&
+ 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);
+ }
+ switch (event) {
+ case VLR_AUTH_E_HLR_SAI_ACK:
+ vlr_subscr_update_tuples(vsub, gsup);
+ goto pass;
+ break;
+ 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);
+ break;
+ }
+
+ return;
+pass:
+ osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_RESP_RESYNC,
+ vlr_timer(vsub->vlr, 3260), 3260);
+ _vlr_subscr_authenticate(fi);
+}
+
+/* Waiting for AUTH RESP from MS (re-sync case) */
+static void auth_fsm_wait_auth_resp_resync(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+ struct vlr_auth_resp_par *par = data;
+ struct vlr_instance *vlr = vsub->vlr;
+ int rc;
+
+ switch (event) {
+ case VLR_AUTH_E_MS_AUTH_RESP:
+ rc = check_auth_resp(vsub, par->is_r99, par->is_utran,
+ par->res, par->res_len);
+ if (rc == false) {
+ if (!afp->by_imsi) {
+ vlr->ops.tx_id_req(vsub->msc_conn_ref,
+ GSM_MI_TYPE_IMSI);
+ osmo_fsm_inst_state_chg(fi,
+ VLR_SUB_AS_WAIT_ID_IMSI,
+ vlr_timer(vlr, 3270), 3270);
+ } else {
+ /* Result = Aborted */
+ auth_fsm_term(fi, VLR_AUTH_RES_ABORTED);
+ }
+ } else {
+ /* Result = Pass */
+ auth_fsm_term(fi, VLR_AUTH_RES_PASSED);
+ }
+ break;
+ case VLR_AUTH_E_MS_AUTH_FAIL:
+ /* Second failure: Result = Fail */
+ auth_fsm_term(fi, VLR_AUTH_RES_AUTH_FAILED);
+ break;
+ }
+}
+
+/* AUT_VLR waiting for Obtain_IMSI_VLR result */
+static void auth_fsm_wait_imsi(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct auth_fsm_priv *afp = fi->priv;
+ struct vlr_subscr *vsub = afp->vsub;
+ const char *mi_string = data;
+
+ switch (event) {
+ case VLR_AUTH_E_MS_ID_IMSI:
+ if (vsub->imsi[0]
+ && !vlr_subscr_matches_imsi(vsub, mi_string)) {
+ LOGVSUBP(LOGL_ERROR, vsub, "IMSI in ID RESP differs:"
+ " %s\n", mi_string);
+ } else {
+ strncpy(vsub->imsi, mi_string, sizeof(vsub->imsi));
+ vsub->imsi[sizeof(vsub->imsi)-1] = '\0';
+ }
+ /* retry with identity=IMSI */
+ afp->by_imsi = true;
+ osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_NEEDS_AUTH, 0, 0);
+ osmo_fsm_inst_dispatch(fi, VLR_AUTH_E_START, NULL);
+ break;
+ }
+}
+
+static const struct osmo_fsm_state auth_fsm_states[] = {
+ [VLR_SUB_AS_NEEDS_AUTH] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_NEEDS_AUTH),
+ .in_event_mask = S(VLR_AUTH_E_START),
+ .out_state_mask = S(VLR_SUB_AS_NEEDS_AUTH_WAIT_AI) |
+ S(VLR_SUB_AS_WAIT_RESP),
+ .action = auth_fsm_needs_auth,
+ },
+ [VLR_SUB_AS_NEEDS_AUTH_WAIT_AI] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_NEEDS_AUTH_WAIT_AI),
+ .in_event_mask = S(VLR_AUTH_E_HLR_SAI_ACK) |
+ S(VLR_AUTH_E_HLR_SAI_NACK),
+ .out_state_mask = S(VLR_SUB_AS_AUTH_FAILED) |
+ S(VLR_SUB_AS_WAIT_RESP),
+ .action = auth_fsm_wait_ai,
+ },
+ [VLR_SUB_AS_WAIT_RESP] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_WAIT_RESP),
+ .in_event_mask = S(VLR_AUTH_E_MS_AUTH_RESP) |
+ S(VLR_AUTH_E_MS_AUTH_FAIL),
+ .out_state_mask = S(VLR_SUB_AS_WAIT_ID_IMSI) |
+ S(VLR_SUB_AS_AUTH_FAILED) |
+ S(VLR_SUB_AS_AUTHENTICATED) |
+ S(VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC),
+ .action = auth_fsm_wait_auth_resp,
+ },
+ [VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC),
+ .in_event_mask = S(VLR_AUTH_E_HLR_SAI_ACK) |
+ S(VLR_AUTH_E_HLR_SAI_NACK),
+ .out_state_mask = S(VLR_SUB_AS_AUTH_FAILED) |
+ S(VLR_SUB_AS_WAIT_RESP_RESYNC),
+ .action = auth_fsm_wait_ai_resync,
+ },
+ [VLR_SUB_AS_WAIT_RESP_RESYNC] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_WAIT_RESP_RESYNC),
+ .in_event_mask = S(VLR_AUTH_E_MS_AUTH_RESP) |
+ S(VLR_AUTH_E_MS_AUTH_FAIL),
+ .out_state_mask = S(VLR_SUB_AS_AUTH_FAILED) |
+ S(VLR_SUB_AS_AUTHENTICATED),
+ .action = auth_fsm_wait_auth_resp_resync,
+ },
+ [VLR_SUB_AS_WAIT_ID_IMSI] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_WAIT_ID_IMSI),
+ .in_event_mask = S(VLR_AUTH_E_MS_ID_IMSI),
+ .out_state_mask = S(VLR_SUB_AS_NEEDS_AUTH),
+ .action = auth_fsm_wait_imsi,
+ },
+ [VLR_SUB_AS_AUTHENTICATED] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_AUTHENTICATED),
+ .in_event_mask = 0,
+ .out_state_mask = 0,
+ },
+ [VLR_SUB_AS_AUTH_FAILED] = {
+ .name = OSMO_STRINGIFY(VLR_SUB_AS_AUTH_FAILED),
+ .in_event_mask = 0,
+ .out_state_mask = 0,
+ .onenter = auth_fsm_onenter_failed,
+ },
+};
+
+struct osmo_fsm vlr_auth_fsm = {
+ .name = "VLR_Authenticate",
+ .states = auth_fsm_states,
+ .num_states = ARRAY_SIZE(auth_fsm_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DVLR,
+ .event_names = fsm_auth_event_names,
+};
+
+/***********************************************************************
+ * User API (for SGSN/MSC code)
+ ***********************************************************************/
+
+/* MSC->VLR: Start Procedure Authenticate_VLR (TS 23.012 Ch. 4.1.2.2) */
+struct osmo_fsm_inst *auth_fsm_start(struct vlr_subscr *vsub,
+ uint32_t log_level,
+ struct osmo_fsm_inst *parent,
+ uint32_t parent_term_event,
+ bool is_r99,
+ bool is_utran)
+{
+ struct osmo_fsm_inst *fi;
+ struct auth_fsm_priv *afp;
+
+ fi = osmo_fsm_inst_alloc_child(&vlr_auth_fsm, parent,
+ parent_term_event);
+
+
+ afp = talloc_zero(fi, struct auth_fsm_priv);
+ if (!afp) {
+ osmo_fsm_inst_dispatch(parent, parent_term_event, 0);
+ return NULL;
+ }
+
+ afp->vsub = vsub;
+ if (vsub->imsi[0])
+ afp->by_imsi = true;
+ afp->is_r99 = is_r99;
+ afp->is_utran = is_utran;
+ fi->priv = afp;
+ vsub->auth_fsm = fi;
+
+ osmo_fsm_inst_dispatch(fi, VLR_AUTH_E_START, NULL);
+
+ return fi;
+}
diff --git a/openbsc/src/libvlr/vlr_auth_fsm.h b/openbsc/src/libvlr/vlr_auth_fsm.h
new file mode 100644
index 000000000..226435f83
--- /dev/null
+++ b/openbsc/src/libvlr/vlr_auth_fsm.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <osmocom/core/utils.h>
+
+/* Parameters to VLR_AUTH_E_MS_AUTH_RESP */
+struct vlr_auth_resp_par {
+ bool is_r99;
+ bool is_utran;
+ const uint8_t *res;
+ unsigned int res_len;
+ 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 */
+ VLR_AUTH_E_HLR_SAI_ACK,
+ /* TS 23.018 OAS_VLR1(2): SendAuthInfo NACK from HLR */
+ VLR_AUTH_E_HLR_SAI_NACK,
+ /* FIXME: merge with NACK? */
+ VLR_AUTH_E_HLR_SAI_ABORT,
+ /* Authentication Response from MS */
+ VLR_AUTH_E_MS_AUTH_RESP,
+ /* Authentication Failure from MS */
+ VLR_AUTH_E_MS_AUTH_FAIL,
+ /* Identity Response (IMSI) from MS */
+ VLR_AUTH_E_MS_ID_IMSI,
+};
+
+struct osmo_fsm vlr_auth_fsm;
+
+struct osmo_fsm_inst *auth_fsm_start(struct vlr_subscr *vsub,
+ uint32_t log_level,
+ struct osmo_fsm_inst *parent,
+ uint32_t parent_term_event,
+ bool is_r99,
+ bool is_utran);
diff --git a/openbsc/src/libvlr/vlr_core.h b/openbsc/src/libvlr/vlr_core.h
new file mode 100644
index 000000000..0e63c7e69
--- /dev/null
+++ b/openbsc/src/libvlr/vlr_core.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <openbsc/vlr.h>
+
+#define LOGGSUPP(level, gsup, fmt, args...) \
+ LOGP(DVLR, level, "GSUP(%s) " fmt, \
+ (gsup)->imsi, \
+ ## args)
+
+#define LOGVSUBP(level, vsub, fmt, args...) \
+ LOGP(DVLR, level, "SUBSCR(%s) " fmt, \
+ vlr_subscr_name(vsub), ## args)
+
+
+const char *vlr_subscr_name(struct vlr_subscr *vsub);
+int vlr_subscr_req_lu(struct vlr_subscr *vsub, bool is_ps);
+int vlr_subscr_req_sai(struct vlr_subscr *vsub, const uint8_t *auts,
+ const uint8_t *auts_rand);
+struct vlr_subscr *vlr_subscr_alloc(struct vlr_instance *vlr);
+void vlr_subscr_update_tuples(struct vlr_subscr *vsub,
+ const struct osmo_gsup_message *gsup);
diff --git a/openbsc/src/libvlr/vlr_lu_fsm.c b/openbsc/src/libvlr/vlr_lu_fsm.c
new file mode 100644
index 000000000..d32659f56
--- /dev/null
+++ b/openbsc/src/libvlr/vlr_lu_fsm.c
@@ -0,0 +1,1424 @@
+/* Osmocom Visitor Location Register (VLR): Location Update FSMs */
+
+/* (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/gsm/gsup.h>
+#include <openbsc/vlr.h>
+#include <openbsc/debug.h>
+
+#include "vlr_core.h"
+#include "vlr_auth_fsm.h"
+#include "vlr_lu_fsm.h"
+
+#define S(x) (1 << (x))
+
+#define LU_TIMEOUT_LONG 30
+
+enum vlr_fsm_result {
+ VLR_FSM_RESULT_NONE,
+ VLR_FSM_RESULT_SUCCESS,
+ VLR_FSM_RESULT_FAILURE,
+};
+
+
+/***********************************************************************
+ * Update_HLR_VLR, TS 23.012 Chapter 4.1.2.4
+ ***********************************************************************/
+
+enum upd_hlr_vlr_state {
+ UPD_HLR_VLR_S_INIT,
+ UPD_HLR_VLR_S_WAIT_FOR_DATA,
+ UPD_HLR_VLR_S_DONE,
+};
+
+enum upd_hlr_vlr_evt {
+ UPD_HLR_VLR_E_START,
+ UPD_HLR_VLR_E_INS_SUB_DATA,
+ UPD_HLR_VLR_E_ACT_TRACE_MODE,
+ UPD_HLR_VLR_E_FW_CHECK_SS_IND,
+ UPD_HLR_VLR_E_UPD_LOC_ACK,
+ UPD_HLR_VLR_E_UPD_LOC_NACK,
+};
+
+static const struct value_string upd_hlr_vlr_event_names[] = {
+ OSMO_VALUE_STRING(UPD_HLR_VLR_E_START),
+ OSMO_VALUE_STRING(UPD_HLR_VLR_E_INS_SUB_DATA),
+ OSMO_VALUE_STRING(UPD_HLR_VLR_E_ACT_TRACE_MODE),
+ OSMO_VALUE_STRING(UPD_HLR_VLR_E_FW_CHECK_SS_IND),
+ OSMO_VALUE_STRING(UPD_HLR_VLR_E_UPD_LOC_ACK),
+ OSMO_VALUE_STRING(UPD_HLR_VLR_E_UPD_LOC_NACK),
+ { 0, NULL }
+};
+
+static void upd_hlr_vlr_fsm_init(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct vlr_subscr *vsub = fi->priv;
+
+ OSMO_ASSERT(event == UPD_HLR_VLR_E_START);
+
+ /* Send UpdateLocation to HLR */
+ vlr_subscr_req_lu(vsub, vsub->vlr->cfg.is_ps);
+ osmo_fsm_inst_state_chg(fi, UPD_HLR_VLR_S_WAIT_FOR_DATA,
+ LU_TIMEOUT_LONG, 0);
+}
+
+static void upd_hlr_vlr_fsm_wait_data(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct vlr_subscr *vsub = fi->priv;
+
+ switch (event) {
+ case UPD_HLR_VLR_E_INS_SUB_DATA:
+ /* FIXME: Insert_Subscr_Data_VLR */
+ break;
+ case UPD_HLR_VLR_E_ACT_TRACE_MODE:
+ /* TODO: Activate_Tracing_VLR */
+ break;
+ case UPD_HLR_VLR_E_FW_CHECK_SS_IND:
+ /* TODO: Forward Check SS Ind to MSC */
+ break;
+ case UPD_HLR_VLR_E_UPD_LOC_ACK:
+ /* Inside Update_HLR_VLR after UpdateLocationAck */
+ vsub->sub_dataconf_by_hlr_ind = true;
+ vsub->loc_conf_in_hlr_ind = true;
+ osmo_fsm_inst_state_chg(fi, UPD_HLR_VLR_S_DONE, 0, 0);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ break;
+ case UPD_HLR_VLR_E_UPD_LOC_NACK:
+ /* Inside Update_HLR_VLR after UpdateLocationNack */
+ /* TODO: Check_User_Error_In_Serving_Network_Entity */
+ vsub->sub_dataconf_by_hlr_ind = false;
+ vsub->loc_conf_in_hlr_ind = false;
+ osmo_fsm_inst_state_chg(fi, UPD_HLR_VLR_S_DONE, 0, 0);
+ /* Data is a pointer to a gsm48_gmm_cause which we
+ * simply pass through */
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data);
+ break;
+ }
+}
+
+static const struct osmo_fsm_state upd_hlr_vlr_states[] = {
+ [UPD_HLR_VLR_S_INIT] = {
+ .in_event_mask = S(UPD_HLR_VLR_E_START),
+ .out_state_mask = S(UPD_HLR_VLR_S_WAIT_FOR_DATA),
+ .name = OSMO_STRINGIFY(UPD_HLR_VLR_S_INIT),
+ .action = upd_hlr_vlr_fsm_init,
+ },
+ [UPD_HLR_VLR_S_WAIT_FOR_DATA] = {
+ .in_event_mask = S(UPD_HLR_VLR_E_INS_SUB_DATA) |
+ S(UPD_HLR_VLR_E_ACT_TRACE_MODE) |
+ S(UPD_HLR_VLR_E_FW_CHECK_SS_IND) |
+ S(UPD_HLR_VLR_E_UPD_LOC_ACK) |
+ S(UPD_HLR_VLR_E_UPD_LOC_NACK),
+ .out_state_mask = S(UPD_HLR_VLR_S_DONE),
+ .name = OSMO_STRINGIFY(UPD_HLR_VLR_S_WAIT_FOR_DATA),
+ .action = upd_hlr_vlr_fsm_wait_data,
+ },
+ [UPD_HLR_VLR_S_DONE] = {
+ .name = OSMO_STRINGIFY(UPD_HLR_VLR_S_DONE),
+ },
+};
+
+static struct osmo_fsm upd_hlr_vlr_fsm = {
+ .name = "upd_hlr_vlr_fsm",
+ .states = upd_hlr_vlr_states,
+ .num_states = ARRAY_SIZE(upd_hlr_vlr_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DVLR,
+ .event_names = upd_hlr_vlr_event_names,
+};
+
+struct osmo_fsm_inst *
+upd_hlr_vlr_proc_start(struct osmo_fsm_inst *parent,
+ struct vlr_subscr *vsub,
+ uint32_t parent_event)
+{
+ struct osmo_fsm_inst *fi;
+
+ fi = osmo_fsm_inst_alloc_child(&upd_hlr_vlr_fsm, parent,
+ parent_event);
+ if (!fi)
+ return NULL;
+
+ fi->priv = vsub;
+ osmo_fsm_inst_dispatch(fi, UPD_HLR_VLR_E_START, NULL);
+
+ return fi;
+}
+
+
+/***********************************************************************
+ * Subscriber_Present_VLR, TS 29.002 Chapter 25.10.1
+ ***********************************************************************/
+
+enum sub_pres_vlr_state {
+ SUB_PRES_VLR_S_INIT,
+ SUB_PRES_VLR_S_WAIT_FOR_HLR,
+ SUB_PRES_VLR_S_DONE,
+};
+
+enum sub_pres_vlr_event {
+ SUB_PRES_VLR_E_START,
+ SUB_PRES_VLR_E_READY_SM_CNF,
+ SUB_PRES_VLR_E_READY_SM_ERR,
+};
+
+static const struct value_string sub_pres_vlr_event_names[] = {
+ OSMO_VALUE_STRING(SUB_PRES_VLR_E_START),
+ OSMO_VALUE_STRING(SUB_PRES_VLR_E_READY_SM_CNF),
+ OSMO_VALUE_STRING(SUB_PRES_VLR_E_READY_SM_ERR),
+ { 0, NULL }
+};
+
+static void sub_pres_vlr_fsm_init(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct vlr_subscr *vsub = fi->priv;
+ OSMO_ASSERT(event == SUB_PRES_VLR_E_START);
+
+ if (!vsub->ms_not_reachable_flag) {
+ osmo_fsm_inst_state_chg(fi, SUB_PRES_VLR_S_DONE, 0, 0);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ return;
+ }
+ /* FIXME: Send READY_FOR_SM via GSUP */
+ osmo_fsm_inst_state_chg(fi, SUB_PRES_VLR_S_WAIT_FOR_HLR,
+ LU_TIMEOUT_LONG, 0);
+}
+
+static void sub_pres_vlr_fsm_wait_hlr(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct vlr_subscr *vsub = fi->priv;
+
+ switch (event) {
+ case SUB_PRES_VLR_E_READY_SM_CNF:
+ vsub->ms_not_reachable_flag = false;
+ break;
+ case SUB_PRES_VLR_E_READY_SM_ERR:
+ break;
+ }
+ osmo_fsm_inst_state_chg(fi, SUB_PRES_VLR_S_DONE, 0, 0);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+static const struct osmo_fsm_state sub_pres_vlr_states[] = {
+ [SUB_PRES_VLR_S_INIT] = {
+ .in_event_mask = S(SUB_PRES_VLR_E_START),
+ .out_state_mask = S(SUB_PRES_VLR_S_WAIT_FOR_HLR) |
+ S(SUB_PRES_VLR_S_DONE),
+ .name = OSMO_STRINGIFY(SUB_PRES_VLR_S_INIT),
+ .action = sub_pres_vlr_fsm_init,
+ },
+ [SUB_PRES_VLR_S_WAIT_FOR_HLR] = {
+ .in_event_mask = S(SUB_PRES_VLR_E_READY_SM_CNF) |
+ S(SUB_PRES_VLR_E_READY_SM_ERR),
+ .out_state_mask = S(SUB_PRES_VLR_S_DONE),
+ .name = OSMO_STRINGIFY(SUB_PRES_VLR_S_WAIT_FOR_HLR),
+ .action = sub_pres_vlr_fsm_wait_hlr,
+ },
+ [SUB_PRES_VLR_S_DONE] = {
+ .name = OSMO_STRINGIFY(SUB_PRES_VLR_S_DONE),
+ },
+};
+
+static struct osmo_fsm sub_pres_vlr_fsm = {
+ .name = "sub_pres_vlr_fsm",
+ .states = sub_pres_vlr_states,
+ .num_states = ARRAY_SIZE(sub_pres_vlr_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DVLR,
+ .event_names = sub_pres_vlr_event_names,
+};
+
+struct osmo_fsm_inst *sub_pres_vlr_fsm_start(struct osmo_fsm_inst *parent,
+ struct vlr_subscr *vsub,
+ uint32_t term_event)
+{
+ struct osmo_fsm_inst *fi;
+
+ fi = osmo_fsm_inst_alloc_child(&sub_pres_vlr_fsm, parent,
+ term_event);
+ if (!fi)
+ return NULL;
+
+ fi->priv = vsub;
+ osmo_fsm_inst_dispatch(fi, SUB_PRES_VLR_E_START, NULL);
+
+ return fi;
+}
+
+/***********************************************************************
+ * Location_Update_Completion_VLR, TS 23.012 Chapter 4.1.2.3
+ ***********************************************************************/
+
+enum lu_compl_vlr_state {
+ LU_COMPL_VLR_S_INIT,
+ LU_COMPL_VLR_S_WAIT_SUB_PRES,
+ LU_COMPL_VLR_S_WAIT_IMEI,
+ LU_COMPL_VLR_S_WAIT_IMEI_TMSI,
+ LU_COMPL_VLR_S_WAIT_TMSI_CNF,
+ LU_COMPL_VLR_S_DONE,
+};
+
+enum lu_compl_vlr_event {
+ LU_COMPL_VLR_E_START,
+ LU_COMPL_VLR_E_SUB_PRES_COMPL,
+ LU_COMPL_VLR_E_IMEI_CHECK_ACK,
+ LU_COMPL_VLR_E_IMEI_CHECK_NACK,
+ LU_COMPL_VLR_E_NEW_TMSI_ACK,
+};
+
+static const struct value_string lu_compl_vlr_event_names[] = {
+ OSMO_VALUE_STRING(LU_COMPL_VLR_E_START),
+ OSMO_VALUE_STRING(LU_COMPL_VLR_E_SUB_PRES_COMPL),
+ OSMO_VALUE_STRING(LU_COMPL_VLR_E_IMEI_CHECK_ACK),
+ OSMO_VALUE_STRING(LU_COMPL_VLR_E_IMEI_CHECK_NACK),
+ OSMO_VALUE_STRING(LU_COMPL_VLR_E_NEW_TMSI_ACK),
+ { 0, NULL }
+};
+
+struct lu_compl_vlr_priv {
+ struct vlr_subscr *vsub;
+ void *msc_conn_ref;
+ struct osmo_fsm_inst *sub_pres_vlr_fsm;
+ uint32_t parent_event_success;
+ uint32_t parent_event_failure;
+ void *parent_event_data;
+ enum vlr_fsm_result result;
+ uint8_t cause;
+ bool assign_tmsi;
+};
+
+static void _vlr_lu_compl_fsm_done(struct osmo_fsm_inst *fi,
+ enum vlr_fsm_result result,
+ uint8_t cause)
+{
+ struct lu_compl_vlr_priv *lcvp = fi->priv;
+ lcvp->result = result;
+ lcvp->cause = cause;
+ osmo_fsm_inst_state_chg(fi, LU_COMPL_VLR_S_DONE, 0, 0);
+}
+
+static void vlr_lu_compl_fsm_success(struct osmo_fsm_inst *fi)
+{
+ struct lu_compl_vlr_priv *lcvp = fi->priv;
+ struct vlr_subscr *vsub = lcvp->vsub;
+ if (!vsub->lu_complete) {
+ vsub->lu_complete = true;
+ /* Balanced by vlr_subscr_rx_imsi_detach() */
+ vlr_subscr_get(vsub);
+ }
+ _vlr_lu_compl_fsm_done(fi, VLR_FSM_RESULT_SUCCESS, 0);
+}
+
+static void vlr_lu_compl_fsm_failure(struct osmo_fsm_inst *fi, uint8_t cause)
+{
+ struct lu_compl_vlr_priv *lcvp = fi->priv;
+ lcvp->vsub->vlr->ops.tx_lu_rej(lcvp->msc_conn_ref, cause);
+ _vlr_lu_compl_fsm_done(fi, VLR_FSM_RESULT_FAILURE, cause);
+}
+
+static void vlr_lu_compl_fsm_dispatch_result(struct osmo_fsm_inst *fi,
+ uint32_t prev_state)
+{
+ struct lu_compl_vlr_priv *lcvp = fi->priv;
+ if (!fi->proc.parent) {
+ LOGPFSML(fi, LOGL_ERROR, "No parent FSM\n");
+ return;
+ }
+ osmo_fsm_inst_dispatch(fi->proc.parent,
+ (lcvp->result == VLR_FSM_RESULT_SUCCESS)
+ ? lcvp->parent_event_success
+ : lcvp->parent_event_failure,
+ &lcvp->cause);
+}
+
+static void lu_compl_vlr_init(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_compl_vlr_priv *lcvp = fi->priv;
+ struct vlr_subscr *vsub = lcvp->vsub;
+ struct vlr_instance *vlr;
+ OSMO_ASSERT(vsub);
+ vlr = vsub->vlr;
+ OSMO_ASSERT(vlr);
+
+ OSMO_ASSERT(event == LU_COMPL_VLR_E_START);
+
+ /* TODO: National Roaming restrictions? */
+ /* TODO: Roaming restriction due to unsupported feature in subscriber
+ * data? */
+ /* TODO: Regional subscription restriction? */
+ /* TODO: Administrative restriction of subscribres' access feature? */
+ /* TODO: AccessRestrictuionData parameter available? */
+ /* TODO: AccessRestrictionData permits RAT? */
+ /* Node 1 */
+ /* TODO: Autonomous CSG supported in VPLMN and allowed by HPLMN? */
+ /* TODO: Hybrid Cel / CSG Cell */
+ /* Node 2 */
+ vsub->la_allowed = true;
+ vsub->imsi_detached_flag = false;
+ /* Start Subscriber_Present_VLR Procedure */
+ osmo_fsm_inst_state_chg(fi, LU_COMPL_VLR_S_WAIT_SUB_PRES,
+ LU_TIMEOUT_LONG, 0);
+
+ lcvp->sub_pres_vlr_fsm = sub_pres_vlr_fsm_start(fi, vsub,
+ LU_COMPL_VLR_E_SUB_PRES_COMPL);
+
+}
+
+static void lu_compl_vlr_new_tmsi(struct osmo_fsm_inst *fi)
+{
+ struct lu_compl_vlr_priv *lcvp = fi->priv;
+ struct vlr_subscr *vsub = lcvp->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ if (vlr_subscr_alloc_tmsi(vsub)) {
+ vlr_lu_compl_fsm_failure(fi,
+ GSM48_REJECT_SRV_OPT_TMP_OUT_OF_ORDER);
+ return;
+ }
+
+ osmo_fsm_inst_state_chg(fi, LU_COMPL_VLR_S_WAIT_TMSI_CNF,
+ vlr_timer(vlr, 3250), 3250);
+
+ vlr->ops.tx_lu_acc(lcvp->msc_conn_ref, vsub->tmsi_new);
+}
+
+/* After completion of Subscriber_Present_VLR */
+static void lu_compl_vlr_wait_subscr_pres(struct osmo_fsm_inst *fi,
+ uint32_t event,
+ void *data)
+{
+ struct lu_compl_vlr_priv *lcvp = fi->priv;
+ struct vlr_subscr *vsub = lcvp->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+
+ OSMO_ASSERT(event == LU_COMPL_VLR_E_SUB_PRES_COMPL);
+
+ lcvp->sub_pres_vlr_fsm = NULL;
+
+ /* TODO: Trace_Subscriber_Activity_VLR */
+
+ if (vlr->cfg.check_imei_rqd) {
+ /* Check IMEI VLR */
+ osmo_fsm_inst_state_chg(fi,
+ lcvp->assign_tmsi ?
+ LU_COMPL_VLR_S_WAIT_IMEI_TMSI
+ : LU_COMPL_VLR_S_WAIT_IMEI,
+ vlr_timer(vlr, 3270), 3270);
+ vlr->ops.tx_id_req(lcvp->msc_conn_ref, GSM_MI_TYPE_IMEI);
+ return;
+ }
+
+ /* Do we need to allocate a TMSI? */
+ if (lcvp->assign_tmsi) {
+ lu_compl_vlr_new_tmsi(fi);
+ return;
+ }
+
+ /* Location Updating Accept */
+ vlr->ops.tx_lu_acc(lcvp->msc_conn_ref, GSM_RESERVED_TMSI);
+ vlr_lu_compl_fsm_success(fi);
+}
+
+/* Waiting for completion of CHECK_IMEI_VLR */
+static void lu_compl_vlr_wait_imei(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_compl_vlr_priv *lcvp = fi->priv;
+ struct vlr_subscr *vsub = lcvp->vsub;
+ struct vlr_instance *vlr = vsub->vlr;
+
+ switch (event) {
+ case LU_COMPL_VLR_E_IMEI_CHECK_ACK:
+ if (!vsub->imei[0]) {
+ /* Abort: Do nothing */
+ vlr_lu_compl_fsm_failure(fi,
+ GSM48_REJECT_PROTOCOL_ERROR);
+ return;
+ }
+ /* Pass */
+ break;
+
+ case LU_COMPL_VLR_E_IMEI_CHECK_NACK:
+ vlr_lu_compl_fsm_failure(fi, GSM48_REJECT_ILLEGAL_ME);
+ /* FIXME: IMEI Check Fail to VLR Application (Detach IMSI VLR) */
+ return;
+ }
+
+ /* IMEI is available. Allocate TMSI if needed. */
+ if (lcvp->assign_tmsi) {
+ if (fi->state != LU_COMPL_VLR_S_WAIT_IMEI_TMSI)
+ LOGPFSML(fi, LOGL_ERROR,
+ "TMSI required, expected to be in state"
+ " LU_COMPL_VLR_S_WAIT_IMEI_TMSI,"
+ " am in %s instead\n",
+ osmo_fsm_state_name(fi->fsm, fi->state));
+ /* Logged an error, continue anyway. */
+
+ lu_compl_vlr_new_tmsi(fi);
+
+ /* Wait for TMSI ack */
+ return;
+ }
+
+ /* No TMSI needed, accept now. */
+ vlr->ops.tx_lu_acc(lcvp->msc_conn_ref, GSM_RESERVED_TMSI);
+ vlr_lu_compl_fsm_success(fi);
+}
+
+/* Waiting for TMSI confirmation */
+static void lu_compl_vlr_wait_tmsi(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_compl_vlr_priv *lcvp = fi->priv;
+ struct vlr_subscr *vsub = lcvp->vsub;
+
+ OSMO_ASSERT(event == LU_COMPL_VLR_E_NEW_TMSI_ACK);
+
+ if (!vsub || vsub->tmsi_new == GSM_RESERVED_TMSI) {
+ LOGPFSML(fi, LOGL_ERROR, "TMSI Realloc Compl implies that"
+ " the subscriber has a new TMSI allocated, but"
+ " the new TMSI is unset.\n");
+ vlr_lu_compl_fsm_failure(fi, GSM48_REJECT_NETWORK_FAILURE);
+ return;
+ }
+
+ vsub->tmsi = vsub->tmsi_new;
+ vsub->tmsi_new = GSM_RESERVED_TMSI;
+
+ vlr_lu_compl_fsm_success(fi);
+}
+
+static const struct osmo_fsm_state lu_compl_vlr_states[] = {
+ [LU_COMPL_VLR_S_INIT] = {
+ .in_event_mask = S(LU_COMPL_VLR_E_START),
+ .out_state_mask = S(LU_COMPL_VLR_S_DONE) |
+ S(LU_COMPL_VLR_S_WAIT_SUB_PRES) |
+ S(LU_COMPL_VLR_S_WAIT_IMEI),
+ .name = OSMO_STRINGIFY(LU_COMPL_VLR_S_INIT),
+ .action = lu_compl_vlr_init,
+ },
+ [LU_COMPL_VLR_S_WAIT_SUB_PRES] = {
+ .in_event_mask = S(LU_COMPL_VLR_E_SUB_PRES_COMPL),
+ .out_state_mask = S(LU_COMPL_VLR_S_WAIT_IMEI) |
+ S(LU_COMPL_VLR_S_WAIT_IMEI_TMSI) |
+ S(LU_COMPL_VLR_S_WAIT_TMSI_CNF) |
+ S(LU_COMPL_VLR_S_DONE),
+ .name = OSMO_STRINGIFY(LU_COMPL_VLR_S_WAIT_SUB_PRES),
+ .action = lu_compl_vlr_wait_subscr_pres,
+ },
+ [LU_COMPL_VLR_S_WAIT_IMEI] = {
+ .in_event_mask = S(LU_COMPL_VLR_E_IMEI_CHECK_ACK) |
+ S(LU_COMPL_VLR_E_IMEI_CHECK_NACK),
+ .out_state_mask = S(LU_COMPL_VLR_S_DONE),
+ .name = OSMO_STRINGIFY(LU_COMPL_VLR_S_WAIT_IMEI),
+ .action = lu_compl_vlr_wait_imei,
+ },
+ [LU_COMPL_VLR_S_WAIT_IMEI_TMSI] = {
+ .in_event_mask = S(LU_COMPL_VLR_E_IMEI_CHECK_ACK) |
+ S(LU_COMPL_VLR_E_IMEI_CHECK_NACK),
+ .out_state_mask = S(LU_COMPL_VLR_S_DONE) |
+ S(LU_COMPL_VLR_S_WAIT_TMSI_CNF),
+ .name = OSMO_STRINGIFY(LU_COMPL_VLR_S_WAIT_IMEI_TMSI),
+ .action = lu_compl_vlr_wait_imei,
+ },
+ [LU_COMPL_VLR_S_WAIT_TMSI_CNF] = {
+ .in_event_mask = S(LU_COMPL_VLR_E_NEW_TMSI_ACK),
+ .out_state_mask = S(LU_COMPL_VLR_S_DONE),
+ .name = OSMO_STRINGIFY(LU_COMPL_VLR_S_WAIT_TMSI_CNF),
+ .action = lu_compl_vlr_wait_tmsi,
+ },
+ [LU_COMPL_VLR_S_DONE] = {
+ .name = OSMO_STRINGIFY(LU_COMPL_VLR_S_DONE),
+ .onenter = vlr_lu_compl_fsm_dispatch_result,
+ },
+};
+
+static struct osmo_fsm lu_compl_vlr_fsm = {
+ .name = "lu_compl_vlr_fsm",
+ .states = lu_compl_vlr_states,
+ .num_states = ARRAY_SIZE(lu_compl_vlr_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DVLR,
+ .event_names = lu_compl_vlr_event_names,
+};
+
+struct osmo_fsm_inst *
+lu_compl_vlr_proc_alloc(struct osmo_fsm_inst *parent,
+ struct vlr_subscr *vsub,
+ void *msc_conn_ref,
+ uint32_t parent_event_success,
+ uint32_t parent_event_failure,
+ bool assign_tmsi)
+{
+ struct osmo_fsm_inst *fi;
+ struct lu_compl_vlr_priv *lcvp;
+
+ fi = osmo_fsm_inst_alloc_child(&lu_compl_vlr_fsm, parent,
+ parent_event_failure);
+ if (!fi)
+ return NULL;
+
+ lcvp = talloc_zero(fi, struct lu_compl_vlr_priv);
+ lcvp->vsub = vsub;
+ lcvp->msc_conn_ref = msc_conn_ref;
+ lcvp->parent_event_success = parent_event_success;
+ lcvp->parent_event_failure = parent_event_failure;
+ lcvp->assign_tmsi = assign_tmsi;
+ fi->priv = lcvp;
+
+ return fi;
+}
+
+
+/***********************************************************************
+ * Update_Location_Area_VLR, TS 23.012 Chapter 4.1.2.1
+ ***********************************************************************/
+
+static const struct value_string fsm_lu_event_names[] = {
+ OSMO_VALUE_STRING(VLR_ULA_E_UPDATE_LA),
+ OSMO_VALUE_STRING(VLR_ULA_E_SEND_ID_ACK),
+ OSMO_VALUE_STRING(VLR_ULA_E_SEND_ID_NACK),
+ OSMO_VALUE_STRING(VLR_ULA_E_AUTH_RES),
+ OSMO_VALUE_STRING(VLR_ULA_E_CIPH_RES),
+ OSMO_VALUE_STRING(VLR_ULA_E_ID_IMSI),
+ OSMO_VALUE_STRING(VLR_ULA_E_ID_IMEI),
+ OSMO_VALUE_STRING(VLR_ULA_E_ID_IMEISV),
+ OSMO_VALUE_STRING(VLR_ULA_E_HLR_LU_RES),
+ OSMO_VALUE_STRING(VLR_ULA_E_UPD_HLR_COMPL),
+ OSMO_VALUE_STRING(VLR_ULA_E_LU_COMPL_SUCCESS),
+ OSMO_VALUE_STRING(VLR_ULA_E_LU_COMPL_FAILURE),
+ OSMO_VALUE_STRING(VLR_ULA_E_NEW_TMSI_ACK),
+ { 0, NULL }
+};
+
+struct lu_fsm_priv {
+ struct vlr_instance *vlr;
+ struct vlr_subscr *vsub;
+ void *msc_conn_ref;
+ struct osmo_fsm_inst *upd_hlr_vlr_fsm;
+ struct osmo_fsm_inst *lu_compl_vlr_fsm;
+ uint32_t parent_event_success;
+ uint32_t parent_event_failure;
+ void *parent_event_data;
+ enum vlr_fsm_result result;
+ uint8_t rej_cause;
+
+ enum vlr_lu_type type;
+ bool lu_by_tmsi;
+ char imsi[16];
+ uint32_t tmsi;
+ struct osmo_location_area_id old_lai;
+ struct osmo_location_area_id new_lai;
+ bool authentication_required;
+ enum vlr_ciph ciphering_required;
+ bool is_r99;
+ bool is_utran;
+ bool assign_tmsi;
+};
+
+
+/* Determine if given location area is served by this VLR */
+static bool lai_in_this_vlr(struct vlr_instance *vlr,
+ const struct osmo_location_area_id *lai)
+{
+ /* TODO: VLR needs to keep a locally configued list of LAIs */
+ return true;
+}
+
+/* Determine if authentication is required */
+static bool is_auth_required(struct lu_fsm_priv *lfp)
+{
+ /* The cases where the authentication procedure should be used
+ * are defined in 3GPP TS 33.102 */
+ /* For now we use a default value passed in to vlr_lu_fsm(). */
+ return lfp->authentication_required
+ || (lfp->ciphering_required != VLR_CIPH_NONE);
+}
+
+/* Determine if ciphering is required */
+static bool is_ciph_required(struct lu_fsm_priv *lfp)
+{
+ return lfp->ciphering_required != VLR_CIPH_NONE;
+}
+
+/* Determine if a HLR Update is required */
+static bool hlr_update_needed(struct vlr_subscr *vsub)
+{
+ /* TODO: properly decide this, rather than always assuming we
+ * need to update the HLR. */
+ return true;
+}
+
+static void lu_fsm_dispatch_result(struct osmo_fsm_inst *fi,
+ uint32_t prev_state)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ if (!fi->proc.parent) {
+ LOGPFSML(fi, LOGL_ERROR, "No parent FSM\n");
+ return;
+ }
+ osmo_fsm_inst_dispatch(fi->proc.parent,
+ (lfp->result == VLR_FSM_RESULT_SUCCESS)
+ ? lfp->parent_event_success
+ : lfp->parent_event_failure,
+ lfp->parent_event_data);
+}
+
+static void _lu_fsm_done(struct osmo_fsm_inst *fi,
+ enum vlr_fsm_result result)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ lfp->result = result;
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_DONE, 0, 0);
+}
+
+static void lu_fsm_success(struct osmo_fsm_inst *fi)
+{
+ _lu_fsm_done(fi, VLR_FSM_RESULT_SUCCESS);
+}
+
+static void lu_fsm_failure(struct osmo_fsm_inst *fi, uint8_t rej_cause)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ if (rej_cause)
+ lfp->vlr->ops.tx_lu_rej(lfp->msc_conn_ref, rej_cause);
+ _lu_fsm_done(fi, VLR_FSM_RESULT_FAILURE);
+}
+
+static void vlr_loc_upd_start_lu_compl_fsm(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ lfp->lu_compl_vlr_fsm =
+ lu_compl_vlr_proc_alloc(fi, lfp->vsub, lfp->msc_conn_ref,
+ VLR_ULA_E_LU_COMPL_SUCCESS,
+ VLR_ULA_E_LU_COMPL_FAILURE,
+ lfp->assign_tmsi);
+
+ osmo_fsm_inst_dispatch(lfp->lu_compl_vlr_fsm, LU_COMPL_VLR_E_START, NULL);
+}
+
+static void lu_fsm_discard_lu_compl_fsm(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ if (!lfp->lu_compl_vlr_fsm)
+ return;
+ osmo_fsm_inst_term(lfp->lu_compl_vlr_fsm, OSMO_FSM_TERM_PARENT, NULL);
+}
+
+/* 4.1.2.1 Node 4 */
+static void vlr_loc_upd_node_4(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_subscr *vsub = lfp->vsub;
+ bool hlr_unknown = false;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ if (hlr_unknown) {
+ /* FIXME: Delete subscriber record */
+ /* LU REJ: Roaming not allowed */
+ lu_fsm_failure(fi, GSM48_REJECT_ROAMING_NOT_ALLOWED);
+ } else {
+ /* Update_HLR_VLR */
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_HLR_UPD,
+ LU_TIMEOUT_LONG, 0);
+ lfp->upd_hlr_vlr_fsm =
+ upd_hlr_vlr_proc_start(fi, vsub, VLR_ULA_E_UPD_HLR_COMPL);
+ }
+}
+
+/* 4.1.2.1 Node B */
+static void vlr_loc_upd_node_b(struct osmo_fsm_inst *fi)
+{
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ /* FIXME */
+ if (0) { /* IMEISV or PgA to send */
+ vlr_loc_upd_node_4(fi);
+ } else {
+ /* Location_Update_Completion */
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_LU_COMPL,
+ LU_TIMEOUT_LONG, 0);
+ vlr_loc_upd_start_lu_compl_fsm(fi);
+ }
+}
+
+/* Non-standard: after Ciphering Mode Complete (or no ciph required) */
+static void vlr_loc_upd_post_ciph(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_subscr *vsub = lfp->vsub;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ OSMO_ASSERT(vsub);
+
+ vsub->conf_by_radio_contact_ind = true;
+ /* Update LAI */
+ vsub->cgi.lai = lfp->new_lai;
+ vsub->dormant_ind = false;
+ vsub->cancel_loc_rx = false;
+ if (hlr_update_needed(vsub)) {
+ vlr_loc_upd_node_4(fi);
+ } else {
+ /* TODO: ADD Support */
+ /* TODO: Node A: PgA Support */
+ vlr_loc_upd_node_b(fi);
+ }
+}
+
+/* 4.1.2.1 after Authentication successful (or no auth rqd) */
+static void vlr_loc_upd_post_auth(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_subscr *vsub = lfp->vsub;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ OSMO_ASSERT(vsub);
+
+ if (!is_ciph_required(lfp)) {
+ vlr_loc_upd_post_ciph(fi);
+ return;
+ }
+
+ if (vlr_set_ciph_mode(vsub->vlr, fi, lfp->msc_conn_ref,
+ lfp->ciphering_required,
+ vsub->vlr->cfg.retrieve_imeisv)) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "Failed to send Ciphering Mode Command\n");
+ vlr_lu_compl_fsm_failure(fi, GSM48_REJECT_NETWORK_FAILURE);
+ return;
+ }
+
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_CIPH, LU_TIMEOUT_LONG, 0);
+}
+
+static void vlr_loc_upd_node1(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_subscr *vsub = lfp->vsub;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ OSMO_ASSERT(vsub);
+
+ if (is_auth_required(lfp)) {
+ /* Authenticate_VLR */
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_AUTH,
+ LU_TIMEOUT_LONG, 0);
+ vsub->auth_fsm = auth_fsm_start(lfp->vsub, fi->log_level,
+ fi, VLR_ULA_E_AUTH_RES,
+ lfp->is_r99,
+ lfp->is_utran);
+ } else {
+ /* no need for authentication */
+ vlr_loc_upd_post_auth(fi);
+ }
+}
+
+static void vlr_loc_upd_want_imsi(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_instance *vlr = lfp->vlr;
+
+ LOGPFSM(fi, "%s()\n", __func__);
+
+ OSMO_ASSERT(lfp->vsub);
+
+ /* Obtain_IMSI_VLR */
+ 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 */
+}
+
+static int assoc_lfp_with_sub(struct osmo_fsm_inst *fi, struct vlr_subscr *vsub)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_instance *vlr = lfp->vlr;
+
+ if (vsub->lu_fsm) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "A Location Updating process is already pending for"
+ " this subscriber. Aborting.\n");
+ /* Also get rid of the other pending LU attempt? */
+ /*lu_fsm_failure(vsub->lu_fsm, GSM48_REJECT_CONGESTION);*/
+ lu_fsm_failure(fi, GSM48_REJECT_CONGESTION);
+ return -EINVAL;
+ }
+ vsub->lu_fsm = fi;
+ vsub->msc_conn_ref = lfp->msc_conn_ref;
+ /* FIXME: send new LAC to HLR? */
+ vsub->lac = lfp->new_lai.lac;
+ lfp->vsub = vsub;
+ /* Tell MSC to associate this subscriber with the given
+ * connection */
+ vlr->ops.subscr_assoc(lfp->msc_conn_ref, lfp->vsub);
+ return 0;
+}
+
+static const char *lai_name(struct osmo_location_area_id *lai)
+{
+ static char buf[64];
+ snprintf(buf, sizeof(buf),"MCC:%u, MNC:%u, LAC:%u",
+ lai->plmn.mcc, lai->plmn.mnc, lai->lac);
+ return buf;
+}
+
+/* 4.1.2.1: Subscriber (via MSC/SGSN) requests location update */
+static void _start_lu_main(struct osmo_fsm_inst *fi)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_instance *vlr = lfp->vlr;
+ struct vlr_subscr *vsub = NULL;
+ bool created;
+
+ /* TODO: PUESBINE related handling */
+
+ /* Is previous LAI in this VLR? */
+ if (!lai_in_this_vlr(vlr, &lfp->old_lai)) {
+#if 0
+ /* FIXME: check previous VLR, (3) */
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_PVLR,
+ LU_TIMEOUT_LONG, 0);
+ return;
+#endif
+ LOGPFSML(fi, LOGL_NOTICE, "LAI change from %s,"
+ " but checking previous VLR not implemented\n",
+ lai_name(&lfp->old_lai));
+ }
+
+ if (!lfp->imsi[0]) {
+ /* TMSI was used */
+ lfp->lu_by_tmsi = true;
+ /* TMSI clash: if a different subscriber already has this TMSI,
+ * we will find that other subscriber in the VLR. So the IMSIs
+ * would mismatch, but we don't know about it. Theoretically,
+ * an authentication process would thwart any attempt to use
+ * someone else's TMSI.
+ * TODO: Otherwise we can ask for the IMSI and verify that it
+ * matches the IMSI on record. */
+ vsub = vlr_subscr_find_or_create_by_tmsi(vlr, lfp->tmsi,
+ &created);
+
+ if (!vsub) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "VLR subscriber allocation failed\n");
+ lu_fsm_failure(fi, GSM48_REJECT_SRV_OPT_TMP_OUT_OF_ORDER);
+ return;
+ }
+
+ vsub->sub_dataconf_by_hlr_ind = false;
+ if (assoc_lfp_with_sub(fi, vsub)) {
+ vlr_subscr_put(vsub);
+ return; /* error */
+ }
+
+ if (created)
+ vlr_loc_upd_want_imsi(fi);
+ else
+ vlr_loc_upd_node1(fi);
+ /* We cannot have MSC area change, as the VLR
+ * serves only one MSC */
+ vlr_subscr_put(vsub);
+ } else {
+ /* IMSI was used */
+ vsub = vlr_subscr_find_or_create_by_imsi(vlr, lfp->imsi, NULL);
+
+ if (!vsub) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "VLR subscriber allocation failed\n");
+ lu_fsm_failure(fi, GSM48_REJECT_SRV_OPT_TMP_OUT_OF_ORDER);
+ vlr_subscr_put(vsub);
+ return;
+ }
+
+ vsub->sub_dataconf_by_hlr_ind = false;
+ if (assoc_lfp_with_sub(fi, vsub)) {
+ vlr_subscr_put(vsub);
+ return; /* error */
+ }
+ vlr_loc_upd_node1(fi);
+ vlr_subscr_put(vsub);
+ }
+}
+
+
+static void lu_fsm_idle(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_instance *vlr = lfp->vlr;
+
+ OSMO_ASSERT(event == VLR_ULA_E_UPDATE_LA);
+
+ if (1) { // FIXME
+ //if (lfp->type == VLR_LU_TYPE_PERIODIC && lfp->vsub->imeisv[0])
+ /* R_IMEISV_IR1 passed */
+ _start_lu_main(fi);
+ } else {
+ vlr->ops.tx_id_req(lfp->msc_conn_ref, GSM_MI_TYPE_IMEISV);
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_IMEISV,
+ vlr_timer(vlr, 3270), 3270);
+ }
+}
+
+static void lu_fsm_wait_imeisv(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ switch (event) {
+ case VLR_ULA_E_ID_IMEISV:
+ /* FIXME: copy IMEISV */
+ _start_lu_main(fi);
+ break;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "event without effect: %s\n",
+ osmo_fsm_event_name(fi->fsm, event));
+ break;
+ }
+}
+
+/* Wait for response from Send_Identification to PVLR */
+static void lu_fsm_wait_pvlr(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ switch (event) {
+ case VLR_ULA_E_SEND_ID_ACK:
+ vlr_loc_upd_node1(fi);
+ break;
+ case VLR_ULA_E_SEND_ID_NACK:
+ vlr_loc_upd_want_imsi(fi);
+ break;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "event without effect: %s\n",
+ osmo_fsm_event_name(fi->fsm, event));
+ break;
+ }
+}
+
+/* Wait for result of Authenticate_VLR procedure */
+static void lu_fsm_wait_auth(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ enum vlr_auth_fsm_result *res = data;
+ uint8_t rej_cause = 0;
+
+ 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;
+
+ lu_fsm_failure(fi, rej_cause);
+}
+
+static void lu_fsm_wait_ciph(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_subscr *vsub = lfp->vsub;
+ struct vlr_ciph_result res = { .cause = VLR_CIPH_REJECT };
+
+ OSMO_ASSERT(event == VLR_ULA_E_CIPH_RES);
+
+ if (!data)
+ LOGPFSML(fi, LOGL_ERROR, "invalid ciphering result: NULL\n");
+ else
+ res = *(struct vlr_ciph_result*)data;
+
+ switch (res.cause) {
+ case VLR_CIPH_COMPL:
+ break;
+ case VLR_CIPH_REJECT:
+ LOGPFSM(fi, "ciphering rejected\n");
+ lu_fsm_failure(fi, GSM48_REJECT_INVALID_MANDANTORY_INF);
+ return;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "invalid ciphering result: %d\n",
+ res.cause);
+ lu_fsm_failure(fi, GSM48_REJECT_INVALID_MANDANTORY_INF);
+ return;
+ }
+
+ if (res.imeisv) {
+ LOGPFSM(fi, "got IMEISV: %s\n", res.imeisv);
+ vlr_subscr_set_imeisv(vsub, res.imeisv);
+ }
+ vlr_loc_upd_post_ciph(fi);
+}
+
+static void lu_fsm_wait_imsi(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_subscr *vsub = lfp->vsub;
+ char *mi_string = data;
+
+ switch (event) {
+ case VLR_ULA_E_ID_IMSI:
+ vlr_subscr_set_imsi(vsub, mi_string);
+ vlr_loc_upd_node1(fi);
+ break;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "event without effect: %s\n",
+ osmo_fsm_event_name(fi->fsm, event));
+ break;
+ }
+}
+
+/* At the end of Update_HLR_VLR */
+static void lu_fsm_wait_hlr_ul_res(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+
+ switch (event) {
+ case VLR_ULA_E_HLR_LU_RES:
+ /* pass-through this event to Update_HLR_VLR */
+ if (data == NULL)
+ osmo_fsm_inst_dispatch(lfp->upd_hlr_vlr_fsm, UPD_HLR_VLR_E_UPD_LOC_ACK, NULL);
+ else
+ osmo_fsm_inst_dispatch(lfp->upd_hlr_vlr_fsm, UPD_HLR_VLR_E_UPD_LOC_NACK, data);
+ break;
+ case VLR_ULA_E_UPD_HLR_COMPL:
+ if (data == NULL) {
+ /* successful case */
+ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_LU_COMPL,
+ LU_TIMEOUT_LONG, 0);
+ vlr_loc_upd_start_lu_compl_fsm(fi);
+ /* continue in MSC ?!? */
+ } else {
+ /* unsuccessful case */
+ enum gsm48_gmm_cause cause =
+ *(enum gsm48_gmm_cause *)data;
+ if (0 /* procedure_error && vlr->cfg.standalone_mode */) {
+ osmo_fsm_inst_state_chg(fi,
+ VLR_ULA_S_WAIT_LU_COMPL_STANDALONE,
+ LU_TIMEOUT_LONG, 0);
+ vlr_loc_upd_start_lu_compl_fsm(fi);
+ } else {
+ lu_fsm_failure(fi, cause);
+ }
+ }
+ break;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "event without effect: %s\n",
+ osmo_fsm_event_name(fi->fsm, event));
+ break;
+ }
+}
+
+/* Wait for end of Location_Update_Completion_VLR */
+static void lu_fsm_wait_lu_compl(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ uint8_t cause;
+
+ switch (event) {
+ case VLR_ULA_E_NEW_TMSI_ACK:
+ osmo_fsm_inst_dispatch(lfp->lu_compl_vlr_fsm,
+ LU_COMPL_VLR_E_NEW_TMSI_ACK, NULL);
+ break;
+ case VLR_ULA_E_ID_IMEI:
+ osmo_fsm_inst_dispatch(lfp->lu_compl_vlr_fsm,
+ LU_COMPL_VLR_E_IMEI_CHECK_ACK, NULL);
+ break;
+ case VLR_ULA_E_LU_COMPL_SUCCESS:
+ lu_fsm_discard_lu_compl_fsm(fi);
+
+ /* Update Register */
+ /* TODO: Set_Notification_Type 23.078 */
+ /* TODO: Notify_gsmSCF 23.078 */
+ /* TODO: Authenticated Radio Contact Established -> ARC */
+ lu_fsm_success(fi);
+ break;
+ case VLR_ULA_E_LU_COMPL_FAILURE:
+ cause = GSM48_REJECT_NETWORK_FAILURE;
+ if (data)
+ cause = *(uint8_t*)data;
+ lu_fsm_discard_lu_compl_fsm(fi);
+ lu_fsm_failure(fi, cause);
+ break;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "event without effect: %s\n",
+ osmo_fsm_event_name(fi->fsm, event));
+ break;
+ }
+}
+
+/* Wait for end of Location_Update_Completion_VLR (standalone case) */
+static void lu_fsm_wait_lu_compl_standalone(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_subscr *vsub = lfp->vsub;
+ uint8_t cause;
+
+ switch (event) {
+ case VLR_ULA_E_NEW_TMSI_ACK:
+ osmo_fsm_inst_dispatch(lfp->lu_compl_vlr_fsm,
+ LU_COMPL_VLR_E_NEW_TMSI_ACK, NULL);
+ break;
+ case VLR_ULA_E_LU_COMPL_SUCCESS:
+ lu_fsm_discard_lu_compl_fsm(fi);
+ vsub->sub_dataconf_by_hlr_ind = false;
+ lu_fsm_success(fi);
+ break;
+ case VLR_ULA_E_LU_COMPL_FAILURE:
+ vsub->sub_dataconf_by_hlr_ind = false;
+ cause = GSM48_REJECT_NETWORK_FAILURE;
+ if (data)
+ cause = *(uint8_t*)data;
+ lu_fsm_discard_lu_compl_fsm(fi);
+ lu_fsm_failure(fi, cause);
+ break;
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "event without effect: %s\n",
+ osmo_fsm_event_name(fi->fsm, event));
+ break;
+ }
+}
+
+static const struct osmo_fsm_state vlr_lu_fsm_states[] = {
+ [VLR_ULA_S_IDLE] = {
+ .in_event_mask = S(VLR_ULA_E_UPDATE_LA),
+ .out_state_mask = S(VLR_ULA_S_WAIT_IMEISV) |
+ S(VLR_ULA_S_WAIT_PVLR) |
+ S(VLR_ULA_S_WAIT_IMSI) |
+ S(VLR_ULA_S_WAIT_AUTH) |
+ S(VLR_ULA_S_WAIT_HLR_UPD) |
+ S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_IDLE),
+ .action = lu_fsm_idle,
+ },
+ [VLR_ULA_S_WAIT_IMEISV] = {
+ .in_event_mask = S(VLR_ULA_E_ID_IMEISV),
+ .out_state_mask = S(VLR_ULA_S_WAIT_PVLR) |
+ S(VLR_ULA_S_WAIT_IMSI) |
+ S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_IMEISV),
+ .action = lu_fsm_wait_imeisv,
+ },
+ [VLR_ULA_S_WAIT_PVLR] = {
+ .in_event_mask = S(VLR_ULA_E_SEND_ID_ACK) |
+ S(VLR_ULA_E_SEND_ID_NACK),
+ .out_state_mask = S(VLR_ULA_S_WAIT_IMSI) |
+ S(VLR_ULA_S_WAIT_AUTH) |
+ S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_PVLR),
+ .action = lu_fsm_wait_pvlr,
+ },
+ [VLR_ULA_S_WAIT_AUTH] = {
+ .in_event_mask = S(VLR_ULA_E_AUTH_RES),
+ .out_state_mask = S(VLR_ULA_S_WAIT_CIPH) |
+ S(VLR_ULA_S_WAIT_LU_COMPL) |
+ S(VLR_ULA_S_WAIT_HLR_UPD) |
+ S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_AUTH),
+ .action = lu_fsm_wait_auth,
+ },
+ [VLR_ULA_S_WAIT_CIPH] = {
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_CIPH),
+ .in_event_mask = S(VLR_ULA_E_CIPH_RES),
+ .out_state_mask = S(VLR_ULA_S_WAIT_LU_COMPL) |
+ S(VLR_ULA_S_WAIT_HLR_UPD) |
+ S(VLR_ULA_S_DONE),
+ .action = lu_fsm_wait_ciph,
+ },
+ [VLR_ULA_S_WAIT_IMSI] = {
+ .in_event_mask = S(VLR_ULA_E_ID_IMSI),
+ .out_state_mask = S(VLR_ULA_S_WAIT_AUTH) |
+ 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_UPD] = {
+ .in_event_mask = S(VLR_ULA_E_HLR_LU_RES) |
+ S(VLR_ULA_E_UPD_HLR_COMPL),
+ .out_state_mask = S(VLR_ULA_S_WAIT_LU_COMPL) |
+ S(VLR_ULA_S_WAIT_LU_COMPL_STANDALONE) |
+ S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_HLR_UPD),
+ .action = lu_fsm_wait_hlr_ul_res,
+ },
+ [VLR_ULA_S_WAIT_LU_COMPL] = {
+ .in_event_mask = S(VLR_ULA_E_LU_COMPL_SUCCESS) |
+ S(VLR_ULA_E_LU_COMPL_FAILURE) |
+ S(VLR_ULA_E_NEW_TMSI_ACK) |
+ S(VLR_ULA_E_ID_IMEI) |
+ S(VLR_ULA_E_ID_IMEISV),
+ .out_state_mask = S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_LU_COMPL),
+ .action = lu_fsm_wait_lu_compl,
+ },
+ [VLR_ULA_S_WAIT_LU_COMPL_STANDALONE] = {
+ .in_event_mask = S(VLR_ULA_E_LU_COMPL_SUCCESS) |
+ S(VLR_ULA_E_LU_COMPL_FAILURE) |
+ S(VLR_ULA_E_NEW_TMSI_ACK),
+ .out_state_mask = S(VLR_ULA_S_DONE),
+ .name = OSMO_STRINGIFY(VLR_ULA_S_WAIT_LU_COMPL_STANDALONE),
+ .action = lu_fsm_wait_lu_compl_standalone,
+ },
+ [VLR_ULA_S_DONE] = {
+ .name = OSMO_STRINGIFY(VLR_ULA_S_DONE),
+ .onenter = lu_fsm_dispatch_result,
+ },
+};
+
+static void fsm_lu_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct lu_fsm_priv *lfp = fi->priv;
+ struct vlr_subscr *vsub = lfp->vsub;
+
+ LOGPFSM(fi, "fsm_lu_cleanup called with cause %s\n",
+ osmo_fsm_term_cause_name(cause));
+ if (vsub && vsub->lu_fsm == fi)
+ vsub->lu_fsm = NULL;
+}
+
+static struct osmo_fsm vlr_lu_fsm = {
+ .name = "vlr_lu_fsm",
+ .states = vlr_lu_fsm_states,
+ .num_states = ARRAY_SIZE(vlr_lu_fsm_states),
+ .allstate_event_mask = 0,
+ .allstate_action = NULL,
+ .log_subsys = DVLR,
+ .event_names = fsm_lu_event_names,
+ .cleanup = fsm_lu_cleanup,
+};
+
+struct osmo_fsm_inst *
+vlr_loc_update(struct osmo_fsm_inst *parent,
+ uint32_t parent_event_success,
+ uint32_t parent_event_failure,
+ void *parent_event_data,
+ struct vlr_instance *vlr, void *msc_conn_ref,
+ enum vlr_lu_type type, uint32_t tmsi, const char *imsi,
+ const struct osmo_location_area_id *old_lai,
+ const struct osmo_location_area_id *new_lai,
+ bool authentication_required,
+ enum vlr_ciph ciphering_required,
+ bool is_r99, bool is_utran,
+ bool assign_tmsi)
+{
+ struct osmo_fsm_inst *fi;
+ struct lu_fsm_priv *lfp;
+
+ fi = osmo_fsm_inst_alloc_child(&vlr_lu_fsm, parent, parent_event_failure);
+ if (!fi)
+ return NULL;
+
+ lfp = talloc_zero(fi, struct lu_fsm_priv);
+ lfp->vlr = vlr;
+ lfp->msc_conn_ref = msc_conn_ref;
+ lfp->tmsi = tmsi;
+ lfp->type = type;
+ lfp->old_lai = *old_lai;
+ lfp->new_lai = *new_lai;
+ lfp->lu_by_tmsi = true;
+ lfp->parent_event_success = parent_event_success;
+ lfp->parent_event_failure = parent_event_failure;
+ lfp->parent_event_data = parent_event_data;
+ lfp->authentication_required = authentication_required;
+ lfp->ciphering_required = ciphering_required;
+ lfp->is_r99 = is_r99;
+ lfp->is_utran = is_utran;
+ lfp->assign_tmsi = assign_tmsi;
+ if (imsi) {
+ strncpy(lfp->imsi, imsi, sizeof(lfp->imsi)-1);
+ lfp->imsi[sizeof(lfp->imsi)-1] = '\0';
+ lfp->lu_by_tmsi = false;
+ }
+ fi->priv = lfp;
+
+ LOGPFSM(fi, "rev=%s net=%s%s%s\n",
+ is_r99 ? "R99" : "GSM",
+ is_utran ? "UTRAN" : "GERAN",
+ (authentication_required || ciphering_required)?
+ " Auth" : " (no Auth)",
+ (authentication_required || ciphering_required)?
+ (ciphering_required? "+Ciph" : " (no Ciph)")
+ : "");
+
+ osmo_fsm_inst_dispatch(fi, VLR_ULA_E_UPDATE_LA, NULL);
+
+ 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)
+{
+ if (!fi || fi->state == VLR_ULA_S_DONE)
+ return;
+ LOGPFSM(fi, "Connection timed out\n");
+ lu_fsm_failure(fi, GSM48_REJECT_CONGESTION);
+}
+
+void vlr_lu_fsm_init(void)
+{
+ osmo_fsm_register(&vlr_lu_fsm);
+ osmo_fsm_register(&upd_hlr_vlr_fsm);
+ osmo_fsm_register(&sub_pres_vlr_fsm);
+ osmo_fsm_register(&lu_compl_vlr_fsm);
+}
diff --git a/openbsc/src/libvlr/vlr_lu_fsm.h b/openbsc/src/libvlr/vlr_lu_fsm.h
new file mode 100644
index 000000000..5cf13c77e
--- /dev/null
+++ b/openbsc/src/libvlr/vlr_lu_fsm.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <osmocom/core/fsm.h>
+
+enum vlr_lu_state {
+ VLR_ULA_S_IDLE,
+ VLR_ULA_S_WAIT_IMEISV,
+ VLR_ULA_S_WAIT_PVLR, /* Waiting for ID from PVLR */
+ 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_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 */
+ VLR_ULA_S_DONE
+};
+
+void vlr_lu_fsm_init(void);
diff --git a/openbsc/src/osmo-nitb/Makefile.am b/openbsc/src/osmo-nitb/Makefile.am
index f4ef487c6..a99334d33 100644
--- a/openbsc/src/osmo-nitb/Makefile.am
+++ b/openbsc/src/osmo-nitb/Makefile.am
@@ -32,6 +32,7 @@ osmo_nitb_LDADD = \
$(top_builddir)/src/libbsc/libbsc.a \
$(top_builddir)/src/libcommon-cs/libcommon-cs.a \
$(top_builddir)/src/libmsc/libmsc.a \
+ $(top_builddir)/src/libvlr/libvlr.a \
$(top_builddir)/src/libtrau/libtrau.a \
$(top_builddir)/src/libcommon/libcommon.a \
$(LIBOSMOGSM_LIBS) \
diff --git a/openbsc/tests/vlr/Makefile.am b/openbsc/tests/vlr/Makefile.am
new file mode 100644
index 000000000..2208a6f59
--- /dev/null
+++ b/openbsc/tests/vlr/Makefile.am
@@ -0,0 +1,22 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall -ggdb3 \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(NULL)
+AM_LDFLAGS = $(COVERAGE_LDFLAGS)
+
+noinst_PROGRAMS = vlr_test
+
+vlr_test_SOURCES = vlr_test.c
+vlr_test_LDADD = \
+ $(top_builddir)/src/libvlr/libvlr.a \
+ $(top_builddir)/src/libcommon/libcommon.a \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBRARY_DL) \
+ $(LIBCRYPTO_LIBS) \
+ $(NULL)
diff --git a/openbsc/tests/vlr/vlr_test.c b/openbsc/tests/vlr/vlr_test.c
new file mode 100644
index 000000000..19beb69c4
--- /dev/null
+++ b/openbsc/tests/vlr/vlr_test.c
@@ -0,0 +1,694 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/fsm.h>
+
+#include <openbsc/vlr.h>
+#include <openbsc/debug.h>
+
+#define S(x) (1 << (x))
+
+/*
+ * TODO:
+ * * test FSM for all testvlr_mode (and more)
+ * * test also the time-outs in the vlr code
+ * * test for memory leaks
+ * * how to get the HLR running? Or test against stub?
+ * * test disappearing MS connection
+ * * test absence of HLR
+ */
+
+void *tall_bsc_ctx;
+static struct vlr_instance *g_vlr;
+
+/***********************************************************************
+ * Finite State Machine simulating MS and MSC towards VLR
+ ***********************************************************************/
+
+static int timer_error_cb(struct osmo_fsm_inst *fi)
+{
+ LOGPFSM(fi, "timer expired waiting for completion\n");
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+ return 1;
+}
+
+enum testvlr_mode {
+ MODE_SUCCESS,
+ MODE_SUCCESS_TMSI,
+ MODE_AUTH_FAIL,
+ MODE_AUTH_RESYNC,
+};
+
+struct testvlr_priv {
+ enum testvlr_mode mode;
+ uint32_t tmsi;
+ char imsi[16];
+ char imei[16];
+ struct osmo_location_area_id old_lai;
+ struct osmo_location_area_id new_lai;
+
+ struct vlr_subscr *subscr;
+
+ struct osmo_fsm_inst *lu_fsm;
+};
+
+#define fsi_priv(x) (struct testvlr_priv *)(x)->priv
+
+enum f_state {
+ /*! initial state */
+ ST_NULL,
+ /*! LU was sent by MS */
+ ST_LU_SENT,
+ /*! waiting for auth re-sync */
+ ST_RESYNC_SENT,
+ /* Waiting for LU ACK */
+ ST_WAIT_LU_ACK,
+ ST_DONE,
+ ST_FAILED,
+};
+
+enum f_event {
+ /* events from MS */
+ EVT_MS_TX_LU, /* transmit LU REQ to network */
+ EVT_MS_TX_ID_RESP, /* tranmit ID RSP to network */
+ EVT_MS_TX_AUTH_RESP, /* transmit AUTH RESP to network */
+ EVT_MS_TX_AUTH_FAIL, /* transmit AUTH FAIL to network */
+ EVT_MS_CONN_LOST, /* connection to MS was lost */
+
+ /* events from VLR */
+ EVT_VLR_AUTH_REQ, /* transmit AUTH REQ to MS */
+ EVT_VLR_ID_REQ_IMSI, /* transmit ID REQ(IMSI) to MS */
+ EVT_VLR_ID_REQ_IMEI, /* tramsmit ID REQ(IMEI) to MS */
+ EVT_VLR_ID_REQ_IMEISV, /* trasnmit ID REQ(IMEISV) to MS */
+ EVT_VLR_AUTH_REJ, /* transmit AUTH REJ to MS */
+ EVT_VLR_SET_CIPH, /* transmit SET CIPH to MS */
+ EVT_VLR_LU_ACK, /* transmit LU ACK to MS */
+ EVT_VLR_LU_REJ, /* transmit LU REJ to MS */
+};
+
+static struct value_string f_event_names[] = {
+ { EVT_MS_TX_LU, "MS-TX-LU" },
+ { EVT_MS_TX_ID_RESP, "MS-TX-ID-RESP" },
+ { EVT_MS_TX_AUTH_RESP, "MS-TX-AUTH-RESP" },
+ { EVT_MS_TX_AUTH_FAIL, "MS-TX-AUTH-FAIL" },
+ { EVT_MS_CONN_LOST, "MS-CONN-LOST" },
+
+ { EVT_VLR_AUTH_REQ, "VLR-AUTH-REQ" },
+ { EVT_VLR_ID_REQ_IMSI, "VLR-ID-REQ-IMSI" },
+ { EVT_VLR_ID_REQ_IMEI, "VLR-ID-REQ-IMEI" },
+ { EVT_VLR_ID_REQ_IMEISV,"VLR-ID-REQ-IMEISV" },
+ { EVT_VLR_AUTH_REJ, "VLR-AUTH-REJ" },
+ { EVT_VLR_SET_CIPH, "VLR-SET-CIPH" },
+ { EVT_VLR_LU_ACK, "VLR-LU-ACK" },
+ { EVT_VLR_LU_REJ, "VLR-LU-REJ" },
+ { 0, NULL }
+};
+
+static void fsm_f_allstate(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct testvlr_priv *priv = fsi_priv(fi);
+ uint8_t mi[16];
+ unsigned int mi_len;
+
+ switch (event) {
+ case EVT_VLR_ID_REQ_IMSI:
+ if (priv->mode != MODE_SUCCESS_TMSI) {
+ LOGP(DGPRS, LOGL_NOTICE, "Unexpected ID REQ "
+ "(IMSI)\n");
+ }
+ mi_len = gsm48_generate_mid_from_imsi(mi, priv->imsi);
+ vlr_subscr_rx_id_resp(priv->subscr, mi+2, mi_len-2);
+ break;
+ case EVT_VLR_ID_REQ_IMEI:
+ mi_len = gsm48_generate_mid_from_imsi(mi, priv->imei);
+ mi[0] = (mi[0] & 0xf8) | GSM_MI_TYPE_IMEI;
+ vlr_subscr_rx_id_resp(priv->subscr+2, mi, mi_len-2);
+ break;
+ case EVT_VLR_ID_REQ_IMEISV:
+ mi_len = gsm48_generate_mid_from_imsi(mi, priv->imei);
+ mi[0] = (mi[0] & 0xf8) | GSM_MI_TYPE_IMEISV;
+ vlr_subscr_rx_id_resp(priv->subscr, mi+2, mi_len-2);
+ break;
+ case EVT_MS_CONN_LOST:
+ vlr_subscr_disconnected(priv->subscr);
+ /* IDEA: not release but keep around in extra state to
+ * see if VLR still sends us anything? */
+ osmo_fsm_inst_free(fi);
+ break;
+ }
+}
+
+static void fsm_f_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct testvlr_priv *priv = fsi_priv(fi);
+ uint32_t tmsi = 0;
+ const char *imsi = NULL;
+
+ switch (event) {
+ case EVT_MS_TX_LU:
+ /* send LU to VLR */
+ if (priv->mode == MODE_SUCCESS)
+ imsi = priv->imsi;
+ else
+ tmsi = priv->tmsi;
+ priv->lu_fsm = vlr_loc_update(fi,
+ EVT_VLR_LU_ACK,
+ EVT_VLR_LU_REJ,
+ NULL,
+ g_vlr, NULL,
+ VLR_LU_TYPE_IMSI_ATTACH, tmsi,
+ imsi, &priv->old_lai,
+ &priv->new_lai,
+ true,
+ true,
+ false,
+ false,
+ true);
+ OSMO_ASSERT(priv->subscr);
+ osmo_fsm_inst_state_chg(fi, ST_LU_SENT, 0, 0);
+ break;
+ default:
+ break;
+ }
+}
+
+static void fsm_f_lu_sent(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct gsm_auth_tuple *at = NULL;
+ struct testvlr_priv *priv = fsi_priv(fi);
+ uint8_t res_fail[4];
+ uint8_t auts[14];
+
+ switch (event) {
+ case EVT_VLR_AUTH_REQ:
+ at = data;
+ OSMO_ASSERT(at);
+ DEBUGP(DGPRS, "%s: at->res=%s\n", __func__, osmo_hexdump(at->vec.res, at->vec.res_len));
+ switch (priv->mode) {
+ case MODE_SUCCESS:
+ case MODE_SUCCESS_TMSI:
+ /* return matching SRES/AUTS */
+ vlr_subscr_rx_auth_resp(priv->subscr, true, false,
+ at->vec.res, at->vec.res_len);
+ break;
+ case MODE_AUTH_FAIL:
+ /* return not matching SRES/AUTS */
+ vlr_subscr_rx_auth_resp(priv->subscr, true, false,
+ res_fail, sizeof(res_fail));
+ /* FIXME: state transition? */
+ break;
+ case MODE_AUTH_RESYNC:
+ /* return SRES/AUTS requesting re-sync */
+ /* FIXME: generate a proper authenticating
+ * re-sync request */
+ vlr_subscr_rx_auth_fail(priv->subscr, auts);
+ /* FIXME: state transition? */
+ osmo_fsm_inst_state_chg(fi, ST_RESYNC_SENT, 0, 0);
+ break;
+ }
+ osmo_fsm_inst_state_chg(fi, ST_WAIT_LU_ACK, 0, 0);
+ break;
+ case EVT_VLR_LU_REJ:
+ {
+ uint8_t cause = *(uint8_t *)data;
+ LOGP(DGPRS, LOGL_NOTICE, "LU(%s): Rejected; cause=0x%02x\n",
+ priv->imsi, cause);
+
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+#if 0
+static void fsm_f_resync_sent(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct testvlr_priv *priv = fsi_priv(fi);
+ struct gsm_auth_tuple *at = NULL;
+
+ /* second auth request is supposed to succed after the
+ * re-sync procedure before */
+ switch (event) {
+ case EVT_VLR_AUTH_REQ:
+ at = data;
+ /* return matching SRES/AUTS now */
+ vlr_subscr_rx_auth_resp(priv->subscr, true, false,
+ at->vec.res, at->vec.res_len);
+ osmo_fsm_inst_state_chg(fi, ST_WAIT_LU_ACK, 0, 0);
+ break;
+ }
+}
+#endif
+
+static void fsm_f_wait_lu_ack(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ struct testvlr_priv *priv = fsi_priv(fi);
+
+ switch (event) {
+ case EVT_VLR_LU_ACK:
+ if (priv->subscr->tmsi != GSM_RESERVED_TMSI) {
+ /* we need to send an TMSI REALLOC COMPL */
+ vlr_subscr_rx_tmsi_reall_compl(priv->subscr);
+ }
+ osmo_fsm_inst_state_chg(fi, ST_DONE, 0, 0);
+ break;
+ case EVT_VLR_LU_REJ:
+ osmo_fsm_inst_state_chg(fi, ST_FAILED, 0, 0);
+ break;
+ }
+}
+
+static void fsm_f_imsi_sent(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ switch (event) {
+ case EVT_MS_TX_ID_RESP:
+ break;
+ }
+}
+
+#if 0
+static void fsm_f_areq_sent(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data)
+{
+ switch (event) {
+ case EVT_MS_TX_AUTH_RESP:
+ break;
+ case EVT_MS_TX_AUTH_FAIL:
+ break;
+ }
+}
+#endif
+
+static struct osmo_fsm_state fsm_success_states[] = {
+ [ST_NULL] = {
+ .in_event_mask = S(EVT_MS_TX_LU),
+ .out_state_mask = S(ST_LU_SENT),
+ .name = "NULL",
+ .action = fsm_f_null,
+ },
+ [ST_LU_SENT] = {
+ .in_event_mask = S(EVT_VLR_AUTH_REQ) |
+ S(EVT_VLR_LU_REJ),
+ //.out_state_mask = S(ST_IDREQ_IMSI_SENT) | S(ST_AUTH_REQ_SENT),
+ .out_state_mask = S(ST_WAIT_LU_ACK),
+ .name = "LU Sent",
+ .action = fsm_f_lu_sent,
+ },
+ [ST_RESYNC_SENT] = {
+ .in_event_mask = S(EVT_VLR_AUTH_REQ),
+ .out_state_mask = S(ST_WAIT_LU_ACK),
+ .name = "AUTH-RESYNC sent",
+ .action = fsm_f_imsi_sent,
+ },
+ [ST_WAIT_LU_ACK] = {
+ .in_event_mask = S(EVT_VLR_LU_ACK) |
+ S(EVT_VLR_SET_CIPH) |
+ S(EVT_VLR_LU_REJ),
+ .out_state_mask = S(ST_DONE),
+ .name = "WAIT-LU-ACK",
+ .action = fsm_f_wait_lu_ack,
+ },
+ [ST_DONE] = {
+ .name = "DONE"
+ },
+};
+
+static struct osmo_fsm vlr_test_fsm = {
+ .name = "VLR Test FSM",
+ .states = fsm_success_states,
+ .num_states = ARRAY_SIZE(fsm_success_states),
+ .log_subsys = DGPRS,
+ .event_names = f_event_names,
+ .allstate_event_mask = S(EVT_MS_CONN_LOST) |
+ S(EVT_VLR_ID_REQ_IMSI) |
+ S(EVT_VLR_ID_REQ_IMEI) |
+ S(EVT_VLR_ID_REQ_IMEISV),
+ .allstate_action = fsm_f_allstate,
+};
+
+/* Testing of Subscriber_Present_VLR */
+
+enum test_sub_pres_state {
+ TSPV_S_INIT,
+ TSPV_S_RUNNING,
+};
+
+enum test_sub_pres_evt {
+ TSPV_E_START,
+ TSPV_E_COMPL,
+};
+
+static void tspv_f_running(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct vlr_subscr *vsub = fi->priv;
+
+ switch (event) {
+ case TSPV_E_COMPL:
+ OSMO_ASSERT(vsub);
+ OSMO_ASSERT(vsub->ms_not_reachable_flag == false);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ break;
+ }
+}
+
+static void tspv_f_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct osmo_fsm_inst *spv;
+ struct vlr_subscr *vsub = fi->priv;
+
+ switch (event) {
+ case TSPV_E_START:
+ OSMO_ASSERT(vsub);
+ vsub->ms_not_reachable_flag = true;
+ spv = sub_pres_vlr_fsm_start(fi, vsub, TSPV_E_COMPL);
+ OSMO_ASSERT(spv);
+ osmo_fsm_inst_state_chg(fi, TSPV_S_RUNNING, 4, 0);
+ break;
+ }
+}
+
+static const struct osmo_fsm_state test_sub_pres_vlr_states[] = {
+ [TSPV_S_INIT] = {
+ .in_event_mask = S(TSPV_E_START),
+ .out_state_mask = S(TSPV_S_RUNNING),
+ .name = "INIT",
+ .action = tspv_f_init,
+ },
+ [TSPV_S_RUNNING] = {
+ .in_event_mask = S(TSPV_E_COMPL),
+ .out_state_mask = 0,
+ .name = "RUNNING",
+ .action = tspv_f_running,
+ },
+};
+
+static struct osmo_fsm test_sub_pres_vlr_fsm = {
+ .name = "Test Subscriber_Present_VLR",
+ .states = test_sub_pres_vlr_states,
+ .num_states = ARRAY_SIZE(test_sub_pres_vlr_states),
+ .log_subsys = DGPRS,
+ .event_names = f_event_names,
+ .timer_cb = timer_error_cb,
+};
+
+#if 0
+static void start_sub_pres_vlr(void *ctx, uint32_t tmsi, const char *imsi)
+{
+ struct osmo_fsm_inst *fi;
+ struct vlr_subscr *vsub = vlr_subscr_alloc(g_vlr);
+
+ vsub->tmsi = tmsi;
+ strncpy(vsub->imsi, imsi, sizeof(vsub->imsi));
+ fi = osmo_fsm_inst_alloc(&test_sub_pres_vlr_fsm, ctx, vsub, LOGL_DEBUG, vsub->imsi);
+ osmo_fsm_inst_dispatch(fi, TSPV_E_START, NULL);
+}
+#endif
+
+/* Testing of Update_HLR_VLR */
+
+enum test_update_hlr_vlr_state {
+ TUHV_S_INIT,
+ TUHV_S_RUNNING,
+};
+
+enum test_update_hlr_vlr_event {
+ TUHV_E_START,
+ TUHV_E_COMPL,
+};
+
+static void tuhv_f_running(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ enum gsm48_gmm_cause *res = data;
+
+ switch (event) {
+ case TUHV_E_COMPL:
+ if (!res) {
+ /* Success */
+ LOGPFSM(fi, "success\n");
+ } else {
+ /* error */
+ LOGPFSM(fi, "errror cause=0x%u\n", *res);
+ }
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ break;
+ }
+}
+
+static void tuhv_f_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct osmo_fsm_inst *child;
+ struct vlr_subscr *vsub = fi->priv;
+
+ switch (event) {
+ case TUHV_E_START:
+ child = upd_hlr_vlr_proc_start(fi, vsub, TUHV_E_COMPL);
+ OSMO_ASSERT(child);
+ osmo_fsm_inst_state_chg(fi, TUHV_S_RUNNING, 4, 0);
+ break;
+ }
+}
+
+static const struct osmo_fsm_state test_upd_hlr_vlr_states[] = {
+ [TUHV_S_INIT] = {
+ .in_event_mask = S(TUHV_E_START),
+ .out_state_mask = S(TUHV_S_RUNNING),
+ .name = "INIT",
+ .action = tuhv_f_init,
+ },
+ [TUHV_S_RUNNING] = {
+ .in_event_mask = S(TUHV_E_COMPL),
+ .out_state_mask = 0,
+ .name = "RUNNING",
+ .action = tuhv_f_running,
+ },
+};
+
+static struct osmo_fsm test_upd_hlr_vlr_fsm = {
+ .name = "Test Update_HLR_VLR",
+ .states = test_upd_hlr_vlr_states,
+ .num_states = ARRAY_SIZE(test_upd_hlr_vlr_states),
+ .log_subsys = DGPRS,
+ .event_names = f_event_names,
+ .timer_cb = timer_error_cb,
+};
+
+#if 0
+static void start_upd_hlr_vlr(void *ctx, uint32_t tmsi, const char *imsi)
+{
+ struct osmo_fsm_inst *fi;
+ struct vlr_subscr *vsub = vlr_subscr_alloc(g_vlr);
+
+ vsub->tmsi = tmsi;
+ strncpy(vsub->imsi, imsi, sizeof(vsub->imsi));
+
+
+ fi = osmo_fsm_inst_alloc(&test_upd_hlr_vlr_fsm, ctx, vsub, LOGL_DEBUG,
+ vsub->imsi);
+ /* we need to set this to fool vlr.c in an ongoing LU */
+ vsub->lu_fsm = fi;
+ osmo_fsm_inst_dispatch(fi, TUHV_E_START, NULL);
+}
+#endif
+
+/***********************************************************************
+ * Integration with VLR code
+ ***********************************************************************/
+
+static struct vlr_instance *g_vlr;
+
+/* VLR asks us to send an authentication request */
+static int msc_vlr_tx_auth_req(void *msc_conn_ref, struct gsm_auth_tuple *at,
+ bool send_autn)
+{
+ struct osmo_fsm_inst *fi = msc_conn_ref;
+ OSMO_ASSERT(at);
+ DEBUGP(DGPRS, "%s: RES=%s\n", __func__,
+ osmo_hexdump_nospc(at->vec.res, at->vec.res_len));
+ osmo_fsm_inst_dispatch(fi, EVT_VLR_AUTH_REQ, at);
+ return 0;
+}
+
+/* VLR asks us to send an authentication reject */
+static int msc_vlr_tx_auth_rej(void *msc_conn_ref)
+{
+ struct osmo_fsm_inst *fi = msc_conn_ref;
+ DEBUGP(DGPRS, "%s\n", __func__);
+ osmo_fsm_inst_dispatch(fi, EVT_VLR_AUTH_REJ, NULL);
+ return 0;
+}
+
+/* VLR asks us to transmit an Identity Request of given type */
+static int msc_vlr_tx_id_req(void *msc_conn_ref, uint8_t mi_type)
+{
+ struct osmo_fsm_inst *fi = msc_conn_ref;
+ uint32_t event;
+
+ DEBUGP(DGPRS, "%s (%u)\n", __func__, mi_type);
+
+ switch (mi_type) {
+ case GSM_MI_TYPE_IMSI:
+ event = EVT_VLR_ID_REQ_IMSI;
+ break;
+ case GSM_MI_TYPE_IMEI:
+ event = EVT_VLR_ID_REQ_IMEI;
+ break;
+ case GSM_MI_TYPE_IMEISV:
+ event = EVT_VLR_ID_REQ_IMEISV;
+ break;
+ default:
+ LOGP(DGPRS, LOGL_ERROR, "Unknown identity 0x%02x\n",
+ mi_type);
+ return -1;
+ }
+ osmo_fsm_inst_dispatch(fi, event, NULL);
+ return 0;
+}
+
+/* VLR asks us to transmit a Location Update Accept */
+static int msc_vlr_tx_lu_ack(void *msc_conn_ref, uint32_t send_tmsi)
+{
+ struct osmo_fsm_inst *fi = msc_conn_ref;
+ DEBUGP(DGPRS, "%s\n", __func__);
+ osmo_fsm_inst_dispatch(fi, EVT_VLR_LU_ACK, NULL);
+ return 0;
+}
+
+/* VLR asks us to transmit a Location Update Reject */
+static int msc_vlr_tx_lu_rej(void *msc_conn_ref, uint8_t cause)
+{
+ struct osmo_fsm_inst *fi = msc_conn_ref;
+ DEBUGP(DGPRS, "%s\n", __func__);
+ osmo_fsm_inst_dispatch(fi, EVT_VLR_LU_REJ, (void *) &cause);
+ return 0;
+}
+
+static int msc_vlr_set_ciph_mode(void *msc_conn_ref, enum vlr_ciph mode,
+ bool retrieve_imeisv)
+{
+ struct osmo_fsm_inst *fi = msc_conn_ref;
+ DEBUGP(DGPRS, "%s\n", __func__);
+ osmo_fsm_inst_dispatch(fi, EVT_VLR_SET_CIPH, NULL);
+ return 0;
+}
+
+/* VLR informs us that the subscriber data has somehow been modified */
+static void msc_vlr_subscr_update(struct vlr_subscr *subscr)
+{
+ DEBUGP(DGPRS, "%s\n", __func__);
+ /* FIXME */
+}
+
+static void msc_vlr_subscr_assoc(void *msc_conn_ref, struct vlr_subscr *vsub)
+{
+ struct osmo_fsm_inst *fi = msc_conn_ref;
+ struct testvlr_priv *priv = fsi_priv(fi);
+ DEBUGP(DGPRS, "%s(%p, %s)\n", __func__, msc_conn_ref, vlr_subscr_name(vsub));
+ priv->subscr = vsub;
+}
+
+/* operations that we need to implement for libvlr */
+static const struct vlr_ops test_vlr_ops = {
+ .tx_auth_req = msc_vlr_tx_auth_req,
+ .tx_auth_rej = msc_vlr_tx_auth_rej,
+ .tx_id_req = msc_vlr_tx_id_req,
+ .tx_lu_acc = msc_vlr_tx_lu_ack,
+ .tx_lu_rej = msc_vlr_tx_lu_rej,
+ .set_ciph_mode = msc_vlr_set_ciph_mode,
+ .subscr_update = msc_vlr_subscr_update,
+ .subscr_assoc = msc_vlr_subscr_assoc,
+};
+
+/***********************************************************************
+ * Actual test cases
+ ***********************************************************************/
+
+
+static struct osmo_fsm_inst *
+start_lu(enum testvlr_mode mode, uint32_t tmsi,
+ const char *imsi, const char *imei)
+{
+ struct testvlr_priv *vp;
+ struct osmo_fsm_inst *fi;
+
+ vp = talloc_zero(tall_bsc_ctx, struct testvlr_priv);
+ vp->mode = mode;
+ vp->tmsi = tmsi;
+ strncpy(vp->imsi, imsi, sizeof(vp->imsi));
+ strncpy(vp->imei, imei, sizeof(vp->imei));
+
+ fi = osmo_fsm_inst_alloc(&vlr_test_fsm, vp, vp, LOGL_DEBUG, vp->imsi);
+ osmo_fsm_inst_dispatch(fi, EVT_MS_TX_LU, NULL);
+ return fi;
+}
+
+/***********************************************************************
+ * Main / Misc
+ ***********************************************************************/
+
+static struct osmo_timer_list tmr;
+
+static void timer_cb(void *data)
+{
+ uint32_t tmsi = rand() % 1000000;
+ uint64_t imsi = 901790000000000 + tmsi;
+ char imsi_str[32];
+
+ snprintf(imsi_str, sizeof(imsi_str), "%lu", imsi);
+ //start_lu(MODE_AUTH_FAIL, tmsi, imsi_str, "23422342");
+ start_lu(MODE_SUCCESS_TMSI, tmsi, imsi_str, "23422342");
+ //start_lu(MODE_SUCCESS, tmsi, imsi_str, "23422342");
+ //start_upd_hlr_vlr(tall_bsc_ctx, tmsi, imsi_str);
+ //start_sub_pres_vlr(tall_bsc_ctx);
+ osmo_timer_schedule(&tmr, 8, 0);
+}
+
+static void sighdlr(int sig)
+{
+ switch (sig) {
+ case SIGUSR1:
+ talloc_report_full(tall_bsc_ctx, stderr);
+ break;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ tall_bsc_ctx = talloc_named_const(NULL, 1, "tall_bsc_ctx");
+
+ signal(SIGUSR1, sighdlr);
+
+ osmo_init_logging(&log_info);
+
+ g_vlr = vlr_alloc(NULL, &test_vlr_ops);
+ vlr_start("VLRTEST", g_vlr, "localhost", 2222);
+ OSMO_ASSERT(g_vlr);
+ osmo_fsm_register(&vlr_test_fsm);
+ osmo_fsm_register(&test_sub_pres_vlr_fsm);
+ osmo_fsm_register(&test_upd_hlr_vlr_fsm);
+
+ g_vlr->cfg.assign_tmsi = true;
+
+ tmr.cb = timer_cb;
+ timer_cb(NULL);
+
+ while (1) {
+ osmo_select_main(0);
+ }
+
+ exit(0);
+}
+
+struct gsm_subscriber_connection;
+int gsm48_tx_mm_serv_ack(struct gsm_subscriber_connection *conn) { return 0; }