add codec_sdp_cc_t9n.h,c

Related: SYS#5066
Change-Id: Iaa307be6a8487aa8d4ba7cd59d5c5ef04818a744
diff --git a/include/osmocom/msc/Makefile.am b/include/osmocom/msc/Makefile.am
index adda44c..e1cf2cd 100644
--- a/include/osmocom/msc/Makefile.am
+++ b/include/osmocom/msc/Makefile.am
@@ -1,6 +1,7 @@
 noinst_HEADERS = \
 	call_leg.h \
 	cell_id_list.h \
+	codec_sdp_cc_t9n.h \
 	db.h \
 	debug.h \
 	e_link.h \
diff --git a/include/osmocom/msc/codec_sdp_cc_t9n.h b/include/osmocom/msc/codec_sdp_cc_t9n.h
new file mode 100644
index 0000000..0934561
--- /dev/null
+++ b/include/osmocom/msc/codec_sdp_cc_t9n.h
@@ -0,0 +1,68 @@
+/* Routines for translation ("t9n") between SDP codec names and CC/BSSMAP codec constants */
+#pragma once
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/mgcp_client/mgcp_client.h>
+#include <osmocom/msc/sdp_msg.h>
+#include <osmocom/gsm/mncc.h>
+
+#define NO_MGCP_CODEC 0xffffffff
+
+extern const struct gsm_mncc_bearer_cap bearer_cap_empty;
+
+enum codec_frhr {
+	CODEC_FRHR_NONE = 0,
+	CODEC_FRHR_FR,
+	CODEC_FRHR_HR,
+};
+
+struct codec_mapping {
+	/* The sdp.payload_type number in a mapping is not necessarily imperative, but may just reflect the usual
+	 * payload type number for a given codec. */
+	struct sdp_audio_codec sdp;
+	/* The id that mgcp_client.h uses for this codec. Must be set in each mapping, because 0 means PCMU. */
+	enum mgcp_codecs mgcp;
+	/* Nr of used entries in speech_ver[] below. */
+	unsigned int speech_ver_count;
+	/* Entries to add to Speech Version lists when this codec is present, if any. */
+	enum gsm48_bcap_speech_ver speech_ver[8];
+	/* If applicable, one of GSM_TCHF_FRAME, GSM_TCHF_FRAME_EFR, GSM_TCHH_FRAME, GSM_TCH_FRAME_AMR; or zero. */
+	uint32_t mncc_payload_msg_type;
+	/* Set to true if gsm0808_speech_codec_type below reflects a meaningful value. */
+	bool has_gsm0808_speech_codec_type;
+	/* gsm0808_speech_codec_type corresponds to gsm0808_speech_codec[_list]->type */
+	enum gsm0808_speech_codec_type gsm0808_speech_codec_type;
+	/* If applicable, entries to add to Permitted Speech lists when this codec is present; or zero. */
+	enum gsm0808_permitted_speech perm_speech;
+	/* If applicable, indicator whether this codec can work on a GERAN half-rate lchan, or whether full-rate is
+	 * required. Leave zero when this codec does not apply to GERAN. */
+	enum codec_frhr frhr;
+};
+
+extern const struct codec_mapping codec_map[];
+#define foreach_codec_mapping(CODEC_MAPPING) \
+	for ((CODEC_MAPPING) = codec_map; (CODEC_MAPPING) < codec_map + ARRAY_SIZE(codec_map); (CODEC_MAPPING)++)
+
+const struct codec_mapping *codec_mapping_by_speech_ver(enum gsm48_bcap_speech_ver speech_ver);
+const struct codec_mapping *codec_mapping_by_gsm0808_speech_codec_type(enum gsm0808_speech_codec_type sct,
+								       uint16_t cfg);
+const struct codec_mapping *codec_mapping_by_perm_speech(enum gsm0808_permitted_speech perm_speech);
+const struct codec_mapping *codec_mapping_by_subtype_name(const char *subtype_name);
+const struct codec_mapping *codec_mapping_by_mgcp_codec(enum mgcp_codecs mgcp);
+
+int bearer_cap_add_speech_ver(struct gsm_mncc_bearer_cap *bearer_cap, enum gsm48_bcap_speech_ver speech_ver);
+int sdp_audio_codec_add_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codec *codec);
+int sdp_audio_codecs_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codecs *ac);
+int bearer_cap_set_radio(struct gsm_mncc_bearer_cap *bearer_cap);
+
+struct sdp_audio_codec *sdp_audio_codecs_add_speech_ver(struct sdp_audio_codecs *ac,
+							enum gsm48_bcap_speech_ver speech_ver);
+struct sdp_audio_codec *sdp_audio_codecs_add_mgcp_codec(struct sdp_audio_codecs *ac, enum mgcp_codecs mgcp_codec);
+void sdp_audio_codecs_from_bearer_cap(struct sdp_audio_codecs *ac, const struct gsm_mncc_bearer_cap *bc);
+
+void sdp_audio_codecs_from_speech_codec_list(struct sdp_audio_codecs *ac, const struct gsm0808_speech_codec_list *cl);
+
+int sdp_audio_codecs_to_gsm0808_channel_type(struct gsm0808_channel_type *ct, const struct sdp_audio_codecs *ac);
+
+enum mgcp_codecs sdp_audio_codec_to_mgcp_codec(const struct sdp_audio_codec *codec);
diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am
index de02a17..b843a65 100644
--- a/src/libmsc/Makefile.am
+++ b/src/libmsc/Makefile.am
@@ -27,6 +27,7 @@
 libmsc_a_SOURCES = \
 	call_leg.c \
 	cell_id_list.c \
+	codec_sdp_cc_t9n.c \
 	sccp_ran.c \
 	msc_vty.c \
 	db.c \
diff --git a/src/libmsc/codec_sdp_cc_t9n.c b/src/libmsc/codec_sdp_cc_t9n.c
new file mode 100644
index 0000000..07089f9
--- /dev/null
+++ b/src/libmsc/codec_sdp_cc_t9n.c
@@ -0,0 +1,446 @@
+/* Translate codec discriminators between SDP and various GSM representations */
+/*
+ * (C) 2019-2022 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.
+ */
+#include <string.h>
+
+#include <osmocom/gsm/mncc.h>
+
+#include <osmocom/msc/sdp_msg.h>
+#include <osmocom/msc/codec_sdp_cc_t9n.h>
+#include <osmocom/msc/mncc.h>
+
+const struct codec_mapping codec_map[] = {
+	/* FIXME: I'm not sure about OFR, OHR -- O means octet-aligned?? */
+	/* FIXME: sdp.fmtp handling is not done properly. I am not sure whether we should completely remove fmtp from
+	 * this mapping, or more intelligently map actual fmtp contents (instead of strcmp). I hope that we will reach
+	 * some clarity on this subject in the future. */
+	{
+		.sdp = {
+			.payload_type = 0,
+			.subtype_name = "PCMU",
+			.rate = 8000,
+		},
+		.mgcp = CODEC_PCMU_8000_1,
+	},
+	{
+		.sdp = {
+			.payload_type = 3,
+			.subtype_name = "GSM",
+			.rate = 8000,
+		},
+		.mgcp = CODEC_GSM_8000_1,
+		.speech_ver_count = 1,
+		.speech_ver = { GSM48_BCAP_SV_FR },
+		.mncc_payload_msg_type = GSM_TCHF_FRAME,
+		.has_gsm0808_speech_codec_type = true,
+		.gsm0808_speech_codec_type = GSM0808_SCT_FR1,
+		.perm_speech = GSM0808_PERM_FR1,
+		.frhr = CODEC_FRHR_FR,
+	},
+	{
+		.sdp = {
+			.payload_type = 8,
+			.subtype_name = "PCMA",
+			.rate = 8000,
+		},
+		.mgcp = CODEC_PCMA_8000_1,
+	},
+	{
+		.sdp = {
+			.payload_type = 18,
+			.subtype_name = "G729",
+			.rate = 8000,
+		},
+		.mgcp = CODEC_G729_8000_1,
+	},
+	{
+		.sdp = {
+			.payload_type = 110,
+			.subtype_name = "GSM-EFR",
+			.rate = 8000,
+		},
+		.mgcp = CODEC_GSMEFR_8000_1,
+		.speech_ver_count = 1,
+		.speech_ver = { GSM48_BCAP_SV_EFR },
+		.mncc_payload_msg_type = GSM_TCHF_FRAME_EFR,
+		.has_gsm0808_speech_codec_type = true,
+		.gsm0808_speech_codec_type = GSM0808_SCT_FR2,
+		.perm_speech = GSM0808_PERM_FR2,
+		.frhr = CODEC_FRHR_FR,
+	},
+	{
+		.sdp = {
+			.payload_type = 111,
+			.subtype_name = "GSM-HR-08",
+			.rate = 8000,
+		},
+		.mgcp = CODEC_GSMHR_8000_1,
+		.speech_ver_count = 1,
+		.speech_ver = { GSM48_BCAP_SV_HR },
+		.mncc_payload_msg_type = GSM_TCHH_FRAME,
+		.has_gsm0808_speech_codec_type = true,
+		.gsm0808_speech_codec_type = GSM0808_SCT_HR1,
+		.perm_speech = GSM0808_PERM_HR1,
+		.frhr = CODEC_FRHR_HR,
+	},
+	{
+		.sdp = {
+			.payload_type = 112,
+			.subtype_name = "AMR",
+			.rate = 8000,
+			/* It is empirically shown to be important to send this fmtp parameter to a SIP peer in SDP,
+			 * otherwise the voice audio is broken noise.
+			 * However, a SIP peer may offer AMR without this parameter set in its SDP, so fmtp must be
+			 * ignored during codec matching: otherwise an incoming AMR codec without this parameter fails
+			 * to match this entry, and it ends in an aborted call due to no codec match.
+			 * If the peer offers plain "AMR/8000" and we reply with "AMR/8000 fmtp:octet-align=1",
+			 * then everything works out happily, */
+			.fmtp = "octet-align=1",
+		},
+		.mgcp = CODEC_AMR_8000_1,
+		.speech_ver_count = 1,
+		.speech_ver = { GSM48_BCAP_SV_AMR_F },
+		.mncc_payload_msg_type = GSM_TCH_FRAME_AMR,
+		.has_gsm0808_speech_codec_type = true,
+		.gsm0808_speech_codec_type = GSM0808_SCT_FR3,
+		.perm_speech = GSM0808_PERM_FR3,
+		.frhr = CODEC_FRHR_FR,
+	},
+	{
+		.sdp = {
+			.payload_type = 112,
+			.subtype_name = "AMR",
+			.rate = 8000,
+			.fmtp = "octet-align=1;mode-set=0,1,2,3",
+		},
+		.mgcp = CODEC_AMR_8000_1,
+		.speech_ver_count = 2,
+		.speech_ver = { GSM48_BCAP_SV_AMR_H, GSM48_BCAP_SV_AMR_OH },
+		.mncc_payload_msg_type = GSM_TCH_FRAME_AMR,
+		.has_gsm0808_speech_codec_type = true,
+		.gsm0808_speech_codec_type = GSM0808_SCT_HR3,
+		.perm_speech = GSM0808_PERM_HR3,
+		.frhr = CODEC_FRHR_HR,
+	},
+	{
+		.sdp = {
+			.payload_type = 113,
+			.subtype_name = "AMR-WB",
+			.rate = 16000,
+			.fmtp = "octet-align=1",
+		},
+		.mgcp = CODEC_AMRWB_16000_1,
+		.speech_ver_count = 2,
+		.speech_ver = { GSM48_BCAP_SV_AMR_OFW, GSM48_BCAP_SV_AMR_FW },
+		.mncc_payload_msg_type = GSM_TCH_FRAME_AMR,
+		.has_gsm0808_speech_codec_type = true,
+		.gsm0808_speech_codec_type = GSM0808_SCT_FR5,
+		.perm_speech = GSM0808_PERM_FR5,
+		.frhr = CODEC_FRHR_FR,
+	},
+	{
+		.sdp = {
+			.payload_type = 113,
+			.subtype_name = "AMR-WB",
+			.rate = 16000,
+			.fmtp = "octet-align=1;mode-set=0,1,2,3", /* TODO: does this make sense?? */
+		},
+		.mgcp = CODEC_AMRWB_16000_1,
+		.speech_ver_count = 1,
+		.speech_ver = { GSM48_BCAP_SV_AMR_OHW },
+		.mncc_payload_msg_type = GSM_TCH_FRAME_AMR,
+		.has_gsm0808_speech_codec_type = true,
+		.gsm0808_speech_codec_type = GSM0808_SCT_HR4,
+		.perm_speech = GSM0808_PERM_HR4,
+		.frhr = CODEC_FRHR_HR,
+	},
+};
+
+const struct gsm_mncc_bearer_cap bearer_cap_empty = {
+		.speech_ver = { -1 },
+	};
+
+const struct codec_mapping *codec_mapping_by_speech_ver(enum gsm48_bcap_speech_ver speech_ver)
+{
+	const struct codec_mapping *m;
+	foreach_codec_mapping(m) {
+		int i;
+		for (i = 0; i < m->speech_ver_count; i++)
+			if (m->speech_ver[i] == speech_ver)
+				return m;
+	}
+	return NULL;
+}
+
+
+const struct codec_mapping *codec_mapping_by_gsm0808_speech_codec_type(enum gsm0808_speech_codec_type sct, uint16_t cfg)
+{
+	const struct codec_mapping *m;
+	foreach_codec_mapping(m) {
+		if (!m->has_gsm0808_speech_codec_type)
+			continue;
+		if (m->gsm0808_speech_codec_type == sct)
+			return m;
+		/* TODO: evaluate cfg bits? */
+	}
+	return NULL;
+}
+
+const struct codec_mapping *codec_mapping_by_perm_speech(enum gsm0808_permitted_speech perm_speech)
+{
+	const struct codec_mapping *m;
+	foreach_codec_mapping(m) {
+		if (m->perm_speech == perm_speech)
+			return m;
+	}
+	return NULL;
+}
+
+const struct codec_mapping *codec_mapping_by_subtype_name(const char *subtype_name)
+{
+	const struct codec_mapping *m;
+	foreach_codec_mapping(m) {
+		if (!strcmp(m->sdp.subtype_name, subtype_name))
+			return m;
+	}
+	return NULL;
+}
+
+const struct codec_mapping *codec_mapping_by_mgcp_codec(enum mgcp_codecs mgcp)
+{
+	const struct codec_mapping *m;
+	foreach_codec_mapping(m) {
+		if (m->mgcp == mgcp)
+			return m;
+	}
+	return NULL;
+}
+
+/* Append given Speech Version to the end of the Bearer Capabilities Speech Version array. Return 1 if added, zero
+ * otherwise (as in, return the number of items added). */
+int bearer_cap_add_speech_ver(struct gsm_mncc_bearer_cap *bearer_cap, enum gsm48_bcap_speech_ver speech_ver)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(bearer_cap->speech_ver) - 1; i++) {
+		if (bearer_cap->speech_ver[i] == speech_ver)
+			return 0;
+		if (bearer_cap->speech_ver[i] == -1) {
+			bearer_cap->speech_ver[i] = speech_ver;
+			bearer_cap->speech_ver[i+1] = -1;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/* From the current speech_ver list present in the bearer_cap, set the bearer_cap.radio.
+ * If a HR speech_ver is present, set to GSM48_BCAP_RRQ_DUAL_FR, otherwise set to GSM48_BCAP_RRQ_FR_ONLY. */
+int bearer_cap_set_radio(struct gsm_mncc_bearer_cap *bearer_cap)
+{
+	bool hr_present;
+	int i;
+	for (i = 0; i < ARRAY_SIZE(bearer_cap->speech_ver) - 1; i++) {
+		const struct codec_mapping *m = codec_mapping_by_speech_ver(bearer_cap->speech_ver[i]);
+
+		if (!m)
+			continue;
+
+		if (m->frhr == CODEC_FRHR_HR)
+			hr_present = true;
+	}
+
+	if (hr_present)
+		bearer_cap->radio = GSM48_BCAP_RRQ_DUAL_FR;
+	else
+		bearer_cap->radio = GSM48_BCAP_RRQ_FR_ONLY;
+
+	return 0;
+}
+
+/* Try to convert the SDP audio codec name to Speech Versions to append to Bearer Capabilities.
+ * Return the number of Speech Version entries added (some may add more than one, others may be unknown/unapplicable and
+ * return 0). */
+int sdp_audio_codec_add_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codec *codec)
+{
+	const struct codec_mapping *m;
+	int added = 0;
+	foreach_codec_mapping(m) {
+		int i;
+		if (strcmp(m->sdp.subtype_name, codec->subtype_name))
+			continue;
+		/* TODO also match rate and fmtp? */
+		for (i = 0; i < m->speech_ver_count; i++) {
+			added += bearer_cap_add_speech_ver(bearer_cap, m->speech_ver[i]);
+		}
+	}
+	return added;
+}
+
+/* Append all audio codecs found in given sdp_msg to Bearer Capability, by traversing all codec entries with
+ * sdp_audio_codec_add_to_bearer_cap(). Return the number of Speech Version entries added.
+ * Note that Speech Version entries are only appended, no previous entries are removed.
+ * Note that only the Speech Version entries are modified; to make a valid Bearer Capabiliy, at least bearer_cap->radio
+ * must also be set (before or after this function); see also bearer_cap_set_radio(). */
+int sdp_audio_codecs_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codecs *ac)
+{
+	const struct sdp_audio_codec *codec;
+	int added = 0;
+
+	foreach_sdp_audio_codec(codec, ac) {
+		added += sdp_audio_codec_add_to_bearer_cap(bearer_cap, codec);
+	}
+
+	return added;
+}
+
+/* Convert Speech Version to SDP audio codec and append to SDP message struct. */
+struct sdp_audio_codec *sdp_audio_codecs_add_speech_ver(struct sdp_audio_codecs *ac,
+							enum gsm48_bcap_speech_ver speech_ver)
+{
+	const struct codec_mapping *m;
+	struct sdp_audio_codec *ret = NULL;
+	foreach_codec_mapping(m) {
+		int i;
+		for (i = 0; i < m->speech_ver_count; i++) {
+			if (m->speech_ver[i] == speech_ver) {
+				ret = sdp_audio_codecs_add_copy(ac, &m->sdp);
+				break;
+			}
+		}
+	}
+	return ret;
+}
+
+struct sdp_audio_codec *sdp_audio_codecs_add_mgcp_codec(struct sdp_audio_codecs *ac, enum mgcp_codecs mgcp_codec)
+{
+	const struct codec_mapping *m = codec_mapping_by_mgcp_codec(mgcp_codec);
+	if (!m)
+		return NULL;
+	return sdp_audio_codecs_add_copy(ac, &m->sdp);
+}
+
+void sdp_audio_codecs_from_bearer_cap(struct sdp_audio_codecs *ac, const struct gsm_mncc_bearer_cap *bc)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(bc->speech_ver); i++) {
+		if (bc->speech_ver[i] == -1)
+			break;
+		sdp_audio_codecs_add_speech_ver(ac, bc->speech_ver[i]);
+	}
+}
+
+void sdp_audio_codecs_from_speech_codec_list(struct sdp_audio_codecs *ac, const struct gsm0808_speech_codec_list *cl)
+{
+	int i;
+	for (i = 0; i < cl->len; i++) {
+		const struct gsm0808_speech_codec *sc = &cl->codec[i];
+		const struct codec_mapping *m = codec_mapping_by_gsm0808_speech_codec_type(sc->type, sc->cfg);
+		if (!m)
+			continue;
+		sdp_audio_codecs_add_copy(ac, &m->sdp);
+	}
+}
+
+int sdp_audio_codecs_to_gsm0808_channel_type(struct gsm0808_channel_type *ct, const struct sdp_audio_codecs *ac)
+{
+	const struct sdp_audio_codec *codec;
+	bool fr_present = false;
+	int first_fr_idx = -1;
+	bool hr_present = false;
+	int first_hr_idx = -1;
+	int idx = -1;
+
+	*ct = (struct gsm0808_channel_type){
+		.ch_indctr = GSM0808_CHAN_SPEECH,
+	};
+
+	foreach_sdp_audio_codec(codec, ac) {
+		const struct codec_mapping *m;
+		int i;
+		bool dup;
+		idx++;
+		foreach_codec_mapping(m) {
+			if (strcmp(m->sdp.subtype_name, codec->subtype_name))
+				continue;
+
+			switch (m->perm_speech) {
+			default:
+				continue;
+
+			case GSM0808_PERM_FR1:
+			case GSM0808_PERM_FR2:
+			case GSM0808_PERM_FR3:
+			case GSM0808_PERM_FR4:
+			case GSM0808_PERM_FR5:
+				fr_present = true;
+				if (first_fr_idx < 0)
+					first_fr_idx = idx;
+				break;
+
+			case GSM0808_PERM_HR1:
+			case GSM0808_PERM_HR2:
+			case GSM0808_PERM_HR3:
+			case GSM0808_PERM_HR4:
+			case GSM0808_PERM_HR6:
+				hr_present = true;
+				if (first_hr_idx < 0)
+					first_hr_idx = idx;
+				break;
+			}
+
+			/* Avoid duplicates */
+			dup = false;
+			for (i = 0; i < ct->perm_spch_len; i++) {
+				if (ct->perm_spch[i] == m->perm_speech) {
+					dup = true;
+					break;
+				}
+			}
+			if (dup)
+				continue;
+
+			ct->perm_spch[ct->perm_spch_len] = m->perm_speech;
+			ct->perm_spch_len++;
+		}
+	}
+
+	if (fr_present && hr_present) {
+		if (first_fr_idx <= first_hr_idx)
+			ct->ch_rate_type = GSM0808_SPEECH_FULL_PREF;
+		else
+			ct->ch_rate_type = GSM0808_SPEECH_HALF_PREF;
+	} else if (fr_present && !hr_present)
+		ct->ch_rate_type = GSM0808_SPEECH_FULL_BM;
+	else if (!fr_present && hr_present)
+		ct->ch_rate_type = GSM0808_SPEECH_HALF_LM;
+	else
+		return -EINVAL;
+	return 0;
+}
+
+enum mgcp_codecs sdp_audio_codec_to_mgcp_codec(const struct sdp_audio_codec *codec)
+{
+	const struct codec_mapping *m;
+	foreach_codec_mapping(m) {
+		if (!sdp_audio_codec_cmp(&m->sdp, codec, false, false))
+			return m->mgcp;
+	}
+	return NO_MGCP_CODEC;
+}