diff options
Diffstat (limited to 'src/libvlr/vlr_lu_fsm.c')
-rw-r--r-- | src/libvlr/vlr_lu_fsm.c | 1424 |
1 files changed, 1424 insertions, 0 deletions
diff --git a/src/libvlr/vlr_lu_fsm.c b/src/libvlr/vlr_lu_fsm.c new file mode 100644 index 000000000..d32659f56 --- /dev/null +++ b/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); +} |