aboutsummaryrefslogtreecommitdiffstats
path: root/src/libmsc/msc_ho.c
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2018-12-07 14:47:34 +0100
committerNeels Hofmeyr <neels@hofmeyr.de>2019-05-08 17:02:32 +0200
commitc4628a3ad4d3c5f65782b152b771bf80357235d6 (patch)
tree8d6e85e33bb1e821ad9dae5b1701cb65f1d0414c /src/libmsc/msc_ho.c
parent56f90132b8d7d6a40cc1665b34ff35c62becb2f0 (diff)
large refactoring: support inter-BSC and inter-MSC Handover
3GPP TS 49.008 '4.3 Roles of MSC-A, MSC-I and MSC-T' defines distinct roles: - MSC-A is responsible for managing subscribers, - MSC-I is the gateway to the RAN. - MSC-T is a second transitory gateway to another RAN during Handover. After inter-MSC Handover, the MSC-I is handled by a remote MSC instance, while the original MSC-A retains the responsibility of subscriber management. MSC-T exists in this patch but is not yet used, since Handover is only prepared for, not yet implemented. Facilitate Inter-MSC and inter-BSC Handover by the same internal split of MSC roles. Compared to inter-MSC Handover, mere inter-BSC has the obvious simplifications: - all of MSC-A, MSC-I and MSC-T roles will be served by the same osmo-msc instance, - messages between MSC-A and MSC-{I,T} don't need to be routed via E-interface (GSUP), - no call routing between MSC-A and -I via MNCC necessary. This is the largest code bomb I have submitted, ever. Out of principle, I apologize to everyone trying to read this as a whole. Unfortunately, I see no sense in trying to split this patch into smaller bits. It would be a huge amount of work to introduce these changes in separate chunks, especially if each should in turn be useful and pass all test suites. So, unfortunately, we are stuck with this code bomb. The following are some details and rationale for this rather huge refactoring: * separate MSC subscriber management from ran_conn struct ran_conn is reduced from the pivotal subscriber management entity it has been so far to a mere storage for an SCCP connection ID and an MSC subscriber reference. The new pivotal subscriber management entity is struct msc_a -- struct msub lists the msc_a, msc_i, msc_t roles, the vast majority of code paths however use msc_a, since MSC-A is where all the interesting stuff happens. Before handover, msc_i is an FSM implementation that encodes to the local ran_conn. After inter-MSC Handover, msc_i is a compatible but different FSM implementation that instead forwards via/from GSUP. Same goes for the msc_a struct: if osmo-msc is the MSC-I "RAN proxy" for a remote MSC-A role, the msc_a->fi is an FSM implementation that merely forwards via/from GSUP. * New SCCP implementation for RAN access To be able to forward BSSAP and RANAP messages via the GSUP interface, the individual message layers need to be cleanly separated. The IuCS implementation used until now (iu_client from libosmo-ranap) did not provide this level of separation, and needed a complete rewrite. It was trivial to implement this in such a way that both BSSAP and RANAP can be handled by the same SCCP code, hence the new SCCP-RAN layer also replaces BSSAP handling. sccp_ran.h: struct sccp_ran_inst provides an abstract handler for incoming RAN connections. A set of callback functions provides implementation specific details. * RAN Abstraction (BSSAP vs. RANAP) The common SCCP implementation did set the theme for the remaining refactoring: make all other MSC code paths entirely RAN-implementation-agnostic. ran_infra.c provides data structures that list RAN implementation specifics, from logging to RAN de-/encoding to SCCP callbacks and timers. A ran_infra pointer hence allows complete abstraction of RAN implementations: - managing connected RAN peers (BSC, RNC) in ran_peer.c, - classifying and de-/encoding RAN PDUs, - recording connected LACs and cell IDs and sending out Paging requests to matching RAN peers. * RAN RESET now also for RANAP ran_peer.c absorbs the reset_fsm from a_reset.c; in consequence, RANAP also supports proper RESET semantics now. Hence osmo-hnbgw now also needs to provide proper RESET handling, which it so far duly ignores. (TODO) * RAN de-/encoding abstraction The RAN abstraction mentioned above serves not only to separate RANAP and BSSAP implementations transparently, but also to be able to optionally handle RAN on distinct levels. Before Handover, all RAN messages are handled by the MSC-A role. However, after an inter-MSC Handover, a standalone MSC-I will need to decode RAN PDUs, at least in order to manage Assignment of RTP streams between BSS/RNC and MNCC call forwarding. ran_msg.h provides a common API with abstraction for: - receiving events from RAN, i.e. passing RAN decode from the BSC/RNC and MS/UE: struct ran_dec_msg represents RAN messages decoded from either BSSMAP or RANAP; - sending RAN events: ran_enc_msg is the counterpart to compose RAN messages that should be encoded to either BSSMAP or RANAP and passed down to the BSC/RNC and MS/UE. The RAN-specific implementations are completely contained by ran_msg_a.c and ran_msg_iu.c. In particular, Assignment and Ciphering have so far been distinct code paths for BSSAP and RANAP, with switch(via_ran){...} statements all over the place. Using RAN_DEC_* and RAN_ENC_* abstractions, these are now completely unified. Note that SGs does not qualify for RAN abstraction: the SGs interface always remains with the MSC-A role, and SGs messages follow quite distinct semantics from the fairly similar GERAN and UTRAN. * MGW and RTP stream management So far, managing MGW endpoints via MGCP was tightly glued in-between GSM-04.08-CC on the one and MNCC on the other side. Prepare for switching RTP streams between different RAN peers by moving to object-oriented implementations: implement struct call_leg and struct rtp_stream with distinct FSMs each. For MGW communication, use the osmo_mgcpc_ep API that has originated from osmo-bsc and recently moved to libosmo-mgcp-client for this purpose. Instead of implementing a sequence of events with code duplication for the RAN and CN sides, the idea is to manage each RTP stream separately by firing and receiving events as soon as codecs and RTP ports are negotiated, and letting the individual FSMs take care of the MGW management "asynchronously". The caller provides event IDs and an FSM instance that should be notified of RTP stream setup progress. Hence it becomes possible to reconnect RTP streams from one GSM-04.08-CC to another (inter-BSC Handover) or between CC and MNCC RTP peers (inter-MSC Handover) without duplicating the MGCP code for each transition. The number of FSM implementations used for MGCP handling may seem a bit of an overkill. But in fact, the number of perspectives on RTP forwarding are far from trivial: - an MGW endpoint is an entity with N connections, and MGCP "sessions" for configuring them by talking to the MGW; - an RTP stream is a remote peer connected to one of the endpoint's connections, which is asynchronously notified of codec and RTP port choices; - a call leg is the higher level view on either an MT or MO side of a voice call, a combination of two RTP streams to forward between two remote peers. BSC MGW PBX CI CI [MGW-endpoint] [--rtp_stream--] [--rtp_stream--] [----------------call_leg----------------] * Use counts Introduce using the new osmo_use_count API added to libosmocore for this purpose. Each use token has a distinct name in the logging, which can be a globally constant name or ad-hoc, like the local __func__ string constant. Use in the new struct msc_a, as well as change vlr_subscr to the new osmo_use_count API. * FSM Timeouts Introduce using the new osmo_tdef API, which provides a common VTY implementation for all timer numbers, and FSM state transitions with the correct timeout. Originated in osmo-bsc, recently moved to libosmocore. Depends: Ife31e6798b4e728a23913179e346552a7dd338c0 (libosmocore) Ib9af67b100c4583342a2103669732dab2e577b04 (libosmocore) Id617265337f09dfb6ddfe111ef5e578cd3dc9f63 (libosmocore) Ie9e2add7bbfae651c04e230d62e37cebeb91b0f5 (libosmo-sccp) I26be5c4b06a680f25f19797407ab56a5a4880ddc (osmo-mgw) Ida0e59f9a1f2dd18efea0a51680a67b69f141efa (osmo-mgw) I9a3effd38e72841529df6c135c077116981dea36 (osmo-mgw) Change-Id: I27e4988e0371808b512c757d2b52ada1615067bd
Diffstat (limited to 'src/libmsc/msc_ho.c')
-rw-r--r--src/libmsc/msc_ho.c879
1 files changed, 879 insertions, 0 deletions
diff --git a/src/libmsc/msc_ho.c b/src/libmsc/msc_ho.c
new file mode 100644
index 000000000..9d130c57c
--- /dev/null
+++ b/src/libmsc/msc_ho.c
@@ -0,0 +1,879 @@
+/* MSC Handover implementation */
+/*
+ * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/sigtran/sccp_helpers.h>
+
+#include <osmocom/msc/msc_ho.h>
+#include <osmocom/msc/ran_msg.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/msc_i.h>
+#include <osmocom/msc/msc_t.h>
+#include <osmocom/msc/e_link.h>
+#include <osmocom/msc/msc_i_remote.h>
+#include <osmocom/msc/msc_t_remote.h>
+#include <osmocom/msc/neighbor_ident.h>
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/msc/ran_peer.h>
+#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/transaction.h>
+#include <osmocom/msc/gsm_04_08.h>
+#include <osmocom/msc/call_leg.h>
+#include <osmocom/msc/rtp_stream.h>
+#include <osmocom/msc/mncc_call.h>
+
+struct osmo_fsm msc_ho_fsm;
+
+#define MSC_A_USE_HANDOVER "Handover"
+
+static const struct osmo_tdef_state_timeout msc_ho_fsm_timeouts[32] = {
+ [MSC_HO_ST_REQUIRED] = { .keep_timer = true, .T = -3 },
+ [MSC_HO_ST_WAIT_REQUEST_ACK] = { .keep_timer = true },
+ [MSC_HO_ST_WAIT_COMPLETE] = { .T = -3 },
+};
+
+/* Transition to a state, using the T timer defined in msc_a_fsm_timeouts.
+ * The actual timeout value is in turn obtained from network->T_defs.
+ * Assumes local variable fi exists. */
+#define msc_ho_fsm_state_chg(msc_a, state) \
+ osmo_tdef_fsm_inst_state_chg((msc_a)->ho.fi, state, msc_ho_fsm_timeouts, (msc_a)->c.ran->tdefs, 5)
+
+static __attribute__((constructor)) void msc_ho_fsm_init()
+{
+ osmo_fsm_register(&msc_ho_fsm);
+}
+
+void msc_ho_down_required_reject(struct msc_a *msc_a, enum gsm0808_cause cause)
+{
+ struct msc_i *msc_i = msc_a_msc_i(msc_a);
+ uint32_t event;
+
+ struct ran_msg ran_enc_msg = {
+ .msg_type = RAN_MSG_HANDOVER_REQUIRED_REJECT,
+ .handover_required_reject = {
+ .cause = cause,
+ },
+ };
+
+ if (msc_i->c.remote_to)
+ event = MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_ERROR;
+ else
+ event = MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST;
+
+ msc_a_msg_down(msc_a, MSC_ROLE_I, event, &ran_enc_msg);
+}
+
+/* Even though this is using the 3GPP TS 48.008 definitions and naming, the intention is to be RAN implementation agnostic.
+ * For other RAN types, the 48.008 items shall be translated to their respective counterparts. */
+void msc_ho_start(struct msc_a *msc_a, const struct ran_handover_required *ho_req)
+{
+ if (msc_a->ho.fi) {
+ LOG_HO(msc_a, LOGL_ERROR, "Rx Handover Required, but Handover is still ongoing\n");
+ msc_ho_down_required_reject(msc_a, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC);
+ return;
+ }
+
+ if (!ho_req->cil.id_list_len) {
+ LOG_HO(msc_a, LOGL_ERROR, "Rx Handover Required without a Cell Identifier List\n");
+ msc_ho_down_required_reject(msc_a, GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING);
+ return;
+ }
+
+ if (msc_a_msc_t(msc_a)) {
+ LOG_HO(msc_a, LOGL_ERROR,
+ "Rx Handover Required, but this subscriber still has an active MSC-T role: %s\n",
+ msc_a_msc_t(msc_a)->c.fi->id);
+ /* Protocol error because the BSS is not supposed to send another Handover Required before the previous
+ * attempt has concluded. */
+ msc_ho_down_required_reject(msc_a, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC);
+ return;
+ }
+
+ /* Paranoia: make sure we start with clean state */
+ msc_a->ho = (struct msc_ho_state){};
+
+ msc_a->ho.fi = osmo_fsm_inst_alloc_child(&msc_ho_fsm, msc_a->c.fi, MSC_A_EV_HANDOVER_END);
+ OSMO_ASSERT(msc_a->ho.fi);
+
+ msc_a->ho.fi->priv = msc_a;
+ msc_a->ho.info = *ho_req;
+ msc_a->ho.next_cil_idx = 0;
+
+ /* Start the timeout */
+ msc_ho_fsm_state_chg(msc_a, MSC_HO_ST_REQUIRED);
+}
+
+static void msc_ho_rtp_rollback_to_old_cell(struct msc_a *msc_a);
+
+static void msc_ho_end(struct msc_a *msc_a, bool success, enum gsm0808_cause cause)
+{
+ struct msc_i *msc_i;
+ struct msc_t *msc_t = msc_a_msc_t(msc_a);
+
+ if (!success) {
+ msc_ho_rtp_rollback_to_old_cell(msc_a);
+ msc_ho_down_required_reject(msc_a, cause);
+ }
+
+ if (success) {
+ /* Any previous call forwarding to a remote MSC becomes obsolete. */
+ if (msc_a->cc.mncc_forwarding_to_remote_ran) {
+ mncc_call_release(msc_a->cc.mncc_forwarding_to_remote_ran);
+ msc_a->cc.mncc_forwarding_to_remote_ran = NULL;
+ }
+
+ /* Replace MSC-I with new MSC-T */
+ if (msc_t->c.remote_to) {
+ /* Inter-MSC Handover. */
+
+ /* The MNCC forwarding set up for inter-MSC handover, so far transitional in msc_a->ho now
+ * becomes the "officially" active MNCC forwarding for this call. */
+ msc_a->cc.mncc_forwarding_to_remote_ran = msc_a->ho.new_cell.mncc_forwarding_to_remote_ran;
+ msc_a->ho.new_cell.mncc_forwarding_to_remote_ran = NULL;
+ mncc_call_reparent(msc_a->cc.mncc_forwarding_to_remote_ran,
+ msc_a->c.fi, -1, MSC_MNCC_EV_CALL_ENDED, NULL, NULL);
+
+ /* inter-MSC link. msc_i_remote_alloc() properly "steals" the e_link from msc_t. */
+ msc_i = msc_i_remote_alloc(msc_a->c.msub, msc_t->c.ran, msc_t->c.remote_to);
+ OSMO_ASSERT(msc_t->c.remote_to == NULL);
+ } else {
+ /* local BSS */
+ msc_i = msc_i_alloc(msc_a->c.msub, msc_t->c.ran);
+ /* msc_i_set_ran_conn() properly "steals" the ran_conn from msc_t */
+ msc_i_set_ran_conn(msc_i, msc_t->ran_conn);
+ }
+ }
+
+ osmo_fsm_inst_term(msc_a->ho.fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+#define msc_ho_failed(msc_a, cause, fmt, args...) do { \
+ LOG_HO(msc_a, LOGL_ERROR, fmt, ##args); \
+ msc_ho_end(msc_a, false, cause); \
+ } while(0)
+#define msc_ho_try_next_cell(msc_a, fmt, args...) do {\
+ LOG_HO(msc_a, LOGL_ERROR, fmt, ##args); \
+ msc_ho_fsm_state_chg(msc_a, MSC_HO_ST_REQUIRED); \
+ } while(0)
+#define msc_ho_success(msc_a) msc_ho_end(msc_a, true, 0)
+
+enum msc_neighbor_type msc_ho_find_target_cell(struct msc_a *msc_a, const struct gsm0808_cell_id *cid,
+ const struct neighbor_ident_entry **remote_msc,
+ struct ran_peer **ran_peer_from_neighbor_ident,
+ struct ran_peer **ran_peer_from_seen_cells)
+{
+ struct gsm_network *net = msc_a_net(msc_a);
+ const struct neighbor_ident_entry *e;
+ struct sccp_ran_inst *sri;
+ struct ran_peer *rp_from_neighbor_ident = NULL;
+ struct ran_peer *rp_from_cell_id = NULL;
+ struct ran_peer *rp;
+ int i;
+
+ OSMO_ASSERT(remote_msc);
+ OSMO_ASSERT(ran_peer_from_neighbor_ident);
+ OSMO_ASSERT(ran_peer_from_seen_cells);
+
+ e = neighbor_ident_find_by_cell(&net->neighbor_ident_list, msc_a->c.ran->type, cid);
+
+ if (e && e->addr.type == MSC_NEIGHBOR_TYPE_REMOTE_MSC) {
+ *remote_msc = e;
+ return MSC_NEIGHBOR_TYPE_REMOTE_MSC;
+ }
+
+ /* It is not a remote MSC target. Figure out local RAN peers. */
+
+ if (e && e->addr.type == MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER) {
+ /* Find local RAN peer in neighbor config. If anything is wrong with that, just keep
+ * rp_from_neighbor_ident == NULL. */
+
+ struct sccp_ran_inst *sri_from_neighbor_ident = NULL;
+ struct osmo_ss7_instance *ss7 = NULL;
+
+ /* Get the sccp_ran_inst with sanity checkin. If anything is fishy, just keep
+ * sri_from_neighbor_ident == NULL and below code will notice the error. */
+ if (e->addr.ran_type < msc_ran_infra_len) {
+ sri_from_neighbor_ident = msc_ran_infra[e->addr.ran_type].sri;
+ ss7 = osmo_sccp_get_ss7(sri_from_neighbor_ident->sccp);
+ if (!ss7)
+ sri_from_neighbor_ident = NULL;
+ }
+
+ if (!sri_from_neighbor_ident) {
+ LOG_HO(msc_a, LOGL_ERROR, "Cannot handover to RAN type %s\n", osmo_rat_type_name(e->addr.ran_type));
+ } else {
+ /* Interpret the point-code string placed in the neighbors config. */
+ int pc = osmo_ss7_pointcode_parse(ss7, e->addr.local_ran_peer_pc_str);
+
+ if (pc < 0) {
+ LOG_HO(msc_a, LOGL_ERROR, "Invalid point code string: %s\n",
+ osmo_quote_str(e->addr.local_ran_peer_pc_str, -1));
+ } else {
+ struct osmo_sccp_addr addr = {};
+ osmo_sccp_make_addr_pc_ssn(&addr, pc, sri_from_neighbor_ident->ran->ssn);
+ rp_from_neighbor_ident = ran_peer_find_by_addr(sri_from_neighbor_ident, &addr);
+ }
+ }
+
+ if (!rp_from_neighbor_ident) {
+ LOG_HO(msc_a, LOGL_ERROR, "Target RAN peer from neighbor config is not connected:"
+ " Cell ID %s resolves to target address %s\n",
+ gsm0808_cell_id_name(cid), e->addr.local_ran_peer_pc_str);
+ } else if (rp_from_neighbor_ident->fi->state != RAN_PEER_ST_READY) {
+ LOG_HO(msc_a, LOGL_ERROR, "Target RAN peer in invalid state: %s (%s)\n",
+ osmo_fsm_inst_state_name(rp_from_neighbor_ident->fi),
+ rp_from_neighbor_ident->fi->id);
+ rp_from_neighbor_ident = NULL;
+ }
+ }
+
+ /* Figure out actually connected RAN peers for this cell ID.
+ * If no cell has been found yet at all, this might determine a Handover target,
+ * otherwise this is for sanity checking. If none is found, just keep rp_from_cell_id == NULL. */
+
+ /* Iterate all connected RAN peers. Possibly, more than one RAN peer has advertised a match for this Cell ID.
+ * For example, if the handover target is identified as LAC=23 but there are multiple cells with distinct CIs
+ * serving in LAC=23, we have an ambiguity. It's up to the user to configure correctly, help with logging. */
+ for (i = 0; i < msc_ran_infra_len; i++) {
+ sri = msc_ran_infra[i].sri;
+ if (!sri)
+ continue;
+
+ rp = ran_peer_find_by_cell_id(sri, cid, true);
+ if (rp && rp->fi && rp->fi->state == RAN_PEER_ST_READY) {
+ if (rp_from_cell_id) {
+ LOG_HO(msc_a, LOGL_ERROR,
+ "Ambiguous match for cell ID %s: more than one RAN type is serving this cell"
+ " ID: %s and %s\n",
+ gsm0808_cell_id_name(cid),
+ rp_from_cell_id->fi->id,
+ rp->fi->id);
+ /* But logging is all we're going to do about it. */
+ }
+
+ /* Use the first found RAN peer, but if multiple matches are found, favor the one that matches
+ * the current RAN type. */
+ if (!rp_from_cell_id || rp->sri == msc_a->c.ran->sri)
+ rp_from_cell_id = rp;
+ }
+ }
+
+ /* Did we find mismatching targets from neighbor config and from connected cells? */
+ if (rp_from_neighbor_ident && rp_from_cell_id
+ && rp_from_neighbor_ident != rp_from_cell_id) {
+ LOG_HO(msc_a, LOGL_ERROR, "Ambiguous match for cell ID %s:"
+ " neighbor config points at %s; a matching cell is also served by connected RAN peer %s\n",
+ gsm0808_cell_id_name(cid), rp_from_neighbor_ident->fi->id, rp_from_cell_id->fi->id);
+ /* But logging is all we're going to do about it. */
+ }
+
+ if (rp_from_neighbor_ident && rp_from_neighbor_ident->sri != msc_a->c.ran->sri) {
+ LOG_HO(msc_a, LOGL_ERROR,
+ "Neighbor config indicates inter-RAT Handover, which is not implemented. Ignoring target %s\n",
+ rp_from_neighbor_ident->fi->id);
+ rp_from_neighbor_ident = NULL;
+ }
+
+ if (rp_from_cell_id && rp_from_cell_id->sri != msc_a->c.ran->sri) {
+ LOG_HO(msc_a, LOGL_ERROR,
+ "Target RAN peer indicates inter-RAT Handover, which is not implemented. Ignoring target %s\n",
+ rp_from_cell_id->fi->id);
+ rp_from_cell_id = NULL;
+ }
+
+ *ran_peer_from_neighbor_ident = rp_from_neighbor_ident;
+ *ran_peer_from_seen_cells = rp_from_cell_id;
+
+ return rp_from_neighbor_ident || rp_from_cell_id ? MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER : MSC_NEIGHBOR_TYPE_NONE;
+}
+
+static bool msc_ho_find_next_target_cell(struct msc_a *msc_a)
+{
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ struct ran_handover_required *info = &msc_a->ho.info;
+ struct gsm0808_cell_id *cid = &msc_a->ho.new_cell.cid;
+ const struct neighbor_ident_entry *e;
+ struct ran_peer *rp_from_neighbor_ident = NULL;
+ struct ran_peer *rp_from_cell_id = NULL;
+ struct ran_peer *rp;
+
+ unsigned int cil_idx = msc_a->ho.next_cil_idx;
+ msc_a->ho.next_cil_idx++;
+
+ msc_a->ho.new_cell.type = MSC_NEIGHBOR_TYPE_NONE;
+
+ if (cil_idx >= info->cil.id_list_len)
+ return false;
+
+ *cid = (struct gsm0808_cell_id){
+ .id_discr = info->cil.id_discr,
+ .id = info->cil.id_list[cil_idx],
+ };
+
+ msc_a->ho.new_cell.cgi = (struct osmo_cell_global_id){
+ .lai = vsub->cgi.lai,
+ };
+ gsm0808_cell_id_to_cgi(&msc_a->ho.new_cell.cgi, cid);
+
+ switch (msc_ho_find_target_cell(msc_a, cid, &e, &rp_from_neighbor_ident, &rp_from_cell_id)) {
+ case MSC_NEIGHBOR_TYPE_REMOTE_MSC:
+ OSMO_ASSERT(e);
+ msc_a->ho.new_cell.ran_type = e->addr.ran_type;
+ msc_a->ho.new_cell.type = MSC_NEIGHBOR_TYPE_REMOTE_MSC;
+ msc_a->ho.new_cell.msc_ipa_name = e->addr.remote_msc_ipa_name.buf;
+ return true;
+
+ case MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER:
+ rp = rp_from_neighbor_ident ? : rp_from_cell_id;
+ OSMO_ASSERT(rp);
+ msc_a->ho.new_cell.type = MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER;
+ msc_a->ho.new_cell.ran_peer = rp;
+ return true;
+
+ default:
+ break;
+ }
+
+ LOG_HO(msc_a, LOGL_DEBUG, "Cannot find target peer for cell ID %s\n", gsm0808_cell_id_name(cid));
+ /* Try the next cell id, if any. */
+ return msc_ho_find_next_target_cell(msc_a);
+}
+
+static void msc_ho_fsm_required_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct msc_a *msc_a = fi->priv;
+
+ if (!msc_ho_find_next_target_cell(msc_a)) {
+ int tried = msc_a->ho.next_cil_idx - 1;
+ msc_ho_failed(msc_a, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
+ "Attempted Handover to %u cells without success\n", tried);
+ return;
+ }
+
+ msc_ho_fsm_state_chg(msc_a, MSC_HO_ST_WAIT_REQUEST_ACK);
+}
+
+static void msc_ho_send_handover_request(struct msc_a *msc_a)
+{
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ struct gsm_network *net = msc_a_net(msc_a);
+ struct gsm0808_channel_type channel_type;
+ struct ran_msg ran_enc_msg = {
+ .msg_type = RAN_MSG_HANDOVER_REQUEST,
+ .handover_request = {
+ .imsi = vsub->imsi,
+ .classmark = &vsub->classmark,
+ .geran = {
+ .chosen_encryption = &msc_a->geran_encr,
+ .a5_encryption_mask = net->a5_encryption_mask,
+ },
+ .bssap_cause = GSM0808_CAUSE_BETTER_CELL,
+ .current_channel_type_1_present = msc_a->ho.info.current_channel_type_1_present,
+ .current_channel_type_1 = msc_a->ho.info.current_channel_type_1,
+ .speech_version_used = msc_a->ho.info.speech_version_used,
+ .old_bss_to_new_bss_info_raw = msc_a->ho.info.old_bss_to_new_bss_info_raw,
+ .old_bss_to_new_bss_info_raw_len = msc_a->ho.info.old_bss_to_new_bss_info_raw_len,
+
+ /* Don't send AoIP Transport Layer Address for inter-MSC Handover */
+ .rtp_ran_local = (msc_a->ho.new_cell.type == MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER)
+ ? call_leg_local_ip(msc_a->cc.call_leg, RTP_TO_RAN) : NULL,
+ },
+ };
+
+ if (msc_a->cc.active_trans) {
+ if (mncc_bearer_cap_to_channel_type(&channel_type, &msc_a->cc.active_trans->bearer_cap)) {
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Failed to encode Bearer Cap to Channel Type\n");
+ return;
+ }
+ ran_enc_msg.handover_request.geran.channel_type = &channel_type;
+ }
+
+ gsm0808_cell_id_from_cgi(&ran_enc_msg.handover_request.cell_id_serving, CELL_IDENT_WHOLE_GLOBAL, &vsub->cgi);
+ ran_enc_msg.handover_request.cell_id_target = msc_a->ho.new_cell.cid;
+
+ if (msc_a_msg_down(msc_a, MSC_ROLE_T, MSC_T_EV_FROM_A_PREPARE_HANDOVER_REQUEST, &ran_enc_msg))
+ msc_ho_try_next_cell(msc_a, "Failed to send Handover Request message\n");
+}
+
+static void msc_ho_fsm_wait_request_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct msc_a *msc_a = fi->priv;
+ struct msc_i *msc_i = msc_a_msc_i(msc_a);
+ struct msc_t *msc_t;
+ struct ran_peer *rp;
+ const char *ipa_name;
+
+ msc_t = msc_a_msc_t(msc_a);
+ if (msc_t) {
+ /* All the other code should prevent this from happening, ever. */
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Cannot initiate Handover Request, there still is an active MSC-T role: %s\n",
+ msc_t->c.fi->id);
+ return;
+ }
+
+ if (!msc_i) {
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Cannot initiate Handover Request, there is no MSC-I role\n");
+ return;
+ }
+
+ if (!msc_i->c.remote_to
+ && !(msc_i->ran_conn && msc_i->ran_conn->ran_peer)) {
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Cannot initiate Handover Request, MSC-I role has no connection\n");
+ return;
+ }
+
+ switch (msc_a->ho.new_cell.type) {
+ case MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER:
+ rp = msc_a->ho.new_cell.ran_peer;
+ OSMO_ASSERT(rp && rp->fi);
+
+ if (msc_i->c.remote_to) {
+ LOG_HO(msc_a, LOGL_INFO,
+ "Starting inter-MSC Subsequent Handover from remote MSC %s to local %s\n",
+ msc_i->c.remote_to->remote_name, rp->fi->id);
+ msc_a->ho.subsequent_ho = true;
+ } else {
+ LOG_HO(msc_a, LOGL_INFO, "Starting inter-BSC Handover from %s to %s\n",
+ msc_i->ran_conn->ran_peer->fi->id, rp->fi->id);
+ }
+
+ msc_t_alloc(msc_a->c.msub, rp);
+ msc_ho_send_handover_request(msc_a);
+ return;
+
+ case MSC_NEIGHBOR_TYPE_REMOTE_MSC:
+ ipa_name = msc_a->ho.new_cell.msc_ipa_name;
+ OSMO_ASSERT(ipa_name);
+
+ if (msc_i->c.remote_to) {
+ LOG_HO(msc_a, LOGL_INFO,
+ "Starting inter-MSC Subsequent Handover from remote MSC %s to remote MSC at %s\n",
+ msc_i->c.remote_to->remote_name, osmo_quote_str(ipa_name, -1));
+ msc_a->ho.subsequent_ho = true;
+ } else {
+ LOG_HO(msc_a, LOGL_INFO, "Starting inter-MSC Handover from local %s to remote MSC at %s\n",
+ msc_i->ran_conn->ran_peer->fi->id,
+ osmo_quote_str(ipa_name, -1));
+ }
+
+ msc_t_remote_alloc(msc_a->c.msub, msc_a->c.ran, (const uint8_t*)ipa_name, strlen(ipa_name));
+ msc_ho_send_handover_request(msc_a);
+ return;
+
+ default:
+ msc_ho_try_next_cell(msc_a, "unknown Handover target type %d\n", msc_a->ho.new_cell.type);
+ return;
+ }
+
+ msc_t = msc_a_msc_t(msc_a);
+ if (!msc_t) {
+ /* There should definitely be one now. */
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Cannot initiate Handover Request, failed to set up a target MSC-T\n");
+ return;
+ }
+}
+
+static void msc_ho_rx_request_ack(struct msc_a *msc_a, struct msc_a_ran_dec_data *hra);
+
+static void msc_ho_fsm_wait_request_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_a *msc_a = fi->priv;
+
+ switch (event) {
+
+ case MSC_HO_EV_RX_REQUEST_ACK:
+ msc_ho_rx_request_ack(msc_a, (struct msc_a_ran_dec_data*)data);
+ return;
+
+ case MSC_HO_EV_RX_FAILURE:
+ msc_ho_failed(msc_a, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
+ "Received Handover Failure message\n");
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void msc_ho_rtp_switch_to_new_cell(struct msc_a *msc_a);
+
+void msc_ho_mncc_forward_cb(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg, void *data)
+{
+ struct msc_a *msc_a = data;
+ switch (mncc_msg->msg_type) {
+ case MNCC_RTP_CONNECT:
+ msc_a->ho.rtp_switched_to_new_cell = true;
+ return;
+ default:
+ return;
+ }
+}
+
+/* Initiate call forwarding via MNCC: call the Handover Number that the other MSC assigned. */
+static int msc_ho_start_inter_msc_call_forwarding(struct msc_a *msc_a, struct msc_t *msc_t,
+ const struct msc_a_ran_dec_data *hra)
+{
+ const struct osmo_gsup_message *e_info = hra->an_apdu->e_info;
+ struct gsm_mncc outgoing_call_req = {};
+ struct call_leg *cl = msc_a->cc.call_leg;
+ struct rtp_stream *rtp_to_ran = cl ? cl->rtp[RTP_TO_RAN] : NULL;
+ struct mncc_call *mncc_call;
+
+ if (!e_info || !e_info->msisdn_enc || !e_info->msisdn_enc_len) {
+ msc_ho_try_next_cell(msc_a,
+ "No Handover Number in Handover Request Acknowledge from remote MSC\n");
+ return -EINVAL;
+ }
+
+ /* Backup old cell's RTP IP:port and codec data */
+ msc_a->ho.old_cell.ran_remote_rtp = rtp_to_ran->remote;
+ msc_a->ho.old_cell.codec = rtp_to_ran->codec;
+
+ /* Blindly taken over from an MNCC trace of existing code: send an all-zero CCCAP: */
+ outgoing_call_req.fields |= MNCC_F_CCCAP;
+
+ /* Called number */
+ outgoing_call_req.fields |= MNCC_F_CALLED;
+ outgoing_call_req.called.plan = 1; /* Empirical magic number. There seem to be no enum or defines for this.
+ * The only other place setting this apparently is gsm48_decode_called(). */
+ if (gsm48_decode_bcd_number2(outgoing_call_req.called.number, sizeof(outgoing_call_req.called.number),
+ e_info->msisdn_enc, e_info->msisdn_enc_len, 0)) {
+ msc_ho_try_next_cell(msc_a,
+ "Failed to decode Handover Number in Handover Request Acknowledge"
+ " from remote MSC\n");
+ return -EINVAL;
+ }
+
+ if (msc_a->cc.active_trans) {
+ outgoing_call_req.fields |= MNCC_F_BEARER_CAP;
+ outgoing_call_req.bearer_cap = msc_a->cc.active_trans->bearer_cap;
+ }
+
+ mncc_call = mncc_call_alloc(msc_a_vsub(msc_a),
+ msc_a->ho.fi,
+ MSC_HO_EV_MNCC_FORWARDING_COMPLETE,
+ MSC_HO_EV_MNCC_FORWARDING_FAILED,
+ msc_ho_mncc_forward_cb, msc_a);
+
+ mncc_call_set_rtp_stream(mncc_call, rtp_to_ran);
+ msc_a->ho.new_cell.mncc_forwarding_to_remote_ran = mncc_call;
+ return mncc_call_outgoing_start(mncc_call, &outgoing_call_req);
+}
+
+static void msc_ho_rx_request_ack(struct msc_a *msc_a, struct msc_a_ran_dec_data *hra)
+{
+ struct msc_t *msc_t = msc_a_msc_t(msc_a);
+ struct ran_msg ran_enc_msg;
+
+ OSMO_ASSERT(hra->ran_dec);
+ OSMO_ASSERT(hra->an_apdu);
+
+ if (!msc_t) {
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE, "MSC-T role missing\n");
+ return;
+ }
+
+ if (!hra->ran_dec->handover_request_ack.rr_ho_command
+ || !hra->ran_dec->handover_request_ack.rr_ho_command_len) {
+ msc_ho_try_next_cell(msc_a, "Missing mandatory IE in Handover Request Acknowledge:"
+ " L3 Info (RR Handover Command)\n");
+ return;
+ }
+
+ if (!hra->ran_dec->handover_request_ack.chosen_channel_present) {
+ LOG_HO(msc_a, LOGL_DEBUG, "No 'Chosen Channel' IE in Handover Request Ack\n");
+ msc_t->geran.chosen_channel = 0;
+ } else
+ msc_t->geran.chosen_channel = hra->ran_dec->handover_request_ack.chosen_channel;
+
+ if (!hra->ran_dec->handover_request_ack.chosen_encr_alg) {
+ LOG_HO(msc_a, LOGL_DEBUG, "No 'Chosen Encryption Algorithm' IE in Handover Request Ack\n");
+ msc_t->geran.chosen_encr_alg = 0;
+ } else {
+ msc_t->geran.chosen_encr_alg = hra->ran_dec->handover_request_ack.chosen_encr_alg;
+ if (msc_t->geran.chosen_encr_alg < 1 || msc_t->geran.chosen_encr_alg > 8) {
+ msc_ho_try_next_cell(msc_a, "Handover Request Ack: Invalid 'Chosen Encryption Algorithm': %u\n",
+ msc_t->geran.chosen_encr_alg);
+ return;
+ }
+ }
+
+ msc_t->geran.chosen_speech_version = hra->ran_dec->handover_request_ack.chosen_speech_version;
+ if (!msc_t->geran.chosen_speech_version)
+ LOG_HO(msc_a, LOGL_DEBUG, "No 'Chosen Speech Version' IE in Handover Request Ack\n");
+
+ /* Inter-MSC call forwarding? */
+ if (msc_a->ho.new_cell.type == MSC_NEIGHBOR_TYPE_REMOTE_MSC) {
+ if (msc_ho_start_inter_msc_call_forwarding(msc_a, msc_t, hra))
+ return;
+ }
+
+ msc_ho_fsm_state_chg(msc_a, MSC_HO_ST_WAIT_COMPLETE);
+
+ /* Forward the RR Handover Command composed by the new RAN peer down to the old RAN peer */
+ ran_enc_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_HANDOVER_COMMAND,
+ .handover_command = {
+ .rr_ho_command = hra->ran_dec->handover_request_ack.rr_ho_command,
+ .rr_ho_command_len = hra->ran_dec->handover_request_ack.rr_ho_command_len,
+ },
+ };
+
+ if (msc_a_msg_down(msc_a, MSC_ROLE_I,
+ msc_a->ho.subsequent_ho ? MSC_I_EV_FROM_A_PREPARE_SUBSEQUENT_HANDOVER_RESULT
+ : MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST,
+ &ran_enc_msg)) {
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE, "Failed to send Handover Command\n");
+ return;
+ }
+
+ msc_a->ho.new_cell.ran_remote_rtp = hra->ran_dec->handover_request_ack.remote_rtp;
+ if (osmo_sockaddr_str_is_set(&msc_a->ho.new_cell.ran_remote_rtp)) {
+ LOG_HO(msc_a, LOGL_DEBUG, "Request Ack contains cell's RTP address " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&msc_a->ho.new_cell.ran_remote_rtp));
+ }
+
+ msc_a->ho.new_cell.codec_present = hra->ran_dec->handover_request_ack.codec_present;
+ msc_a->ho.new_cell.codec = hra->ran_dec->handover_request_ack.codec;
+ if (hra->ran_dec->handover_request_ack.codec_present) {
+ LOG_HO(msc_a, LOGL_DEBUG, "Request Ack contains codec %s\n",
+ osmo_mgcpc_codec_name(msc_a->ho.new_cell.codec));
+ }
+}
+
+static void msc_ho_rtp_switch_to_new_cell(struct msc_a *msc_a)
+{
+ struct call_leg *cl = msc_a->cc.call_leg;
+ struct rtp_stream *rtp_to_ran = cl ? cl->rtp[RTP_TO_RAN] : NULL;
+
+ if (!rtp_to_ran) {
+ LOG_HO(msc_a, LOGL_DEBUG, "No RTP stream, nothing to switch\n");
+ return;
+ }
+
+ if (!osmo_sockaddr_str_is_set(&msc_a->ho.new_cell.ran_remote_rtp)) {
+ LOG_HO(msc_a, LOGL_DEBUG, "New cell's RTP IP:port not yet known, not switching RTP stream\n");
+ return;
+ }
+
+ if (msc_a->ho.rtp_switched_to_new_cell) {
+ LOG_HO(msc_a, LOGL_DEBUG, "Already switched RTP to new cell\n");
+ return;
+ }
+ msc_a->ho.rtp_switched_to_new_cell = true;
+
+ /* Backup old cell's RTP IP:port and codec data */
+ msc_a->ho.old_cell.ran_remote_rtp = rtp_to_ran->remote;
+ msc_a->ho.old_cell.codec = rtp_to_ran->codec;
+
+ LOG_HO(msc_a, LOGL_DEBUG, "Switching RTP stream to new cell: from " OSMO_SOCKADDR_STR_FMT " to " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&msc_a->ho.old_cell.ran_remote_rtp),
+ OSMO_SOCKADDR_STR_FMT_ARGS(&msc_a->ho.new_cell.ran_remote_rtp));
+
+ /* If a previous forwarding to a remote MSC is still active, this now becomes no longer responsible for the RTP
+ * stream. */
+ if (msc_a->cc.mncc_forwarding_to_remote_ran) {
+ if (msc_a->cc.mncc_forwarding_to_remote_ran->rtps != rtp_to_ran) {
+ LOG_HO(msc_a, LOGL_ERROR,
+ "Unexpected state: previous MNCC forwarding not using RTP-to-RAN stream\n");
+ /* That would be weird, but carry on anyway... */
+ }
+ mncc_call_detach_rtp_stream(msc_a->cc.mncc_forwarding_to_remote_ran);
+ }
+
+ /* Switch over to the new peer */
+ rtp_stream_set_remote_addr(rtp_to_ran, &msc_a->ho.new_cell.ran_remote_rtp);
+ if (msc_a->ho.new_cell.codec_present)
+ rtp_stream_set_codec(rtp_to_ran, msc_a->ho.new_cell.codec);
+ else
+ LOG_HO(msc_a, LOGL_ERROR, "No codec is set\n");
+ rtp_stream_commit(rtp_to_ran);
+}
+
+static void msc_ho_rtp_rollback_to_old_cell(struct msc_a *msc_a)
+{
+ struct call_leg *cl = msc_a->cc.call_leg;
+ struct rtp_stream *rtp_to_ran = cl ? cl->rtp[RTP_TO_RAN] : NULL;
+
+ if (!msc_a->ho.rtp_switched_to_new_cell) {
+ LOG_HO(msc_a, LOGL_DEBUG, "Not switched RTP to new cell yet, no need to roll back\n");
+ return;
+ }
+
+ if (!rtp_to_ran) {
+ LOG_HO(msc_a, LOGL_DEBUG, "No RTP stream, nothing to switch\n");
+ return;
+ }
+
+ if (!osmo_sockaddr_str_is_set(&msc_a->ho.old_cell.ran_remote_rtp)) {
+ LOG_HO(msc_a, LOGL_DEBUG, "Have no RTP IP:port for the old cell, not switching back to\n");
+ return;
+ }
+
+ /* The new call forwarding to a remote MSC is no longer needed because the handover failed */
+ if (msc_a->ho.new_cell.mncc_forwarding_to_remote_ran)
+ mncc_call_detach_rtp_stream(msc_a->ho.new_cell.mncc_forwarding_to_remote_ran);
+
+ /* If before this handover, there was a call forwarding to a remote MSC in place, this now goes back into
+ * responsibility. */
+ if (msc_a->cc.mncc_forwarding_to_remote_ran)
+ mncc_call_set_rtp_stream(msc_a->cc.mncc_forwarding_to_remote_ran, rtp_to_ran);
+
+ msc_a->ho.rtp_switched_to_new_cell = false;
+ msc_a->ho.ready_to_switch_rtp = false;
+ LOG_HO(msc_a, LOGL_NOTICE, "Switching RTP back to old cell\n");
+
+ /* Switch back to the old cell */
+ rtp_stream_set_remote_addr(rtp_to_ran, &msc_a->ho.old_cell.ran_remote_rtp);
+ rtp_stream_set_codec(rtp_to_ran, msc_a->ho.old_cell.codec);
+ rtp_stream_commit(rtp_to_ran);
+}
+
+static void msc_ho_send_handover_succeeded(struct msc_a *msc_a)
+{
+ struct ran_msg ran_enc_msg = {
+ .msg_type = RAN_MSG_HANDOVER_SUCCEEDED,
+ };
+
+ if (msc_a_msg_down(msc_a, MSC_ROLE_I, MSC_I_EV_FROM_A_FORWARD_ACCESS_SIGNALLING_REQUEST, &ran_enc_msg))
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE, "Failed to send Handover Succeeded message\n");
+}
+
+static void msc_ho_fsm_wait_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct msc_a *msc_a = fi->priv;
+
+ switch (event) {
+
+ case MSC_HO_EV_RX_DETECT:
+ msc_a->ho.ready_to_switch_rtp = true;
+ /* For inter-MSC, the mncc_fsm switches the rtp_stream upon MNCC_RTP_CONNECT.
+ * For inter-BSC, need to switch here to the address obtained from Handover Request Ack. */
+ if (msc_a->ho.new_cell.type == MSC_NEIGHBOR_TYPE_LOCAL_RAN_PEER)
+ msc_ho_rtp_switch_to_new_cell(msc_a);
+ msc_ho_send_handover_succeeded(msc_a);
+ return;
+
+ case MSC_HO_EV_RX_COMPLETE:
+ msc_ho_success(msc_a);
+ return;
+
+ case MSC_HO_EV_RX_FAILURE:
+ msc_ho_failed(msc_a, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
+ "Received Handover Failure message\n");
+ return;
+
+ case MSC_HO_EV_MNCC_FORWARDING_FAILED:
+ msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE, "MNCC Forwarding failed\n");
+ return;
+
+ case MSC_HO_EV_MNCC_FORWARDING_COMPLETE:
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void msc_ho_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct msc_a *msc_a = fi->priv;
+ struct msc_t *msc_t = msc_a_msc_t(msc_a);
+
+ /* paranoia */
+ if (msc_a->ho.fi != fi)
+ return;
+
+ /* Completely clear all handover state */
+ msc_a->ho = (struct msc_ho_state){};
+
+ if (msc_t)
+ msc_t_clear(msc_t);
+}
+
+static int msc_ho_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ return 1;
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state msc_ho_fsm_states[] = {
+ [MSC_HO_ST_REQUIRED] = {
+ .name = OSMO_STRINGIFY(MSC_HO_ST_REQUIRED),
+ .out_state_mask = 0
+ | S(MSC_HO_ST_REQUIRED)
+ | S(MSC_HO_ST_WAIT_REQUEST_ACK)
+ ,
+ .onenter = msc_ho_fsm_required_onenter,
+ },
+ [MSC_HO_ST_WAIT_REQUEST_ACK] = {
+ .name = OSMO_STRINGIFY(MSC_HO_ST_WAIT_REQUEST_ACK),
+ .in_event_mask = 0
+ | S(MSC_HO_EV_RX_REQUEST_ACK)
+ | S(MSC_HO_EV_RX_FAILURE)
+ ,
+ .out_state_mask = 0
+ | S(MSC_HO_ST_REQUIRED)
+ | S(MSC_HO_ST_WAIT_COMPLETE)
+ ,
+ .onenter = msc_ho_fsm_wait_request_ack_onenter,
+ .action = msc_ho_fsm_wait_request_ack,
+ },
+ [MSC_HO_ST_WAIT_COMPLETE] = {
+ .name = OSMO_STRINGIFY(MSC_HO_ST_WAIT_COMPLETE),
+ .in_event_mask = 0
+ | S(MSC_HO_EV_RX_DETECT)
+ | S(MSC_HO_EV_RX_COMPLETE)
+ | S(MSC_HO_EV_RX_FAILURE)
+ | S(MSC_HO_EV_MNCC_FORWARDING_COMPLETE)
+ | S(MSC_HO_EV_MNCC_FORWARDING_FAILED)
+ ,
+ .action = msc_ho_fsm_wait_complete,
+ },
+};
+
+static const struct value_string msc_ho_fsm_event_names[] = {
+ OSMO_VALUE_STRING(MSC_HO_EV_RX_REQUEST_ACK),
+ OSMO_VALUE_STRING(MSC_HO_EV_RX_DETECT),
+ OSMO_VALUE_STRING(MSC_HO_EV_RX_COMPLETE),
+ OSMO_VALUE_STRING(MSC_HO_EV_RX_FAILURE),
+ {}
+};
+
+struct osmo_fsm msc_ho_fsm = {
+ .name = "handover",
+ .states = msc_ho_fsm_states,
+ .num_states = ARRAY_SIZE(msc_ho_fsm_states),
+ .log_subsys = DHO,
+ .event_names = msc_ho_fsm_event_names,
+ .timer_cb = msc_ho_fsm_timer_cb,
+ .cleanup = msc_ho_fsm_cleanup,
+};