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