Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 1 | /* Handle an MNCC managed call (external MNCC). */ |
| 2 | /* At the time of writing, this is only used for inter-MSC handover: forward a voice stream to a remote MSC. |
| 3 | * Maybe it makes sense to also use it for all "normal" external call management at some point. */ |
| 4 | /* |
| 5 | * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> |
| 6 | * All Rights Reserved |
| 7 | * |
| 8 | * SPDX-License-Identifier: AGPL-3.0+ |
| 9 | * |
| 10 | * Author: Neels Hofmeyr |
| 11 | * |
| 12 | * This program is free software; you can redistribute it and/or modify |
| 13 | * it under the terms of the GNU Affero General Public License as published by |
| 14 | * the Free Software Foundation; either version 3 of the License, or |
| 15 | * (at your option) any later version. |
| 16 | * |
| 17 | * This program is distributed in the hope that it will be useful, |
| 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 20 | * GNU Affero General Public License for more details. |
| 21 | * |
| 22 | * You should have received a copy of the GNU Affero General Public License |
| 23 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 24 | */ |
| 25 | |
| 26 | #include <string.h> |
| 27 | |
| 28 | #include <osmocom/core/msgb.h> |
| 29 | #include <osmocom/core/fsm.h> |
| 30 | #include <osmocom/core/tdef.h> |
| 31 | |
| 32 | #include <osmocom/msc/mncc_call.h> |
| 33 | #include <osmocom/msc/debug.h> |
| 34 | #include <osmocom/msc/gsm_data.h> |
| 35 | #include <osmocom/msc/rtp_stream.h> |
| 36 | #include <osmocom/msc/msub.h> |
| 37 | #include <osmocom/msc/vlr.h> |
Neels Hofmeyr | a001a70 | 2022-10-31 17:57:30 +0100 | [diff] [blame] | 38 | #include <osmocom/msc/codec_mapping.h> |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 39 | |
| 40 | struct osmo_fsm mncc_call_fsm; |
| 41 | static bool mncc_call_tx_rtp_create(struct mncc_call *mncc_call); |
| 42 | |
| 43 | LLIST_HEAD(mncc_call_list); |
| 44 | |
| 45 | static const struct osmo_tdef_state_timeout mncc_call_fsm_timeouts[32] = { |
| 46 | /* TODO */ |
| 47 | }; |
| 48 | |
| 49 | struct gsm_network *gsmnet = NULL; |
| 50 | |
| 51 | /* Transition to a state, using the T timer defined in msc_a_fsm_timeouts. |
| 52 | * The actual timeout value is in turn obtained from network->T_defs. |
| 53 | * Assumes local variable fi exists. */ |
| 54 | #define mncc_call_fsm_state_chg(MNCC, STATE) \ |
| 55 | osmo_tdef_fsm_inst_state_chg((MNCC)->fi, STATE, mncc_call_fsm_timeouts, gsmnet->mncc_tdefs, 5) |
| 56 | |
| 57 | #define mncc_call_error(MNCC, FMT, ARGS...) do { \ |
| 58 | LOG_MNCC_CALL(MNCC, LOGL_ERROR, FMT, ##ARGS); \ |
| 59 | osmo_fsm_inst_term((MNCC)->fi, OSMO_FSM_TERM_REGULAR, 0); \ |
| 60 | } while(0) |
| 61 | |
| 62 | void mncc_call_fsm_init(struct gsm_network *net) |
| 63 | { |
Harald Welte | 34a8cc3 | 2019-12-01 15:32:09 +0100 | [diff] [blame] | 64 | OSMO_ASSERT(osmo_fsm_register(&mncc_call_fsm) == 0); |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 65 | gsmnet = net; |
| 66 | } |
| 67 | |
| 68 | void mncc_call_fsm_update_id(struct mncc_call *mncc_call) |
| 69 | { |
| 70 | osmo_fsm_inst_update_id_f_sanitize(mncc_call->fi, '-', "%s:callref-0x%x%s%s", |
| 71 | vlr_subscr_name(mncc_call->vsub), mncc_call->callref, |
| 72 | mncc_call->remote_msisdn_present ? ":to-msisdn-" : "", |
| 73 | mncc_call->remote_msisdn_present ? mncc_call->remote_msisdn.number : ""); |
| 74 | } |
| 75 | |
| 76 | /* Invoked by the socket read callback in case the given MNCC call instance is responsible for the given callref. */ |
| 77 | void mncc_call_rx(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg) |
| 78 | { |
| 79 | if (!mncc_call) |
| 80 | return; |
| 81 | LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Rx %s\n", get_mncc_name(mncc_msg->msg_type)); |
| 82 | osmo_fsm_inst_dispatch(mncc_call->fi, MNCC_CALL_EV_RX_MNCC_MSG, (void*)mncc_msg); |
| 83 | } |
| 84 | |
| 85 | /* Send an MNCC message (associated with this MNCC call). */ |
| 86 | int mncc_call_tx(struct mncc_call *mncc_call, union mncc_msg *mncc_msg) |
| 87 | { |
| 88 | struct msgb *msg; |
| 89 | unsigned char *data; |
| 90 | |
| 91 | LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "tx %s\n", get_mncc_name(mncc_msg->msg_type)); |
| 92 | |
| 93 | msg = msgb_alloc(sizeof(*mncc_msg), "MNCC-tx"); |
| 94 | OSMO_ASSERT(msg); |
| 95 | |
| 96 | data = msgb_put(msg, sizeof(*mncc_msg)); |
| 97 | memcpy(data, mncc_msg, sizeof(*mncc_msg)); |
| 98 | |
| 99 | if (gsmnet->mncc_recv(gsmnet, msg)) { |
| 100 | mncc_call_error(mncc_call, "Failed to send MNCC message %s\n", get_mncc_name(mncc_msg->msg_type)); |
| 101 | return -EIO; |
| 102 | } |
| 103 | return 0; |
| 104 | } |
| 105 | |
| 106 | /* Send a trivial MNCC message with just a message type (associated with this MNCC call). */ |
| 107 | int mncc_call_tx_msgt(struct mncc_call *mncc_call, uint32_t msg_type) |
| 108 | { |
| 109 | union mncc_msg mncc_msg = { |
| 110 | .signal = { |
| 111 | .msg_type = msg_type, |
| 112 | .callref = mncc_call->callref, |
| 113 | }, |
| 114 | }; |
| 115 | return mncc_call_tx(mncc_call, &mncc_msg); |
| 116 | } |
| 117 | |
| 118 | /* Allocate an MNCC FSM as child of the given MSC role FSM. |
| 119 | * parent_event_call_released is mandatory and is passed as the parent_term_event. |
| 120 | * parent_event_call_setup_complete is dispatched when the MNCC FSM enters the MNCC_CALL_ST_TALKING state. |
| 121 | * parent_event_call_setup_complete is optional, pass a negative number to avoid dispatching. |
| 122 | * |
| 123 | * If non-NULL, message_cb is invoked whenever an MNCC message is received from the the MNCC socket, which is useful to |
| 124 | * forward things like DTMF to CC or to another MNCC call. |
| 125 | * |
| 126 | * After mncc_call_alloc(), call either mncc_call_outgoing_start() or mncc_call_incoming_start(). |
| 127 | */ |
| 128 | struct mncc_call *mncc_call_alloc(struct vlr_subscr *vsub, |
| 129 | struct osmo_fsm_inst *parent, |
| 130 | int parent_event_call_setup_complete, |
| 131 | uint32_t parent_event_call_released, |
| 132 | mncc_call_message_cb_t message_cb, void *forward_cb_data) |
| 133 | { |
| 134 | struct mncc_call *mncc_call; |
| 135 | struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&mncc_call_fsm, parent, parent_event_call_released); |
| 136 | OSMO_ASSERT(fi); |
| 137 | OSMO_ASSERT(vsub); |
| 138 | |
| 139 | mncc_call = talloc(fi, struct mncc_call); |
| 140 | OSMO_ASSERT(mncc_call); |
| 141 | fi->priv = mncc_call; |
| 142 | |
| 143 | *mncc_call = (struct mncc_call){ |
| 144 | .fi = fi, |
| 145 | .vsub = vsub, |
| 146 | .parent_event_call_setup_complete = parent_event_call_setup_complete, |
| 147 | .message_cb = message_cb, |
| 148 | .forward_cb_data = forward_cb_data, |
| 149 | }; |
| 150 | |
| 151 | llist_add(&mncc_call->entry, &mncc_call_list); |
| 152 | mncc_call_fsm_update_id(mncc_call); |
| 153 | |
| 154 | return mncc_call; |
| 155 | } |
| 156 | |
| 157 | void mncc_call_reparent(struct mncc_call *mncc_call, |
| 158 | struct osmo_fsm_inst *new_parent, |
| 159 | int parent_event_call_setup_complete, |
| 160 | uint32_t parent_event_call_released, |
| 161 | mncc_call_message_cb_t message_cb, void *forward_cb_data) |
| 162 | { |
| 163 | LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Reparenting from parent %s to parent %s\n", |
| 164 | mncc_call->fi->proc.parent->name, new_parent->name); |
| 165 | osmo_fsm_inst_change_parent(mncc_call->fi, new_parent, parent_event_call_released); |
| 166 | talloc_steal(new_parent, mncc_call->fi); |
| 167 | mncc_call->parent_event_call_setup_complete = parent_event_call_setup_complete; |
| 168 | mncc_call->message_cb = message_cb; |
| 169 | mncc_call->forward_cb_data = forward_cb_data; |
| 170 | } |
| 171 | |
| 172 | /* Associate an rtp_stream with this MNCC call instance (optional). |
| 173 | * Can be called directly after mncc_call_alloc(). If an rtp_stream is set, upon receiving the MNCC_RTP_CONNECT containing |
| 174 | * the PBX's RTP IP and port, pass the IP:port information to rtp_stream_set_remote_addr() and rtp_stream_commit() to |
| 175 | * update the MGW connection. If no rtp_stream is associated, the caller is responsible to manually extract the RTP |
| 176 | * IP:port from the MNCC_RTP_CONNECT message forwarded to mncc_call_message_cb_t (see mncc_call_alloc()). |
| 177 | * When an rtp_stream is set, call rtp_stream_release() when the MNCC call ends; call mncc_call_detach_rtp_stream() before |
| 178 | * the MNCC call releases if that is not desired. |
| 179 | */ |
| 180 | int mncc_call_set_rtp_stream(struct mncc_call *mncc_call, struct rtp_stream *rtps) |
| 181 | { |
| 182 | if (mncc_call->rtps && mncc_call->rtps != rtps) { |
| 183 | LOG_MNCC_CALL(mncc_call, LOGL_ERROR, |
| 184 | "Cannot associate with RTP stream %s, already associated with %s\n", |
| 185 | rtps ? rtps->fi->name : "NULL", mncc_call->rtps->fi->name); |
| 186 | return -ENOSPC; |
| 187 | } |
| 188 | |
| 189 | mncc_call->rtps = rtps; |
| 190 | LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Associated with RTP stream %s\n", mncc_call->rtps->fi->name); |
| 191 | return 0; |
| 192 | } |
| 193 | |
| 194 | /* Disassociate the rtp_stream from this MNCC call instance, and clear the remote RTP IP:port info. |
| 195 | * When the MNCC FSM ends for any reason, it will release the RTP stream (which usually triggers complete tear down of |
Martin Hauke | 3f07dac | 2019-11-14 17:49:08 +0100 | [diff] [blame] | 196 | * the call_leg and CC transaction). If the RTP stream should still remain in use, e.g. during Subsequent inter-MSC |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 197 | * Handover where this MNCC was a forwarding to a remote MSC that is no longer needed, this function must be called |
| 198 | * before the MNCC FSM instance terminates. Call this *before* setting a new remote RTP address on the rtp_stream, since |
| 199 | * this clears the rtp_stream->remote ip:port information. */ |
| 200 | void mncc_call_detach_rtp_stream(struct mncc_call *mncc_call) |
| 201 | { |
| 202 | struct rtp_stream *rtps = mncc_call->rtps; |
Vadim Yanitskiy | c5a8e9f | 2019-05-11 04:53:23 +0700 | [diff] [blame] | 203 | struct osmo_sockaddr_str clear = { 0 }; |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 204 | if (!rtps) |
| 205 | return; |
| 206 | mncc_call->rtps = NULL; |
| 207 | rtp_stream_set_remote_addr(rtps, &clear); |
| 208 | } |
| 209 | |
| 210 | static void mncc_call_tx_setup_ind(struct mncc_call *mncc_call) |
| 211 | { |
Philipp Maier | 8c472bd | 2020-09-18 18:01:32 +0200 | [diff] [blame] | 212 | union mncc_msg mncc_msg; |
| 213 | mncc_msg.signal = mncc_call->outgoing_req; |
| 214 | mncc_msg.signal.msg_type = MNCC_SETUP_IND; |
| 215 | mncc_msg.signal.callref = mncc_call->callref; |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 216 | |
Philipp Maier | 8c472bd | 2020-09-18 18:01:32 +0200 | [diff] [blame] | 217 | OSMO_STRLCPY_ARRAY(mncc_msg.signal.imsi, mncc_call->vsub->imsi); |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 218 | |
| 219 | if (!(mncc_call->outgoing_req.fields & MNCC_F_CALLING)) { |
| 220 | /* No explicit calling number set, use the local subscriber */ |
Philipp Maier | 8c472bd | 2020-09-18 18:01:32 +0200 | [diff] [blame] | 221 | mncc_msg.signal.fields |= MNCC_F_CALLING; |
| 222 | OSMO_STRLCPY_ARRAY(mncc_msg.signal.calling.number, mncc_call->vsub->msisdn); |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 223 | |
| 224 | } |
| 225 | mncc_call->local_msisdn_present = true; |
Philipp Maier | 8c472bd | 2020-09-18 18:01:32 +0200 | [diff] [blame] | 226 | mncc_call->local_msisdn = mncc_msg.signal.calling; |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 227 | |
Pau Espin Pedrol | 2e21a68 | 2021-06-04 16:45:44 +0200 | [diff] [blame] | 228 | rate_ctr_inc(rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_CALL_MO_SETUP)); |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 229 | |
Philipp Maier | 8c472bd | 2020-09-18 18:01:32 +0200 | [diff] [blame] | 230 | mncc_call_tx(mncc_call, &mncc_msg); |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 231 | } |
| 232 | |
| 233 | static void mncc_call_rx_setup_req(struct mncc_call *mncc_call, const struct gsm_mncc *incoming_req) |
| 234 | { |
| 235 | mncc_call->callref = incoming_req->callref; |
| 236 | |
| 237 | if (incoming_req->fields & MNCC_F_CALLED) { |
| 238 | mncc_call->local_msisdn_present = true; |
| 239 | mncc_call->local_msisdn = incoming_req->called; |
| 240 | } |
| 241 | |
| 242 | if (incoming_req->fields & MNCC_F_CALLING) { |
| 243 | mncc_call->remote_msisdn_present = true; |
| 244 | mncc_call->remote_msisdn = incoming_req->calling; |
| 245 | } |
| 246 | |
| 247 | mncc_call_fsm_update_id(mncc_call); |
| 248 | } |
| 249 | |
| 250 | /* Remote PBX asks for RTP_CREATE. This merely asks us to create an RTP stream, and does not actually contain any useful |
| 251 | * information like the remote RTP IP:port (these follow in the RTP_CONNECT from the SIP side) */ |
| 252 | static bool mncc_call_rx_rtp_create(struct mncc_call *mncc_call) |
| 253 | { |
| 254 | mncc_call->received_rtp_create = true; |
| 255 | |
| 256 | if (!mncc_call->rtps) { |
| 257 | LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, but no RTP stream associated\n"); |
| 258 | return true; |
| 259 | } |
| 260 | |
Neels Hofmeyr | 84ce206 | 2019-10-05 05:15:25 +0200 | [diff] [blame] | 261 | if (!osmo_sockaddr_str_is_nonzero(&mncc_call->rtps->local)) { |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 262 | LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, but RTP stream has no local address\n"); |
| 263 | return true; |
| 264 | } |
| 265 | |
| 266 | if (!mncc_call->rtps->codec_known) { |
| 267 | LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, but RTP stream has no codec set\n"); |
| 268 | return true; |
| 269 | } |
| 270 | |
| 271 | LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, responding with " OSMO_SOCKADDR_STR_FMT " %s\n", |
| 272 | OSMO_SOCKADDR_STR_FMT_ARGS(&mncc_call->rtps->local), |
| 273 | osmo_mgcpc_codec_name(mncc_call->rtps->codec)); |
| 274 | /* Already know what RTP IP:port to tell the MNCC. Send it. */ |
| 275 | return mncc_call_tx_rtp_create(mncc_call); |
| 276 | } |
| 277 | |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 278 | static bool mncc_call_tx_rtp_create(struct mncc_call *mncc_call) |
| 279 | { |
Neels Hofmeyr | 84ce206 | 2019-10-05 05:15:25 +0200 | [diff] [blame] | 280 | if (!mncc_call->rtps || !osmo_sockaddr_str_is_nonzero(&mncc_call->rtps->local)) { |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 281 | mncc_call_error(mncc_call, "Cannot send RTP_CREATE, no local RTP address set up\n"); |
| 282 | return false; |
| 283 | } |
| 284 | struct osmo_sockaddr_str *rtp_local = &mncc_call->rtps->local; |
| 285 | union mncc_msg mncc_msg = { |
| 286 | .rtp = { |
| 287 | .msg_type = MNCC_RTP_CREATE, |
| 288 | .callref = mncc_call->callref, |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 289 | }, |
| 290 | }; |
| 291 | |
Pau Espin Pedrol | eeda9e1 | 2020-09-03 22:11:03 +0200 | [diff] [blame] | 292 | if (osmo_sockaddr_str_to_sockaddr(rtp_local, &mncc_msg.rtp.addr)) { |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 293 | mncc_call_error(mncc_call, "Failed to compose IP address " OSMO_SOCKADDR_STR_FMT "\n", |
| 294 | OSMO_SOCKADDR_STR_FMT_ARGS(rtp_local)); |
| 295 | return false; |
| 296 | } |
| 297 | |
| 298 | if (mncc_call->rtps->codec_known) { |
Neels Hofmeyr | a001a70 | 2022-10-31 17:57:30 +0100 | [diff] [blame] | 299 | const struct codec_mapping *m = codec_mapping_by_mgcp_codec(mncc_call->rtps->codec); |
| 300 | |
| 301 | if (!m) { |
| 302 | mncc_call_error(mncc_call, "Failed to resolve audio codec '%s'\n", |
| 303 | osmo_mgcpc_codec_name(mncc_call->rtps->codec)); |
| 304 | return false; |
| 305 | } |
| 306 | mncc_msg.rtp.payload_type = m->sdp.payload_type; |
| 307 | mncc_msg.rtp.payload_msg_type = m->mncc_payload_msg_type; |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 308 | } |
| 309 | |
| 310 | if (mncc_call_tx(mncc_call, &mncc_msg)) |
| 311 | return false; |
| 312 | return true; |
| 313 | } |
| 314 | |
| 315 | static bool mncc_call_rx_rtp_connect(struct mncc_call *mncc_call, const struct gsm_mncc_rtp *mncc_msg) |
| 316 | { |
| 317 | struct osmo_sockaddr_str rtp; |
| 318 | |
| 319 | if (!mncc_call->rtps) { |
| 320 | /* The user has not associated an RTP stream, hence we're not supposed to take any action here. */ |
| 321 | return true; |
| 322 | } |
| 323 | |
Pau Espin Pedrol | eeda9e1 | 2020-09-03 22:11:03 +0200 | [diff] [blame] | 324 | if (osmo_sockaddr_str_from_sockaddr(&rtp, &mncc_msg->addr)) { |
Neels Hofmeyr | c4628a3 | 2018-12-07 14:47:34 +0100 | [diff] [blame] | 325 | mncc_call_error(mncc_call, "Cannot RTP-CONNECT, invalid RTP IP:port in incoming MNCC message\n"); |
| 326 | return false; |
| 327 | } |
| 328 | |
| 329 | rtp_stream_set_remote_addr(mncc_call->rtps, &rtp); |
| 330 | if (rtp_stream_commit(mncc_call->rtps)) { |
| 331 | mncc_call_error(mncc_call, "RTP-CONNECT, failed, RTP stream is not properly set up: %s\n", |
| 332 | mncc_call->rtps->fi->id); |
| 333 | return false; |
| 334 | } |
| 335 | return true; |
| 336 | } |
| 337 | |
| 338 | /* Return true if the FSM instance still exists after this call, false if it was terminated. */ |
| 339 | static bool mncc_call_rx_release_msg(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg) |
| 340 | { |
| 341 | switch (mncc_msg->msg_type) { |
| 342 | case MNCC_DISC_REQ: |
| 343 | /* Remote call leg ended the call, MNCC tells us to DISC. We ack with a REL. */ |
| 344 | mncc_call_tx_msgt(mncc_call, MNCC_REL_IND); |
| 345 | osmo_fsm_inst_term(mncc_call->fi, OSMO_FSM_TERM_REGULAR, 0); |
| 346 | return false; |
| 347 | |
| 348 | case MNCC_REL_REQ: |
| 349 | /* MNCC acks with a REL to a previous DISC IND we have (probably) sent. |
| 350 | * We ack with a REL CNF. */ |
| 351 | mncc_call_tx_msgt(mncc_call, MNCC_REL_CNF); |
| 352 | osmo_fsm_inst_term(mncc_call->fi, OSMO_FSM_TERM_REGULAR, 0); |
| 353 | return false; |
| 354 | |
| 355 | default: |
| 356 | return true; |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | /* Return true if the FSM instance still exists after this call, false if it was terminated. */ |
| 361 | static bool mncc_call_rx_common_msg(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg) |
| 362 | { |
| 363 | switch (mncc_msg->msg_type) { |
| 364 | case MNCC_RTP_CREATE: |
| 365 | mncc_call_rx_rtp_create(mncc_call); |
| 366 | return true; |
| 367 | |
| 368 | case MNCC_RTP_CONNECT: |
| 369 | mncc_call_rx_rtp_connect(mncc_call, &mncc_msg->rtp); |
| 370 | return true; |
| 371 | |
| 372 | default: |
| 373 | return mncc_call_rx_release_msg(mncc_call, mncc_msg); |
| 374 | } |
| 375 | } |
| 376 | |
| 377 | static void mncc_call_forward(struct mncc_call *mncc_call, const union mncc_msg *mncc_msg) |
| 378 | { |
| 379 | if (!mncc_call || !mncc_call->message_cb) |
| 380 | return; |
| 381 | mncc_call->message_cb(mncc_call, mncc_msg, mncc_call->forward_cb_data); |
| 382 | } |
| 383 | |
| 384 | /* Initiate an outgoing call. |
| 385 | * The outgoing_req represents the details for the MNCC_SETUP_IND message sent to initiate the outgoing call. Pass at |
| 386 | * least a called number (set outgoing_req->fields |= MNCC_F_CALLED and populate outgoing_req->called). All other items |
| 387 | * are optional and can be included if required. The message type, callref and IMSI from this struct are ignored, |
| 388 | * instead they are determined internally upon sending the MNCC message. If no calling number is set in the message |
| 389 | * struct, it will be set from mncc_call->vsub->msisdn. |
| 390 | */ |
| 391 | int mncc_call_outgoing_start(struct mncc_call *mncc_call, const struct gsm_mncc *outgoing_req) |
| 392 | { |
| 393 | if (!mncc_call) |
| 394 | return -EINVAL; |
| 395 | /* By dispatching an event instead of taking direct action, make sure that the FSM permits starting an outgoing |
| 396 | * call. */ |
| 397 | return osmo_fsm_inst_dispatch(mncc_call->fi, MNCC_CALL_EV_OUTGOING_START, (void*)outgoing_req); |
| 398 | } |
| 399 | |
| 400 | /* Handle an incoming call. |
| 401 | * When the MNCC recv callback (not included in this mncc_call_fsm API) detects an incoming call (MNCC_SETUP_REQ), take over |
| 402 | * handling of the incoming call by the given mncc_call instance. |
| 403 | * In incoming_req->setup_req_msg, pass the struct gsm_mncc message containing the received MNCC_SETUP_REQ. |
| 404 | * mncc_call_incoming_start() will immediately respond with a MNCC_CALL_CONF_IND; in incoming_req->bearer_cap, pass the |
| 405 | * bearer capabilities that should be included in this MNCC_CALL_CONF_IND message; in incoming_req->cccap, pass the |
| 406 | * CCCAP to be sent, if any. |
| 407 | */ |
| 408 | int mncc_call_incoming_start(struct mncc_call *mncc_call, const struct mncc_call_incoming_req *incoming_req) |
| 409 | { |
| 410 | if (!mncc_call) |
| 411 | return -EINVAL; |
| 412 | /* By dispatching an event instead of taking direct action, make sure that the FSM permits starting an incoming |
| 413 | * call. */ |
| 414 | return osmo_fsm_inst_dispatch(mncc_call->fi, MNCC_CALL_EV_INCOMING_START, (void*)incoming_req); |
| 415 | } |
| 416 | |
| 417 | static void mncc_call_incoming_tx_call_conf_ind(struct mncc_call *mncc_call, const struct gsm_mncc_bearer_cap *bearer_cap) |
| 418 | { |
| 419 | if (mncc_call->fi->state != MNCC_CALL_ST_INCOMING_WAIT_COMPLETE) { |
| 420 | LOG_MNCC_CALL(mncc_call, LOGL_ERROR, "%s not allowed in this state\n", __func__); |
| 421 | return; |
| 422 | } |
| 423 | |
| 424 | union mncc_msg mncc_msg = { |
| 425 | .signal = { |
| 426 | .msg_type = MNCC_CALL_CONF_IND, |
| 427 | .callref = mncc_call->callref, |
| 428 | }, |
| 429 | }; |
| 430 | |
| 431 | if (bearer_cap) { |
| 432 | mncc_msg.signal.fields |= MNCC_F_BEARER_CAP; |
| 433 | mncc_msg.signal.bearer_cap = *bearer_cap; |
| 434 | } |
| 435 | |
| 436 | mncc_call_tx(mncc_call, &mncc_msg); |
| 437 | } |
| 438 | |
| 439 | /* Send an MNCC_SETUP_CNF message. Typically after the local side is ready to receive the call and RTP (e.g. for a GSM |
| 440 | * CC call, the lchan and RTP should be ready and the CC call should have been confirmed and alerting). |
| 441 | * For inter-MSC call forwarding, this can happen immediately upon the MNCC_RTP_CREATE. |
| 442 | */ |
| 443 | int mncc_call_incoming_tx_setup_cnf(struct mncc_call *mncc_call, const struct gsm_mncc_number *connected_number) |
| 444 | { |
| 445 | if (mncc_call->fi->state != MNCC_CALL_ST_INCOMING_WAIT_COMPLETE) { |
| 446 | LOG_MNCC_CALL(mncc_call, LOGL_ERROR, "%s not allowed in this state\n", __func__); |
| 447 | return -EINVAL; |
| 448 | } |
| 449 | |
| 450 | union mncc_msg mncc_msg = { |
| 451 | .signal = { |
| 452 | .msg_type = MNCC_SETUP_CNF, |
| 453 | .callref = mncc_call->callref, |
| 454 | }, |
| 455 | }; |
| 456 | |
| 457 | if (connected_number) { |
| 458 | mncc_msg.signal.fields |= MNCC_F_CONNECTED; |
| 459 | mncc_msg.signal.connected = *connected_number; |
| 460 | } |
| 461 | |
| 462 | return mncc_call_tx(mncc_call, &mncc_msg); |
| 463 | } |
| 464 | |
| 465 | static void mncc_call_fsm_not_started(struct osmo_fsm_inst *fi, uint32_t event, void *data) |
| 466 | { |
| 467 | struct mncc_call *mncc_call = fi->priv; |
| 468 | const struct gsm_mncc *outgoing_req; |
| 469 | const struct mncc_call_incoming_req *incoming_req; |
| 470 | |
| 471 | switch (event) { |
| 472 | case MNCC_CALL_EV_OUTGOING_START: |
| 473 | outgoing_req = data; |
| 474 | mncc_call->outgoing_req = *outgoing_req; |
| 475 | mncc_call->callref = msc_cc_next_outgoing_callref(); |
| 476 | mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_OUTGOING_WAIT_PROCEEDING); |
| 477 | mncc_call_tx_setup_ind(mncc_call); |
| 478 | return; |
| 479 | |
| 480 | case MNCC_CALL_EV_INCOMING_START: |
| 481 | incoming_req = data; |
| 482 | mncc_call_rx_setup_req(mncc_call, &incoming_req->setup_req_msg); |
| 483 | mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_INCOMING_WAIT_COMPLETE); |
| 484 | mncc_call_incoming_tx_call_conf_ind(mncc_call, incoming_req->bearer_cap_present ? &incoming_req->bearer_cap : NULL); |
| 485 | return; |
| 486 | |
| 487 | default: |
| 488 | OSMO_ASSERT(false); |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | static void mncc_call_fsm_outgoing_wait_proceeding(struct osmo_fsm_inst *fi, uint32_t event, void *data) |
| 493 | { |
| 494 | struct mncc_call *mncc_call = fi->priv; |
| 495 | const union mncc_msg *mncc_msg; |
| 496 | |
| 497 | switch (event) { |
| 498 | case MNCC_CALL_EV_RX_MNCC_MSG: |
| 499 | mncc_msg = data; |
| 500 | if (!mncc_call_rx_common_msg(mncc_call, mncc_msg)) |
| 501 | return; |
| 502 | |
| 503 | switch (mncc_msg->msg_type) { |
| 504 | case MNCC_CALL_PROC_REQ: |
| 505 | mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_OUTGOING_WAIT_COMPLETE); |
| 506 | break; |
| 507 | default: |
| 508 | break; |
| 509 | } |
| 510 | |
| 511 | mncc_call_forward(mncc_call, mncc_msg); |
| 512 | return; |
| 513 | |
| 514 | default: |
| 515 | OSMO_ASSERT(false); |
| 516 | }; |
| 517 | } |
| 518 | |
| 519 | static void mncc_call_fsm_outgoing_wait_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data) |
| 520 | { |
| 521 | struct mncc_call *mncc_call = fi->priv; |
| 522 | const union mncc_msg *mncc_msg; |
| 523 | |
| 524 | switch (event) { |
| 525 | case MNCC_CALL_EV_RX_MNCC_MSG: |
| 526 | mncc_msg = data; |
| 527 | if (!mncc_call_rx_common_msg(mncc_call, mncc_msg)) |
| 528 | return; |
| 529 | |
| 530 | switch (mncc_msg->msg_type) { |
| 531 | case MNCC_SETUP_RSP: |
| 532 | mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_TALKING); |
| 533 | mncc_call_tx_msgt(mncc_call, MNCC_SETUP_COMPL_IND); |
| 534 | break; |
| 535 | default: |
| 536 | break; |
| 537 | } |
| 538 | |
| 539 | mncc_call_forward(mncc_call, mncc_msg); |
| 540 | return; |
| 541 | |
| 542 | default: |
| 543 | OSMO_ASSERT(false); |
| 544 | }; |
| 545 | } |
| 546 | |
| 547 | static void mncc_call_fsm_incoming_wait_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data) |
| 548 | { |
| 549 | struct mncc_call *mncc_call = fi->priv; |
| 550 | const union mncc_msg *mncc_msg; |
| 551 | |
| 552 | switch (event) { |
| 553 | case MNCC_CALL_EV_RX_MNCC_MSG: |
| 554 | mncc_msg = data; |
| 555 | if (!mncc_call_rx_common_msg(mncc_call, mncc_msg)) |
| 556 | return; |
| 557 | |
| 558 | switch (mncc_msg->msg_type) { |
| 559 | case MNCC_SETUP_COMPL_REQ: |
| 560 | mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_TALKING); |
| 561 | break; |
| 562 | default: |
| 563 | break; |
| 564 | } |
| 565 | |
| 566 | mncc_call_forward(mncc_call, mncc_msg); |
| 567 | return; |
| 568 | |
| 569 | default: |
| 570 | OSMO_ASSERT(false); |
| 571 | }; |
| 572 | } |
| 573 | |
| 574 | static void mncc_call_fsm_talking(struct osmo_fsm_inst *fi, uint32_t event, void *data) |
| 575 | { |
| 576 | struct mncc_call *mncc_call = fi->priv; |
| 577 | const union mncc_msg *mncc_msg; |
| 578 | |
| 579 | switch (event) { |
| 580 | case MNCC_CALL_EV_RX_MNCC_MSG: |
| 581 | mncc_msg = data; |
| 582 | if (!mncc_call_rx_common_msg(mncc_call, mncc_msg)) |
| 583 | return; |
| 584 | mncc_call_forward(mncc_call, mncc_msg); |
| 585 | return; |
| 586 | |
| 587 | default: |
| 588 | OSMO_ASSERT(false); |
| 589 | }; |
| 590 | } |
| 591 | |
| 592 | static void mncc_call_fsm_wait_release_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) |
| 593 | { |
| 594 | struct mncc_call *mncc_call = fi->priv; |
| 595 | const union mncc_msg *mncc_msg; |
| 596 | |
| 597 | switch (event) { |
| 598 | case MNCC_CALL_EV_RX_MNCC_MSG: |
| 599 | mncc_msg = data; |
| 600 | if (!mncc_call_rx_release_msg(mncc_call, mncc_msg)) |
| 601 | return; |
| 602 | mncc_call_forward(mncc_call, mncc_msg); |
| 603 | return; |
| 604 | |
| 605 | default: |
| 606 | OSMO_ASSERT(false); |
| 607 | }; |
| 608 | } |
| 609 | |
| 610 | static void mncc_call_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) |
| 611 | { |
| 612 | struct mncc_call *mncc_call = fi->priv; |
| 613 | |
| 614 | switch (fi->state) { |
| 615 | case MNCC_CALL_ST_NOT_STARTED: |
| 616 | case MNCC_CALL_ST_WAIT_RELEASE_ACK: |
| 617 | break; |
| 618 | default: |
| 619 | /* Make sure we did indicate some sort of release */ |
| 620 | mncc_call_tx_msgt(mncc_call, MNCC_REL_IND); |
| 621 | break; |
| 622 | } |
| 623 | |
| 624 | /* Releasing the RTP stream should trigger completely tearing down the call leg as well as the CC transaction. |
| 625 | * In case of an inter-MSC handover where this MNCC connection is replaced by another MNCC / another BSC |
| 626 | * connection, the caller needs to detach the RTP stream from this FSM before terminating it. */ |
| 627 | if (mncc_call->rtps) { |
| 628 | rtp_stream_release(mncc_call->rtps); |
| 629 | mncc_call->rtps = NULL; |
| 630 | } |
| 631 | |
| 632 | llist_del(&mncc_call->entry); |
| 633 | } |
| 634 | |
| 635 | static int mncc_call_fsm_timer_cb(struct osmo_fsm_inst *fi) |
| 636 | { |
| 637 | return 1; |
| 638 | } |
| 639 | |
| 640 | #define S(x) (1 << (x)) |
| 641 | |
| 642 | static const struct osmo_fsm_state mncc_call_fsm_states[] = { |
| 643 | [MNCC_CALL_ST_NOT_STARTED] = { |
| 644 | .name = "NOT_STARTED", |
| 645 | .in_event_mask = 0 |
| 646 | | S(MNCC_CALL_EV_OUTGOING_START) |
| 647 | | S(MNCC_CALL_EV_INCOMING_START) |
| 648 | , |
| 649 | .out_state_mask = 0 |
| 650 | | S(MNCC_CALL_ST_OUTGOING_WAIT_PROCEEDING) |
| 651 | | S(MNCC_CALL_ST_INCOMING_WAIT_COMPLETE) |
| 652 | , |
| 653 | .action = mncc_call_fsm_not_started, |
| 654 | }, |
| 655 | [MNCC_CALL_ST_OUTGOING_WAIT_PROCEEDING] = { |
| 656 | .name = "OUTGOING_WAIT_PROCEEDING", |
| 657 | .in_event_mask = 0 |
| 658 | | S(MNCC_CALL_EV_RX_MNCC_MSG) |
| 659 | , |
| 660 | .out_state_mask = 0 |
| 661 | | S(MNCC_CALL_ST_OUTGOING_WAIT_COMPLETE) |
| 662 | | S(MNCC_CALL_ST_WAIT_RELEASE_ACK) |
| 663 | , |
| 664 | .action = mncc_call_fsm_outgoing_wait_proceeding, |
| 665 | }, |
| 666 | [MNCC_CALL_ST_OUTGOING_WAIT_COMPLETE] = { |
| 667 | .name = "OUTGOING_WAIT_COMPLETE", |
| 668 | .in_event_mask = 0 |
| 669 | | S(MNCC_CALL_EV_RX_MNCC_MSG) |
| 670 | , |
| 671 | .out_state_mask = 0 |
| 672 | | S(MNCC_CALL_ST_TALKING) |
| 673 | | S(MNCC_CALL_ST_WAIT_RELEASE_ACK) |
| 674 | , |
| 675 | .action = mncc_call_fsm_outgoing_wait_complete, |
| 676 | }, |
| 677 | [MNCC_CALL_ST_INCOMING_WAIT_COMPLETE] = { |
| 678 | .name = "INCOMING_WAIT_COMPLETE", |
| 679 | .in_event_mask = 0 |
| 680 | | S(MNCC_CALL_EV_RX_MNCC_MSG) |
| 681 | , |
| 682 | .out_state_mask = 0 |
| 683 | | S(MNCC_CALL_ST_TALKING) |
| 684 | | S(MNCC_CALL_ST_WAIT_RELEASE_ACK) |
| 685 | , |
| 686 | .action = mncc_call_fsm_incoming_wait_complete, |
| 687 | }, |
| 688 | [MNCC_CALL_ST_TALKING] = { |
| 689 | .name = "TALKING", |
| 690 | .in_event_mask = 0 |
| 691 | | S(MNCC_CALL_EV_RX_MNCC_MSG) |
| 692 | , |
| 693 | .out_state_mask = 0 |
| 694 | | S(MNCC_CALL_ST_WAIT_RELEASE_ACK) |
| 695 | , |
| 696 | .action = mncc_call_fsm_talking, |
| 697 | }, |
| 698 | [MNCC_CALL_ST_WAIT_RELEASE_ACK] = { |
| 699 | .name = "WAIT_RELEASE_ACK", |
| 700 | .in_event_mask = 0 |
| 701 | | S(MNCC_CALL_EV_RX_MNCC_MSG) |
| 702 | , |
| 703 | .action = mncc_call_fsm_wait_release_ack, |
| 704 | }, |
| 705 | }; |
| 706 | |
| 707 | static const struct value_string mncc_call_fsm_event_names[] = { |
| 708 | OSMO_VALUE_STRING(MNCC_CALL_EV_RX_MNCC_MSG), |
| 709 | |
| 710 | OSMO_VALUE_STRING(MNCC_CALL_EV_OUTGOING_START), |
| 711 | OSMO_VALUE_STRING(MNCC_CALL_EV_OUTGOING_ALERTING), |
| 712 | OSMO_VALUE_STRING(MNCC_CALL_EV_OUTGOING_SETUP_COMPLETE), |
| 713 | |
| 714 | OSMO_VALUE_STRING(MNCC_CALL_EV_INCOMING_START), |
| 715 | OSMO_VALUE_STRING(MNCC_CALL_EV_INCOMING_SETUP), |
| 716 | OSMO_VALUE_STRING(MNCC_CALL_EV_INCOMING_SETUP_COMPLETE), |
| 717 | |
| 718 | OSMO_VALUE_STRING(MNCC_CALL_EV_CN_RELEASE), |
| 719 | OSMO_VALUE_STRING(MNCC_CALL_EV_MS_RELEASE), |
| 720 | {} |
| 721 | }; |
| 722 | |
| 723 | struct osmo_fsm mncc_call_fsm = { |
| 724 | .name = "mncc_call", |
| 725 | .states = mncc_call_fsm_states, |
| 726 | .num_states = ARRAY_SIZE(mncc_call_fsm_states), |
| 727 | .log_subsys = DMNCC, |
| 728 | .event_names = mncc_call_fsm_event_names, |
| 729 | .timer_cb = mncc_call_fsm_timer_cb, |
| 730 | .cleanup = mncc_call_fsm_cleanup, |
| 731 | }; |
| 732 | |
| 733 | struct mncc_call *mncc_call_find_by_callref(uint32_t callref) |
| 734 | { |
| 735 | struct mncc_call *mncc_call; |
| 736 | llist_for_each_entry(mncc_call, &mncc_call_list, entry) { |
| 737 | if (mncc_call->callref == callref) |
| 738 | return mncc_call; |
| 739 | } |
| 740 | return NULL; |
| 741 | } |
| 742 | |
| 743 | void mncc_call_release(struct mncc_call *mncc_call) |
| 744 | { |
| 745 | if (!mncc_call) |
| 746 | return; |
| 747 | mncc_call_tx_msgt(mncc_call, MNCC_DISC_IND); |
| 748 | mncc_call_fsm_state_chg(mncc_call, MNCC_CALL_ST_WAIT_RELEASE_ACK); |
| 749 | } |