/* Implementation to manage two RTP streams that make up an MO or MT call leg's RTP forwarding. */
/*
 * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 * All Rights Reserved
 *
 * Author: Neels Hofmeyr
 *
 * SPDX-License-Identifier: AGPL-3.0+
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 */
#include <osmocom/core/fsm.h>
#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>

#include <osmocom/msc/debug.h>
#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/msc_a.h>

#include <osmocom/msc/call_leg.h>
#include <osmocom/msc/rtp_stream.h>

#define LOG_CALL_LEG(cl, level, fmt, args...) \
	LOGPFSML(cl ? cl->fi : NULL, level, fmt, ##args)

static struct gsm_network *gsmnet = NULL;

enum call_leg_state {
	CALL_LEG_ST_ESTABLISHING,
	CALL_LEG_ST_ESTABLISHED,
	CALL_LEG_ST_RELEASING,
};

struct osmo_tdef g_mgw_tdefs[] = {
	{ .T=-2427, .default_val=4, .desc="MGCP response timeout" },
	{ .T=-2, .default_val=30, .desc="RTP stream establishing timeout" },
	{}
};

static const struct osmo_tdef_state_timeout call_leg_fsm_timeouts[32] = {
	[CALL_LEG_ST_ESTABLISHING] = { .T = -2 },
	[CALL_LEG_ST_RELEASING] = { .T = -2 },
};

#define call_leg_state_chg(cl, state) \
	osmo_tdef_fsm_inst_state_chg((cl)->fi, state, call_leg_fsm_timeouts, g_mgw_tdefs, 10)

static struct osmo_fsm call_leg_fsm;

void call_leg_init(struct gsm_network *net)
{
	gsmnet = net;
	OSMO_ASSERT( osmo_fsm_register(&call_leg_fsm) == 0 );
}

/* Allocate a call leg FSM instance as child of an arbitrary other FSM instance.
 * The call leg FSM dispatches events to its parent FSM instance on specific events:
 * - parent_event_term: dispatch this to the parent FI when the call leg terminates (call ended, either planned or by
 *   failure).
 * - parent_event_rtp_addr_available: one of the rtp_stream instances managed by the call leg has received an RTP
 *   address from the MGW. The struct rtp_stream instance is passed as data argument for the event dispatch.
 * - parent_event_rtp_complete: one of the rtp_stream instances entered the RTP_STREAM_ST_ESTABLISHED state.
 */
struct call_leg *call_leg_alloc(struct osmo_fsm_inst *parent_fi,
				uint32_t parent_event_term,
				uint32_t parent_event_rtp_addr_available,
				uint32_t parent_event_rtp_complete)
{
	struct call_leg *cl;
	struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&call_leg_fsm, parent_fi, parent_event_term);

	OSMO_ASSERT(fi);

	cl = talloc(fi, struct call_leg);
	OSMO_ASSERT(cl);
	fi->priv = cl;
	*cl = (struct call_leg){
		.fi = fi,
		.parent_event_rtp_addr_available = parent_event_rtp_addr_available,
		.parent_event_rtp_complete = parent_event_rtp_complete,
	};

	return cl;
}

void call_leg_reparent(struct call_leg *cl,
		       struct osmo_fsm_inst *new_parent_fi,
		       uint32_t parent_event_term,
		       uint32_t parent_event_rtp_addr_available,
		       uint32_t parent_event_rtp_complete)
{
	LOG_CALL_LEG(cl, LOGL_DEBUG, "Reparenting from parent %s to parent %s\n",
		     cl->fi->proc.parent->name, new_parent_fi->name);
	osmo_fsm_inst_change_parent(cl->fi, new_parent_fi, parent_event_term);
	talloc_steal(new_parent_fi, cl->fi);
	cl->parent_event_rtp_addr_available = parent_event_rtp_addr_available;
	cl->parent_event_rtp_complete = parent_event_rtp_complete;
}

static int call_leg_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
	struct call_leg *cl = fi->priv;
	call_leg_release(cl);
	return 0;
}

void call_leg_release(struct call_leg *cl)
{
	if (!cl)
		return;
	if (cl->fi->state == CALL_LEG_ST_RELEASING)
		return;
	call_leg_state_chg(cl, CALL_LEG_ST_RELEASING);
}

static void call_leg_mgw_endpoint_gone(struct call_leg *cl)
{
	struct mgcp_client *mgcp_client;
	int i;

	/* Put MGCP client back into MGW pool */
	mgcp_client = osmo_mgcpc_ep_client(cl->mgw_endpoint);
	mgcp_client_pool_put(mgcp_client);

	cl->mgw_endpoint = NULL;
	for (i = 0; i < ARRAY_SIZE(cl->rtp); i++) {
		if (!cl->rtp[i])
			continue;
		cl->rtp[i]->ci = NULL;
	}
}

static void call_leg_fsm_establishing_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
	struct call_leg *cl = fi->priv;
	struct rtp_stream *rtps;
	int i;
	bool established;

	switch (event) {

	case CALL_LEG_EV_RTP_STREAM_ESTABLISHED:
		/* An rtp_stream says it is established. If all are now established, change to state
		 * CALL_LEG_ST_ESTABLISHED. */
		established = true;
		for (i = 0; i < ARRAY_SIZE(cl->rtp); i++) {
			if (!rtp_stream_is_established(cl->rtp[i])) {
				established = false;
				break;
			}
		}
		if (!established)
			break;
		if (cl->fi->state != CALL_LEG_ST_ESTABLISHED)
			call_leg_state_chg(cl, CALL_LEG_ST_ESTABLISHED);
		break;

	case CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE:
		rtps = data;
		osmo_fsm_inst_dispatch(fi->proc.parent, cl->parent_event_rtp_addr_available, rtps);
		break;

	case CALL_LEG_EV_RTP_STREAM_GONE:
		call_leg_release(cl);
		break;

	case CALL_LEG_EV_MGW_ENDPOINT_GONE:
		call_leg_mgw_endpoint_gone(cl);
		call_leg_release(cl);
		break;

	default:
		OSMO_ASSERT(false);
	}
}

void call_leg_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
	struct call_leg *cl = fi->priv;
	osmo_fsm_inst_dispatch(fi->proc.parent, cl->parent_event_rtp_complete, cl);
}

void call_leg_fsm_releasing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
	/* Trigger termination of children FSMs (rtp_stream(s)) before
	 * terminating ourselves, otherwise we are not able to receive
	 * CALL_LEG_EV_MGW_ENDPOINT_GONE from cl->mgw_endpoint (call_leg =>
	 * rtp_stream => mgw_endpoint), because osmo_fsm disabled dispatching
	 * events to an FSM in process of terminating. */
	osmo_fsm_inst_term_children(fi, OSMO_FSM_TERM_PARENT, NULL);
	osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
}

static void call_leg_fsm_releasing(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
	struct call_leg *cl = fi->priv;

	switch (event) {

	case CALL_LEG_EV_RTP_STREAM_GONE:
		/* We're already terminating, child RTP streams will also terminate, there is nothing left to do. */
		break;

	case CALL_LEG_EV_MGW_ENDPOINT_GONE:
		call_leg_mgw_endpoint_gone(cl);
		break;

	default:
		OSMO_ASSERT(false);
	}
}

static const struct value_string call_leg_fsm_event_names[] = {
	OSMO_VALUE_STRING(CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE),
	OSMO_VALUE_STRING(CALL_LEG_EV_RTP_STREAM_ESTABLISHED),
	OSMO_VALUE_STRING(CALL_LEG_EV_RTP_STREAM_GONE),
	OSMO_VALUE_STRING(CALL_LEG_EV_MGW_ENDPOINT_GONE),
	{}
};

#define S(x)	(1 << (x))

static const struct osmo_fsm_state call_leg_fsm_states[] = {
	[CALL_LEG_ST_ESTABLISHING] = {
		.name = "ESTABLISHING",
		.in_event_mask = 0
			| S(CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE)
			| S(CALL_LEG_EV_RTP_STREAM_ESTABLISHED)
			| S(CALL_LEG_EV_RTP_STREAM_GONE)
			| S(CALL_LEG_EV_MGW_ENDPOINT_GONE)
			,
		.out_state_mask = 0
			| S(CALL_LEG_ST_ESTABLISHED)
			| S(CALL_LEG_ST_RELEASING)
			,
		.action = call_leg_fsm_establishing_established,
	},
	[CALL_LEG_ST_ESTABLISHED] = {
		.name = "ESTABLISHED",
		.in_event_mask = 0
			| S(CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE)
			| S(CALL_LEG_EV_RTP_STREAM_ESTABLISHED)
			| S(CALL_LEG_EV_RTP_STREAM_GONE)
			| S(CALL_LEG_EV_MGW_ENDPOINT_GONE)
			,
		.out_state_mask = 0
			| S(CALL_LEG_ST_ESTABLISHING)
			| S(CALL_LEG_ST_RELEASING)
			,
		.onenter = call_leg_fsm_established_onenter,
		.action = call_leg_fsm_establishing_established, /* same action function as above */
	},
	[CALL_LEG_ST_RELEASING] = {
		.name = "RELEASING",
		.in_event_mask = 0
			| S(CALL_LEG_EV_RTP_STREAM_GONE)
			| S(CALL_LEG_EV_MGW_ENDPOINT_GONE)
			,
		.onenter = call_leg_fsm_releasing_onenter,
		.action = call_leg_fsm_releasing,
	},
};

static struct osmo_fsm call_leg_fsm = {
	.name = "call_leg",
	.states = call_leg_fsm_states,
	.num_states = ARRAY_SIZE(call_leg_fsm_states),
	.log_subsys = DCC,
	.event_names = call_leg_fsm_event_names,
	.timer_cb = call_leg_fsm_timer_cb,
};

const struct value_string rtp_direction_names[] = {
	OSMO_VALUE_STRING(RTP_TO_RAN),
	OSMO_VALUE_STRING(RTP_TO_CN),
	{}
};

int call_leg_ensure_rtp_alloc(struct call_leg *cl, enum rtp_direction dir, uint32_t call_id, struct gsm_trans *for_trans)
{
	if (cl->rtp[dir])
		return 0;

	if (!cl->mgw_endpoint) {
		struct mgcp_client *mgcp_client = mgcp_client_pool_get(gsmnet->mgw.mgw_pool);
		if (!mgcp_client) {
			LOG_CALL_LEG(cl, LOGL_ERROR,
				     "cannot ensure MGW endpoint -- no MGW configured, check configuration!\n");
			return -ENODEV;
		}
		cl->mgw_endpoint = osmo_mgcpc_ep_alloc(cl->fi, CALL_LEG_EV_MGW_ENDPOINT_GONE,
						       mgcp_client, gsmnet->mgw.tdefs, cl->fi->id,
						       "%s", mgcp_client_rtpbridge_wildcard(mgcp_client));
	}
	if (!cl->mgw_endpoint) {
		LOG_CALL_LEG(cl, LOGL_ERROR, "failed to setup MGW endpoint\n");
		return -EIO;
	}

	cl->rtp[dir] = rtp_stream_alloc(cl->fi, CALL_LEG_EV_RTP_STREAM_GONE, CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE,
					CALL_LEG_EV_RTP_STREAM_ESTABLISHED, dir, call_id, for_trans);
	OSMO_ASSERT(cl->rtp[dir]);
	return 0;
}

struct osmo_sockaddr_str *call_leg_local_ip(struct call_leg *cl, enum rtp_direction dir)
{
	struct rtp_stream *rtps;
	if (!cl)
		return NULL;
	rtps = cl->rtp[dir];
	if (!rtps)
		return NULL;
	if (!osmo_sockaddr_str_is_nonzero(&rtps->local))
		return NULL;
	return &rtps->local;
}

/* Make sure an MGW endpoint CI is set up for an RTP connection.
 * This is the one-stop for all to either completely set up a new endpoint connection, or to modify an existing one.
 * If not yet present, allocate the rtp_stream for the given direction.
 * Then, call rtp_stream_set_codecs() if codecs_if_known is non-NULL, and/or rtp_stream_set_remote_addr() if
 * remote_addr_if_known is non-NULL.
 * Finally make sure that a CRCX is sent out for this direction, if this has not already happened.
 * If the CRCX has already happened but new codec / remote_addr data was passed, call rtp_stream_commit() to trigger an
 * MDCX.
 */
int call_leg_ensure_ci(struct call_leg *cl, enum rtp_direction dir, uint32_t call_id, struct gsm_trans *for_trans,
		       const struct sdp_audio_codecs *codecs_if_known,
		       const struct osmo_sockaddr_str *remote_addr_if_known)
{
	if (call_leg_ensure_rtp_alloc(cl, dir, call_id, for_trans))
		return -EIO;
	rtp_stream_set_mode(cl->rtp[dir], cl->crcx_conn_mode[dir]);
	if (dir == RTP_TO_RAN && cl->ran_peer_supports_osmux) {
		cl->rtp[dir]->use_osmux = true;
		cl->rtp[dir]->remote_osmux_cid = -1; /* wildcard */
	}
	if (codecs_if_known)
		rtp_stream_set_codecs(cl->rtp[dir], codecs_if_known);
	if (remote_addr_if_known && osmo_sockaddr_str_is_nonzero(remote_addr_if_known))
		rtp_stream_set_remote_addr(cl->rtp[dir], remote_addr_if_known);
	return rtp_stream_ensure_ci(cl->rtp[dir], cl->mgw_endpoint);
}

int call_leg_local_bridge(struct call_leg *cl1, uint32_t call_id1, struct gsm_trans *trans1,
			  struct call_leg *cl2, uint32_t call_id2, struct gsm_trans *trans2)
{
	struct sdp_audio_codecs *cn_codecs = NULL;

	cl1->local_bridge = cl2;
	cl2->local_bridge = cl1;

	/* Marry the two CN sides of the call legs. Call establishment should have made all efforts for these to be
	 * compatible. However, for local bridging, the codecs and payload type numbers must be exactly identical on
	 * both sides. Both sides may so far have different payload type numbers or slightly differing codecs, but it
	 * will only work when the SDP on the RTP_TO_CN sides of the call legs talk the same payload type numbers.
	 * So, simply take the SDP from one RTP_TO_CN side, and overwrite the other RTP_TO_CN side's SDP with it.
	 * If all goes to plan, the codecs will be identical, or possibly the MGW will do a conversion like AMR-BE to
	 * AMR-OA. In the worst case, the other call leg cannot transcode, and the call fails -- because codec
	 * negotiation did not do a good enough job.
	 *
	 * Copy one call leg's CN config to the other:
	 *
	 *     call leg 1         call leg 2
	 *     ---MGW-ep-------   ---MGW-ep-------
	 *     RAN      CN        CN       RAN
	 *     AMR:112  AMR:112   AMR:96   AMR:96
	 *                 |
	 *                 +-------+
	 *                         |
	 *                         V
	 *     AMR:112  AMR:112   AMR:112  AMR:96
	 *                               ^MGW-endpoint converts payload type numbers between 112 and 96.
	 */
	if (cl1->rtp[RTP_TO_CN] && cl1->rtp[RTP_TO_CN]->codecs_known)
		cn_codecs = &cl1->rtp[RTP_TO_CN]->codecs;
	else if (cl2->rtp[RTP_TO_CN] && cl2->rtp[RTP_TO_CN]->codecs_known)
		cn_codecs = &cl2->rtp[RTP_TO_CN]->codecs;
	if (!cn_codecs) {
		LOG_CALL_LEG(cl1, LOGL_ERROR, "RAN-side CN stream codec is not known, not ready for bridging\n");
		LOG_CALL_LEG(cl2, LOGL_ERROR, "RAN-side CN stream codec is not known, not ready for bridging\n");
		return -EINVAL;
	}

	call_leg_ensure_ci(cl1, RTP_TO_CN, call_id1, trans1,
			   cn_codecs, &cl2->rtp[RTP_TO_CN]->local);
	call_leg_ensure_ci(cl2, RTP_TO_CN, call_id2, trans2,
			   cn_codecs, &cl1->rtp[RTP_TO_CN]->local);
	return 0;
}
