Merge branch 'zecke/features/rtp-bridge'

* Implement the rtp-bridge mode for MNCC
* Audio doesn't flow through the NITB at all
* It only works with IPv4 BTSes right now
* We need to select an audio codec way too early
* No tandem free operation
* Early assignment always equals TCH/F
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h
index 90f3c80..c167c49 100644
--- a/openbsc/include/openbsc/gsm_data.h
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -120,6 +120,11 @@
 	/* Are we part of a special "silent" call */
 	int silent_call;
 
+	/* MNCC rtp bridge markers */
+	int mncc_rtp_bridge;
+	int mncc_rtp_create_pending;
+	int mncc_rtp_connect_pending;
+
 	/* bsc structures */
 	struct osmo_bsc_sccp_con *sccp_con;
 
diff --git a/openbsc/include/openbsc/mncc.h b/openbsc/include/openbsc/mncc.h
index ffac7fd..46ba237 100644
--- a/openbsc/include/openbsc/mncc.h
+++ b/openbsc/include/openbsc/mncc.h
@@ -92,6 +92,9 @@
 #define MNCC_FRAME_RECV		0x0201
 #define MNCC_FRAME_DROP		0x0202
 #define MNCC_LCHAN_MODIFY	0x0203
+#define MNCC_RTP_CREATE		0x0204
+#define MNCC_RTP_CONNECT	0x0205
+#define MNCC_RTP_FREE		0x0206
 
 #define GSM_TCHF_FRAME		0x0300
 #define GSM_TCHF_FRAME_EFR	0x0301
@@ -163,7 +166,7 @@
 	unsigned char	data[0];
 };
 
-#define MNCC_SOCK_VERSION	4
+#define MNCC_SOCK_VERSION	5
 struct gsm_mncc_hello {
 	uint32_t	msg_type;
 	uint32_t	version;
@@ -179,6 +182,15 @@
 	uint32_t	lchan_type_offset;
 };
 
+struct gsm_mncc_rtp {
+	uint32_t	msg_type;
+	uint32_t	callref;
+	uint32_t	ip;
+	uint16_t	port;
+	uint32_t	payload_type;
+	uint32_t	payload_msg_type;
+};
+
 char *get_mncc_name(int value);
 void mncc_set_cause(struct gsm_mncc *data, int loc, int val);
 void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg);
diff --git a/openbsc/include/openbsc/mncc_int.h b/openbsc/include/openbsc/mncc_int.h
index 4f30f08..213ce14 100644
--- a/openbsc/include/openbsc/mncc_int.h
+++ b/openbsc/include/openbsc/mncc_int.h
@@ -9,4 +9,6 @@
 
 extern struct mncc_int mncc_int;
 
+uint8_t mncc_codec_for_mode(int lchan_type);
+
 #endif
diff --git a/openbsc/src/libmsc/gsm_04_08.c b/openbsc/src/libmsc/gsm_04_08.c
index 435f0dc..7db7586 100644
--- a/openbsc/src/libmsc/gsm_04_08.c
+++ b/openbsc/src/libmsc/gsm_04_08.c
@@ -52,6 +52,7 @@
 #include <openbsc/bsc_api.h>
 #include <openbsc/osmo_msc.h>
 #include <openbsc/handover.h>
+#include <openbsc/mncc_int.h>
 #include <osmocom/abis/e1_input.h>
 #include <osmocom/core/bitvec.h>
 
@@ -67,6 +68,8 @@
 void *tall_locop_ctx;
 void *tall_authciphop_ctx;
 
+static int tch_rtp_signal(struct gsm_lchan *lchan, int signal);
+
 static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn);
 static int gsm48_tx_simple(struct gsm_subscriber_connection *conn,
 			   uint8_t pdisc, uint8_t msg_type);
@@ -1458,6 +1461,15 @@
 {
 	struct rtp_socket *old_rs, *new_rs, *other_rs;
 
+	/* Ask the new socket to send to the already known port. */
+	if (new_lchan->conn->mncc_rtp_bridge) {
+		LOGP(DHO, LOGL_DEBUG, "Forwarding RTP\n");
+		rsl_ipacc_mdcx(new_lchan,
+					old_lchan->abis_ip.connect_ip,
+					old_lchan->abis_ip.connect_port, 0);
+		return 0;
+	}
+
 	if (ipacc_rtp_direct) {
 		LOGP(DHO, LOGL_ERROR, "unable to handover in direct RTP mode\n");
 		return 0;
@@ -1502,11 +1514,19 @@
 	return 0;
 }
 
+static void maybe_switch_for_handover(struct gsm_lchan *lchan)
+{
+	struct gsm_lchan *old_lchan;
+	old_lchan = bsc_handover_pending(lchan);
+	if (old_lchan)
+		switch_for_handover(old_lchan, lchan);
+}
+
 /* some other part of the code sends us a signal */
 static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
 				 void *handler_data, void *signal_data)
 {
-	struct gsm_lchan *lchan = signal_data, *old_lchan;
+	struct gsm_lchan *lchan = signal_data;
 	int rc;
 	struct gsm_network *net;
 	struct gsm_trans *trans;
@@ -1514,6 +1534,10 @@
 	if (subsys != SS_ABISIP)
 		return 0;
 
+	/* RTP bridge handling */
+	if (lchan->conn && lchan->conn->mncc_rtp_bridge)
+		return tch_rtp_signal(lchan, signal);
+
 	/* in case we use direct BTS-to-BTS RTP */
 	if (ipacc_rtp_direct)
 		return 0;
@@ -1555,9 +1579,7 @@
 		 * Do we have a handover pending for this new lchan? In that
 		 * case re-route the audio from the old channel to the new one.
 		 */
-		old_lchan = bsc_handover_pending(lchan);
-		if (old_lchan)
-			switch_for_handover(old_lchan, lchan);
+		maybe_switch_for_handover(lchan);
 		break;
 	case S_ABISIP_DLCX_IND:
 		/* the BTS tells us a RTP stream has been disconnected */
@@ -2900,11 +2922,218 @@
 static int _gsm48_lchan_modify(struct gsm_trans *trans, void *arg)
 {
 	struct gsm_mncc *mode = arg;
+	struct gsm_lchan *lchan = trans->conn->lchan;
+
+	/*
+	 * We were forced to make an assignment a lot earlier and
+	 * we should avoid sending another assignment that might
+	 * even lead to a different kind of lchan (TCH/F vs. TCH/H).
+	 * In case of rtp-bridge it is too late to change things
+	 * here.
+	 */
+	if (trans->conn->mncc_rtp_bridge && lchan->tch_mode != GSM48_CMODE_SIGN)
+		return 0;
 
 	return gsm0808_assign_req(trans->conn, mode->lchan_mode,
 		trans->conn->lchan->type != GSM_LCHAN_TCH_H);
 }
 
+static void mncc_recv_rtp(struct gsm_network *net, uint32_t callref,
+		int cmd, uint32_t addr, uint16_t port, uint32_t payload_type,
+		uint32_t payload_msg_type)
+{
+	uint8_t data[sizeof(struct gsm_mncc)];
+	struct gsm_mncc_rtp *rtp;
+
+	memset(&data, 0, sizeof(data));
+	rtp = (struct gsm_mncc_rtp *) &data[0];
+
+	rtp->callref = callref;
+	rtp->msg_type = cmd;
+	rtp->ip = addr;
+	rtp->port = port;
+	rtp->payload_type = payload_type;
+	rtp->payload_msg_type = payload_msg_type;
+	mncc_recvmsg(net, NULL, cmd, (struct gsm_mncc *)data);
+}
+
+static void mncc_recv_rtp_sock(struct gsm_network *net, struct gsm_trans *trans, int cmd)
+{
+	struct gsm_lchan *lchan;
+	int msg_type;
+
+	lchan = trans->conn->lchan;
+	switch (lchan->abis_ip.rtp_payload) {
+	case RTP_PT_GSM_FULL:
+		msg_type = GSM_TCHF_FRAME;
+		break;
+	case RTP_PT_GSM_EFR:
+		msg_type = GSM_TCHF_FRAME_EFR;
+		break;
+	case RTP_PT_GSM_HALF:
+		msg_type = GSM_TCHH_FRAME;
+		break;
+	case RTP_PT_AMR:
+		msg_type = GSM_TCH_FRAME_AMR;
+		break;
+	default:
+		LOGP(DMNCC, LOGL_ERROR, "%s unknown payload type %d\n",
+			gsm_lchan_name(lchan), lchan->abis_ip.rtp_payload);
+		msg_type = 0;
+		break;
+	}
+
+	return mncc_recv_rtp(net, trans->callref, cmd,
+			lchan->abis_ip.bound_ip,
+			lchan->abis_ip.bound_port,
+			lchan->abis_ip.rtp_payload,
+			msg_type);
+}
+
+static void mncc_recv_rtp_err(struct gsm_network *net, uint32_t callref, int cmd)
+{
+	return mncc_recv_rtp(net, callref, cmd, 0, 0, 0, 0);
+}
+
+static int tch_rtp_create(struct gsm_network *net, uint32_t callref)
+{
+	struct gsm_bts *bts;
+	struct gsm_lchan *lchan;
+	struct gsm_trans *trans;
+
+	/* Find callref */
+	trans = trans_find_by_callref(net, callref);
+	if (!trans) {
+		LOGP(DMNCC, LOGL_ERROR, "RTP create for non-existing trans\n");
+		mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE);
+		return -EIO;
+	}
+	log_set_context(BSC_CTX_SUBSCR, trans->subscr);
+	if (!trans->conn) {
+		LOGP(DMNCC, LOGL_NOTICE, "RTP create for trans without conn\n");
+		mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE);
+		return 0;
+	}
+
+	lchan = trans->conn->lchan;
+	bts = lchan->ts->trx->bts;
+	if (!is_ipaccess_bts(bts)) {
+		/*
+		 * I want this to be straight forward and have no audio flow
+		 * through the nitb/osmo-mss system. This currently means that
+		 * this will not work with BS11/Nokia type BTS. We would need
+		 * to have a trau<->rtp bridge for these but still preferable
+		 * in another process.
+		 */
+		LOGP(DMNCC, LOGL_ERROR, "RTP create only works with IP systems\n");
+		mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE);
+		return -EINVAL;
+	}
+
+	trans->conn->mncc_rtp_bridge = 1;
+	/*
+	 * *sigh* we need to pick a codec now. Pick the most generic one
+	 * right now and hope we could fix that later on. This is very
+	 * similiar to the routine above.
+	 * Fallback to the internal MNCC mode to select a route.
+	 */
+	if (lchan->tch_mode == GSM48_CMODE_SIGN) {
+		trans->conn->mncc_rtp_create_pending = 1;
+		return gsm0808_assign_req(trans->conn,
+				mncc_codec_for_mode(lchan->type),
+				lchan->type != GSM_LCHAN_TCH_H);
+	}
+
+	mncc_recv_rtp_sock(trans->net, trans, MNCC_RTP_CREATE);
+	return 0;
+}
+
+static int tch_rtp_connect(struct gsm_network *net, void *arg)
+{
+	struct gsm_lchan *lchan;
+	struct gsm_trans *trans;
+	struct gsm_mncc_rtp *rtp = arg;
+
+	/* Find callref */
+	trans = trans_find_by_callref(net, rtp->callref);
+	if (!trans) {
+		LOGP(DMNCC, LOGL_ERROR, "RTP connect for non-existing trans\n");
+		mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT);
+		return -EIO;
+	}
+	log_set_context(BSC_CTX_SUBSCR, trans->subscr);
+	if (!trans->conn) {
+		LOGP(DMNCC, LOGL_ERROR, "RTP connect for trans without conn\n");
+		mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT);
+		return 0;
+	}
+
+	lchan = trans->conn->lchan;
+
+	/* TODO: Check if payload_msg_type is compatible with what we have */
+	if (rtp->payload_type != lchan->abis_ip.rtp_payload) {
+		LOGP(DMNCC, LOGL_ERROR, "RTP connect with different RTP payload\n");
+		mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT);
+	}
+
+	/*
+	 * FIXME: payload2 can't be sent with MDCX as the osmo-bts code
+	 * complains about both rtp and rtp payload2 being present in the
+	 * same package!
+	 */
+	trans->conn->mncc_rtp_connect_pending = 1;
+	return rsl_ipacc_mdcx(lchan, rtp->ip, rtp->port, 0);
+}
+
+static int tch_rtp_signal(struct gsm_lchan *lchan, int signal)
+{
+	struct gsm_network *net;
+	struct gsm_trans *tmp, *trans = NULL;
+
+	net = lchan->ts->trx->bts->network;
+	llist_for_each_entry(tmp, &net->trans_list, entry) {
+		if (!tmp->conn)
+			continue;
+		if (tmp->conn->lchan != lchan && tmp->conn->ho_lchan != lchan)
+			continue;
+		trans = tmp;
+		break;
+	}
+
+	if (!trans) {
+		LOGP(DMNCC, LOGL_ERROR, "%s IPA abis signal but no transaction.\n",
+			gsm_lchan_name(lchan));
+		return 0;
+	}
+
+	switch (signal) {
+	case S_ABISIP_CRCX_ACK:
+		if (lchan->conn->mncc_rtp_create_pending) {
+			lchan->conn->mncc_rtp_create_pending = 0;
+			LOGP(DMNCC, LOGL_NOTICE, "%s sending pending RTP create ind.\n",
+				gsm_lchan_name(lchan));
+			mncc_recv_rtp_sock(net, trans, MNCC_RTP_CREATE);
+		}
+		/*
+		 * TODO: this appears to be too early? Why not until after
+		 * the handover detect or the handover complete?
+		 */
+		maybe_switch_for_handover(lchan);
+		break;
+	case S_ABISIP_MDCX_ACK:
+		if (lchan->conn->mncc_rtp_connect_pending) {
+			lchan->conn->mncc_rtp_connect_pending = 0;
+			LOGP(DMNCC, LOGL_NOTICE, "%s sending pending RTP connect ind.\n",
+				gsm_lchan_name(lchan));
+			mncc_recv_rtp_sock(net, trans, MNCC_RTP_CONNECT);
+		}
+		break;
+	}
+
+	return 0;
+}
+
+
 static struct downstate {
 	uint32_t	states;
 	int		type;
@@ -2985,6 +3214,13 @@
 		return tch_recv_mncc(net, data->callref, 0);
 	case MNCC_FRAME_RECV:
 		return tch_recv_mncc(net, data->callref, 1);
+	case MNCC_RTP_CREATE:
+		return tch_rtp_create(net, data->callref);
+	case MNCC_RTP_CONNECT:
+		return tch_rtp_connect(net, arg);
+	case MNCC_RTP_FREE:
+		/* unused right now */
+		return -EIO;
 	case GSM_TCHF_FRAME:
 	case GSM_TCHF_FRAME_EFR:
 	case GSM_TCHH_FRAME:
diff --git a/openbsc/src/libmsc/mncc.c b/openbsc/src/libmsc/mncc.c
index 73db5f0..2767faa 100644
--- a/openbsc/src/libmsc/mncc.c
+++ b/openbsc/src/libmsc/mncc.c
@@ -83,6 +83,9 @@
 	{"MNCC_FRAME_RECV",	0x0201},
 	{"MNCC_FRAME_DROP",	0x0202},
 	{"MNCC_LCHAN_MODIFY",	0x0203},
+	{"MNCC_RTP_CREATE",	0x0204},
+	{"MNCC_RTP_CONNECT",	0x0205},
+	{"MNCC_RTP_FREE",	0x0206},
 
 	{"GSM_TCHF_FRAME",	0x0300},
 	{"GSM_TCHF_FRAME_EFR",	0x0301},
diff --git a/openbsc/src/libmsc/mncc_builtin.c b/openbsc/src/libmsc/mncc_builtin.c
index 5c3461b..7d5a7f1 100644
--- a/openbsc/src/libmsc/mncc_builtin.c
+++ b/openbsc/src/libmsc/mncc_builtin.c
@@ -65,16 +65,21 @@
 	return NULL;
 }
 
-static uint8_t determine_lchan_mode(struct gsm_mncc *setup)
+uint8_t mncc_codec_for_mode(int lchan_type)
 {
 	/* FIXME: check codec capabilities of the phone */
 
-	if (setup->lchan_type != GSM_LCHAN_TCH_H)
+	if (lchan_type != GSM_LCHAN_TCH_H)
 		return mncc_int.def_codec[0];
 	else
 		return mncc_int.def_codec[1];
 }
 
+static uint8_t determine_lchan_mode(struct gsm_mncc *setup)
+{
+	return mncc_codec_for_mode(setup->lchan_type);
+}
+
 /* on incoming call, look up database and send setup to remote subscr. */
 static int mncc_setup_ind(struct gsm_call *call, int msg_type,
 			  struct gsm_mncc *setup)