1489 lines
42 KiB
C
1489 lines
42 KiB
C
/* 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/gsm48.h>
|
|
#include <osmocom/msc/vlr.h>
|
|
#include <osmocom/msc/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 inline struct vlr_subscr *upd_hlr_vlr_fi_priv(struct osmo_fsm_inst *fi);
|
|
|
|
static void upd_hlr_vlr_fsm_init(struct osmo_fsm_inst *fi, uint32_t event,
|
|
void *data)
|
|
{
|
|
struct vlr_subscr *vsub = upd_hlr_vlr_fi_priv(fi);
|
|
int rc;
|
|
|
|
OSMO_ASSERT(event == UPD_HLR_VLR_E_START);
|
|
|
|
/* Send UpdateLocation to HLR */
|
|
rc = vlr_subscr_req_lu(vsub, vsub->vlr->cfg.is_ps);
|
|
if (rc < 0)
|
|
LOGPFSML(fi, LOGL_ERROR, "Failed to send UpdateLocation to HLR\n");
|
|
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 = upd_hlr_vlr_fi_priv(fi);
|
|
|
|
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,
|
|
};
|
|
|
|
static inline struct vlr_subscr *upd_hlr_vlr_fi_priv(struct osmo_fsm_inst *fi)
|
|
{
|
|
OSMO_ASSERT(fi->fsm == &upd_hlr_vlr_fsm);
|
|
return (struct vlr_subscr*)fi->priv;
|
|
}
|
|
|
|
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 inline struct vlr_subscr *sub_pres_vlr_fi_priv(struct osmo_fsm_inst *fi);
|
|
|
|
static void sub_pres_vlr_fsm_init(struct osmo_fsm_inst *fi, uint32_t event,
|
|
void *data)
|
|
{
|
|
struct vlr_subscr *vsub = sub_pres_vlr_fi_priv(fi);
|
|
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 = sub_pres_vlr_fi_priv(fi);
|
|
|
|
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,
|
|
};
|
|
|
|
static inline struct vlr_subscr *sub_pres_vlr_fi_priv(struct osmo_fsm_inst *fi)
|
|
{
|
|
OSMO_ASSERT(fi->fsm == &sub_pres_vlr_fsm);
|
|
return (struct vlr_subscr*)fi->priv;
|
|
}
|
|
|
|
/* Note that the start event is dispatched right away, so in case the FSM immediately concludes from that
|
|
* event, the created FSM struct may no longer be valid as it already deallocated again, and it may
|
|
* furthermore already have invoked the parent FSM instance's deallocation as well. Hence, instead of
|
|
* returning, store the created FSM instance address in *fi_p before dispatching the event. It is thus
|
|
* possible to store the instance's pointer in a parent FSM instance without running danger of using
|
|
* already freed memory. */
|
|
void sub_pres_vlr_fsm_start(struct osmo_fsm_inst **fi_p,
|
|
struct osmo_fsm_inst *parent,
|
|
struct vlr_subscr *vsub,
|
|
uint32_t term_event)
|
|
{
|
|
struct osmo_fsm_inst *fi;
|
|
|
|
OSMO_ASSERT(fi_p);
|
|
|
|
fi = osmo_fsm_inst_alloc_child(&sub_pres_vlr_fsm, parent,
|
|
term_event);
|
|
*fi_p = fi;
|
|
if (!fi)
|
|
return;
|
|
|
|
fi->priv = vsub;
|
|
osmo_fsm_inst_dispatch(fi, SUB_PRES_VLR_E_START, NULL);
|
|
}
|
|
|
|
/***********************************************************************
|
|
* 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 inline struct lu_compl_vlr_priv *lu_compl_vlr_fi_priv(struct osmo_fsm_inst *fi);
|
|
|
|
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 = lu_compl_vlr_fi_priv(fi);
|
|
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 = lu_compl_vlr_fi_priv(fi);
|
|
struct vlr_subscr *vsub = lcvp->vsub;
|
|
if (!vsub->lu_complete) {
|
|
vsub->lu_complete = true;
|
|
/* Balanced by vlr_subscr_expire() */
|
|
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 = lu_compl_vlr_fi_priv(fi);
|
|
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 = lu_compl_vlr_fi_priv(fi);
|
|
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 = lu_compl_vlr_fi_priv(fi);
|
|
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);
|
|
|
|
sub_pres_vlr_fsm_start(&lcvp->sub_pres_vlr_fsm, 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 = lu_compl_vlr_fi_priv(fi);
|
|
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 = lu_compl_vlr_fi_priv(fi);
|
|
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 = lu_compl_vlr_fi_priv(fi);
|
|
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);
|
|
}
|
|
|
|
static inline struct lu_compl_vlr_priv *lu_compl_vlr_fi_priv(struct osmo_fsm_inst *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 = lu_compl_vlr_fi_priv(fi);
|
|
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,
|
|
};
|
|
|
|
static inline struct lu_compl_vlr_priv *lu_compl_vlr_fi_priv(struct osmo_fsm_inst *fi)
|
|
{
|
|
OSMO_ASSERT(fi->fsm == &lu_compl_vlr_fsm);
|
|
return (struct lu_compl_vlr_priv*)fi->priv;
|
|
}
|
|
|
|
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;
|
|
bool 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;
|
|
}
|
|
|
|
/* Determine if ciphering is required */
|
|
static bool is_ciph_required(struct lu_fsm_priv *lfp)
|
|
{
|
|
return lfp->ciphering_required;
|
|
}
|
|
|
|
/* 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 inline struct lu_fsm_priv *lu_fsm_fi_priv(struct osmo_fsm_inst *fi);
|
|
|
|
static void lu_fsm_dispatch_result(struct osmo_fsm_inst *fi,
|
|
uint32_t prev_state)
|
|
{
|
|
struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
|
|
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 = lu_fsm_fi_priv(fi);
|
|
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, enum gsm48_reject_value rej_cause)
|
|
{
|
|
struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
|
|
lfp->vlr->ops.tx_lu_rej(lfp->msc_conn_ref, rej_cause ? : GSM48_REJECT_NETWORK_FAILURE);
|
|
_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 = lu_fsm_fi_priv(fi);
|
|
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 = lu_fsm_fi_priv(fi);
|
|
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 = lu_fsm_fi_priv(fi);
|
|
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__);
|
|
|
|
/* OsmoHLR does not support PgA, neither stores the IMEISV, so we have no need to update the HLR
|
|
* with either. TODO: depend on actual HLR configuration. See 3GPP TS 23.012 Release 14, process
|
|
* Update_Location_Area_VLR (ULA_VLR2). */
|
|
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 = lu_fsm_fi_priv(fi);
|
|
struct vlr_subscr *vsub = lfp->vsub;
|
|
|
|
LOGPFSM(fi, "%s()\n", __func__);
|
|
|
|
OSMO_ASSERT(vsub);
|
|
|
|
if (lfp->is_utran) {
|
|
int rc;
|
|
rc = lfp->vlr->ops.tx_common_id(lfp->msc_conn_ref);
|
|
if (rc)
|
|
LOGPFSML(fi, LOGL_ERROR,
|
|
"Error while sending Common ID (%d)\n", rc);
|
|
}
|
|
|
|
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 = lu_fsm_fi_priv(fi);
|
|
struct vlr_subscr *vsub = lfp->vsub;
|
|
bool umts_aka;
|
|
|
|
LOGPFSM(fi, "%s()\n", __func__);
|
|
|
|
OSMO_ASSERT(vsub);
|
|
|
|
if (!is_ciph_required(lfp)) {
|
|
vlr_loc_upd_post_ciph(fi);
|
|
return;
|
|
}
|
|
|
|
if (!vsub->last_tuple) {
|
|
LOGPFSML(fi, LOGL_ERROR, "No auth tuple available\n");
|
|
lu_fsm_failure(fi, GSM48_REJECT_NETWORK_FAILURE);
|
|
return;
|
|
}
|
|
|
|
switch (vsub->sec_ctx) {
|
|
case VLR_SEC_CTX_GSM:
|
|
umts_aka = false;
|
|
break;
|
|
case VLR_SEC_CTX_UMTS:
|
|
umts_aka = true;
|
|
break;
|
|
default:
|
|
LOGPFSML(fi, LOGL_ERROR, "Cannot start ciphering, security context is not established\n");
|
|
lu_fsm_failure(fi, GSM48_REJECT_NETWORK_FAILURE);
|
|
return;
|
|
}
|
|
|
|
if (vlr_set_ciph_mode(vsub->vlr, fi, lfp->msc_conn_ref,
|
|
lfp->ciphering_required,
|
|
umts_aka,
|
|
vsub->vlr->cfg.retrieve_imeisv_ciphered)) {
|
|
LOGPFSML(fi, LOGL_ERROR,
|
|
"Failed to send Ciphering Mode Command\n");
|
|
lu_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 = lu_fsm_fi_priv(fi);
|
|
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 = lu_fsm_fi_priv(fi);
|
|
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 = lu_fsm_fi_priv(fi);
|
|
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 int _lu_fsm_associate_vsub(struct osmo_fsm_inst *fi)
|
|
{
|
|
struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
|
|
struct vlr_instance *vlr = lfp->vlr;
|
|
struct vlr_subscr *vsub = NULL;
|
|
|
|
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, NULL);
|
|
|
|
if (!vsub) {
|
|
LOGPFSML(fi, LOGL_ERROR, "VLR subscriber allocation failed\n");
|
|
lu_fsm_failure(fi, GSM48_REJECT_SRV_OPT_TMP_OUT_OF_ORDER);
|
|
return -1;
|
|
}
|
|
|
|
vsub->sub_dataconf_by_hlr_ind = false;
|
|
if (assoc_lfp_with_sub(fi, vsub)) {
|
|
vlr_subscr_put(vsub);
|
|
return -1; /* error, fsm failure invoked in assoc_lfp_with_sub() */
|
|
}
|
|
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 -1;
|
|
}
|
|
|
|
vsub->sub_dataconf_by_hlr_ind = false;
|
|
if (assoc_lfp_with_sub(fi, vsub)) {
|
|
vlr_subscr_put(vsub);
|
|
return -1; /* error, fsm failure invoked in assoc_lfp_with_sub() */
|
|
}
|
|
vlr_subscr_put(vsub);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* 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 = lu_fsm_fi_priv(fi);
|
|
struct vlr_instance *vlr = lfp->vlr;
|
|
|
|
/* 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",
|
|
osmo_lai_name(&lfp->old_lai));
|
|
}
|
|
|
|
/* If this is a TMSI based LU, we may not have the IMSI. Make sure that
|
|
* we know the IMSI, either on record, or request it. */
|
|
if (!lfp->vsub->imsi[0])
|
|
vlr_loc_upd_want_imsi(fi);
|
|
else
|
|
vlr_loc_upd_node1(fi);
|
|
}
|
|
|
|
static void lu_fsm_idle(struct osmo_fsm_inst *fi, uint32_t event,
|
|
void *data)
|
|
{
|
|
struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
|
|
struct vlr_instance *vlr = lfp->vlr;
|
|
|
|
OSMO_ASSERT(event == VLR_ULA_E_UPDATE_LA);
|
|
|
|
if (_lu_fsm_associate_vsub(fi))
|
|
return; /* error. FSM already terminated. */
|
|
|
|
OSMO_ASSERT(lfp->vsub);
|
|
|
|
/* See 3GPP TS 23.012, procedure Retrieve_IMEISV_If_Required */
|
|
if ((!vlr->cfg.retrieve_imeisv_early)
|
|
|| (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:
|
|
/* IMEISV was copied in vlr_subscr_rx_id_resp(), and that's
|
|
* where we received this event from. */
|
|
_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 = lu_fsm_fi_priv(fi);
|
|
enum gsm48_reject_value *res = data;
|
|
|
|
OSMO_ASSERT(event == VLR_ULA_E_AUTH_RES);
|
|
|
|
lfp->upd_hlr_vlr_fsm = NULL;
|
|
|
|
if (!res || *res) {
|
|
lu_fsm_failure(fi, res? *res : GSM48_REJECT_NETWORK_FAILURE);
|
|
return;
|
|
}
|
|
|
|
/* Result == Pass */
|
|
vlr_loc_upd_post_auth(fi);
|
|
}
|
|
|
|
static void lu_fsm_wait_ciph(struct osmo_fsm_inst *fi, uint32_t event,
|
|
void *data)
|
|
{
|
|
struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi);
|
|
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 = lu_fsm_fi_priv(fi);
|
|
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 = lu_fsm_fi_priv(fi);
|
|
|
|
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;
|
|
/* Ignoring standalone mode for now. */
|
|
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 = lu_fsm_fi_priv(fi);
|
|
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 */
|
|
|
|
if (lfp->type == VLR_LU_TYPE_IMSI_ATTACH)
|
|
lfp->vlr->ops.tx_mm_info(lfp->msc_conn_ref);
|
|
|
|
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 = lu_fsm_fi_priv(fi);
|
|
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_WAIT_AUTH) |
|
|
S(VLR_ULA_S_WAIT_HLR_UPD) |
|
|
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 = lu_fsm_fi_priv(fi);
|
|
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,
|
|
};
|
|
|
|
static inline struct lu_fsm_priv *lu_fsm_fi_priv(struct osmo_fsm_inst *fi)
|
|
{
|
|
OSMO_ASSERT(fi->fsm == &vlr_lu_fsm);
|
|
return (struct lu_fsm_priv*)fi->priv;
|
|
}
|
|
|
|
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,
|
|
bool 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)")
|
|
: "");
|
|
|
|
if (is_utran && !authentication_required)
|
|
LOGPFSML(fi, LOGL_ERROR,
|
|
"Authentication off on UTRAN network. Good luck.\n");
|
|
|
|
osmo_fsm_inst_dispatch(fi, VLR_ULA_E_UPDATE_LA, NULL);
|
|
|
|
return fi;
|
|
}
|
|
|
|
void vlr_loc_update_cancel(struct osmo_fsm_inst *fi,
|
|
enum osmo_fsm_term_cause fsm_cause,
|
|
uint8_t gsm48_cause)
|
|
{
|
|
struct lu_fsm_priv *lfp;
|
|
|
|
OSMO_ASSERT(fi);
|
|
OSMO_ASSERT(fi->fsm == &vlr_lu_fsm);
|
|
|
|
lfp = fi->priv;
|
|
lfp->rej_cause = gsm48_cause;
|
|
|
|
if (fi->state != VLR_ULA_S_DONE)
|
|
lu_fsm_failure(fi, gsm48_cause);
|
|
}
|
|
|
|
void vlr_lu_fsm_init(void)
|
|
{
|
|
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);
|
|
}
|