Add initial CSD support with external MNCC

Implement and use CSD bearer service logic (with similar audio codec code):
* csd_filter (codec_filter)
* csd_bs (sdp_audio_codec)
* csd_bs_list (sdp_audio_codecs)

Related: OS#4394
Change-Id: Ide8b8321e0401dcbe35da2ec9cee0abca821d99a
diff --git a/src/libmsc/csd_bs.c b/src/libmsc/csd_bs.c
new file mode 100644
index 0000000..adb9293
--- /dev/null
+++ b/src/libmsc/csd_bs.c
@@ -0,0 +1,477 @@
+/* 3GPP TS 122.002 Bearer Services */
+/*
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Oliver Smith
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <errno.h>
+
+#include <osmocom/msc/csd_bs.h>
+#include <osmocom/msc/debug.h>
+
+/* csd_bs related below */
+
+struct csd_bs_map {
+	/* BS number (20, 21, ...) */
+	unsigned int num;
+	/* Access Structure (1: asynchronous, 0: synchronous) */
+	bool async;
+	/* QoS Attribute (1: transparent, 0: non-transparent) */
+	bool transp;
+	/* Rate Adaption (V110, V120 etc.) */
+	enum gsm48_bcap_ra ra;
+	/* Fixed Network User Rate */
+	unsigned int rate;
+};
+
+static const struct csd_bs_map bs_map[] = {
+	/* 3.1.1.1.2 */
+	[CSD_BS_21_T_V110_0k3] = {
+		.num = 21,
+		.async = true,
+		.transp = true,
+		.ra = GSM48_BCAP_RA_V110_X30,
+		.rate = 300,
+	},
+	[CSD_BS_22_T_V110_1k2] = {
+		.num = 22,
+		.async = true,
+		.transp = true,
+		.ra = GSM48_BCAP_RA_V110_X30,
+		.rate = 1200,
+	},
+	[CSD_BS_24_T_V110_2k4] = {
+		.num = 24,
+		.async = true,
+		.transp = true,
+		.ra = GSM48_BCAP_RA_V110_X30,
+		.rate = 2400,
+	},
+	[CSD_BS_25_T_V110_4k8] = {
+		.num = 25,
+		.async = true,
+		.transp = true,
+		.ra = GSM48_BCAP_RA_V110_X30,
+		.rate = 4800,
+	},
+	[CSD_BS_26_T_V110_9k6] = {
+		.num = 26,
+		.async = true,
+		.transp = true,
+		.ra = GSM48_BCAP_RA_V110_X30,
+		.rate = 9600,
+	},
+
+	/* 3.1.1.2.2 */
+	[CSD_BS_21_NT_V110_0k3] = {
+		.num = 21,
+		.async = true,
+		.transp = false,
+		.ra = GSM48_BCAP_RA_V110_X30,
+		.rate = 300,
+	},
+	[CSD_BS_22_NT_V110_1k2] = {
+		.num = 22,
+		.async = true,
+		.transp = false,
+		.ra = GSM48_BCAP_RA_V110_X30,
+		.rate = 1200,
+	},
+	[CSD_BS_24_NT_V110_2k4] = {
+		.num = 24,
+		.async = true,
+		.transp = false,
+		.ra = GSM48_BCAP_RA_V110_X30,
+		.rate = 2400,
+	},
+	[CSD_BS_25_NT_V110_4k8] = {
+		.num = 25,
+		.async = true,
+		.transp = false,
+		.ra = GSM48_BCAP_RA_V110_X30,
+		.rate = 4800,
+	},
+	[CSD_BS_26_NT_V110_9k6] = {
+		.num = 26,
+		.async = true,
+		.transp = false,
+		.ra = GSM48_BCAP_RA_V110_X30,
+		.rate = 9600,
+	},
+
+	/* 3.1.2.1.2 */
+	[CSD_BS_31_T_V110_1k2] = {
+		.num = 31,
+		.async = false,
+		.transp = true,
+		.ra = GSM48_BCAP_RA_V110_X30,
+		.rate = 1200,
+	},
+	[CSD_BS_32_T_V110_2k4] = {
+		.num = 32,
+		.async = false,
+		.transp = true,
+		.ra = GSM48_BCAP_RA_V110_X30,
+		.rate = 2400,
+	},
+	[CSD_BS_33_T_V110_4k8] = {
+		.num = 33,
+		.async = false,
+		.transp = true,
+		.ra = GSM48_BCAP_RA_V110_X30,
+		.rate = 4800,
+	},
+	[CSD_BS_34_T_V110_9k6] = {
+		.num = 34,
+		.async = false,
+		.transp = true,
+		.ra = GSM48_BCAP_RA_V110_X30,
+		.rate = 9600,
+	},
+};
+
+osmo_static_assert(ARRAY_SIZE(bs_map) == CSD_BS_MAX, _invalid_size_bs_map);
+
+bool csd_bs_is_transp(enum csd_bs bs)
+{
+	return bs_map[bs].transp;
+}
+
+/* Short single-line representation, convenient for logging.
+ * Like "BS25NT" */
+int csd_bs_to_str_buf(char *buf, size_t buflen, enum csd_bs bs)
+{
+	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+	const struct csd_bs_map *map = &bs_map[bs];
+
+	OSMO_STRBUF_PRINTF(sb, "BS%u%s",
+			   map->num,
+			   map->transp ? "T" : "NT");
+
+	if (map->ra != GSM48_BCAP_RA_V110_X30)
+		OSMO_STRBUF_PRINTF(sb, "-RA=%d", map->ra);
+
+	return sb.chars_needed;
+}
+
+char *csd_bs_to_str_c(void *ctx, enum csd_bs bs)
+{
+	OSMO_NAME_C_IMPL(ctx, 32, "csd_bs_to_str_c-ERROR", csd_bs_to_str_buf, bs)
+}
+
+const char *csd_bs_to_str(enum csd_bs bs)
+{
+	return csd_bs_to_str_c(OTC_SELECT, bs);
+}
+
+static int csd_bs_to_gsm0808_data_rate_transp(enum csd_bs bs)
+{
+	switch (bs_map[bs].rate) {
+	case 1200:
+		return GSM0808_DATA_RATE_TRANSP_1k2;
+	case 2400:
+		return GSM0808_DATA_RATE_TRANSP_2k4;
+	case 4800:
+		return GSM0808_DATA_RATE_TRANSP_4k8;
+	case 9600:
+		return GSM0808_DATA_RATE_TRANSP_9k6;
+	}
+	return -EINVAL;
+}
+
+static int csd_bs_to_gsm0808_data_rate_non_transp(enum csd_bs bs)
+{
+	uint16_t rate = bs_map[bs].rate;
+
+	if (rate < 6000)
+		return GSM0808_DATA_RATE_NON_TRANSP_6k0;
+	if (rate < 12000)
+		return GSM0808_DATA_RATE_NON_TRANSP_12k0;
+
+	return -EINVAL;
+}
+
+static int csd_bs_to_gsm0808_data_rate_non_transp_allowed(enum csd_bs bs)
+{
+	uint16_t rate = bs_map[bs].rate;
+
+	if (rate < 6000)
+		return GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_6k0;
+	if (rate < 12000)
+		return GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_12k0;
+
+	return -EINVAL;
+}
+
+enum csd_bs csd_bs_from_bearer_cap(const struct gsm_mncc_bearer_cap *cap, bool transp)
+{
+	enum gsm48_bcap_ra ra = cap->data.rate_adaption;
+	enum gsm48_bcap_user_rate rate = cap->data.user_rate;
+	bool async = cap->data.async;
+
+	if (ra == GSM48_BCAP_RA_V110_X30 && async && transp) {
+		switch (rate) {
+		case GSM48_BCAP_UR_300:
+			return CSD_BS_21_T_V110_0k3;
+		case GSM48_BCAP_UR_1200:
+			return CSD_BS_22_T_V110_1k2;
+		case GSM48_BCAP_UR_2400:
+			return CSD_BS_24_T_V110_2k4;
+		case GSM48_BCAP_UR_4800:
+			return CSD_BS_25_T_V110_4k8;
+		case GSM48_BCAP_UR_9600:
+			return CSD_BS_26_T_V110_9k6;
+		default:
+			return CSD_BS_NONE;
+		}
+	}
+
+	if (ra == GSM48_BCAP_RA_V110_X30 && async && !transp) {
+		switch (rate) {
+		case GSM48_BCAP_UR_300:
+			return CSD_BS_21_NT_V110_0k3;
+		case GSM48_BCAP_UR_1200:
+			return CSD_BS_22_NT_V110_1k2;
+		case GSM48_BCAP_UR_2400:
+			return CSD_BS_24_NT_V110_2k4;
+		case GSM48_BCAP_UR_4800:
+			return CSD_BS_25_NT_V110_4k8;
+		case GSM48_BCAP_UR_9600:
+			return CSD_BS_26_NT_V110_9k6;
+		default:
+			return CSD_BS_NONE;
+		}
+	}
+
+	if (ra == GSM48_BCAP_RA_V110_X30 && !async && transp) {
+		switch (rate) {
+		case GSM48_BCAP_UR_1200:
+			return CSD_BS_31_T_V110_1k2;
+		case GSM48_BCAP_UR_2400:
+			return CSD_BS_32_T_V110_2k4;
+		case GSM48_BCAP_UR_4800:
+			return CSD_BS_33_T_V110_4k8;
+		case GSM48_BCAP_UR_9600:
+			return CSD_BS_34_T_V110_9k6;
+		default:
+			return CSD_BS_NONE;
+		}
+	}
+
+	return CSD_BS_NONE;
+}
+
+/* csd_bs_list related below */
+
+int csd_bs_list_to_str_buf(char *buf, size_t buflen, const struct csd_bs_list *list)
+{
+	struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+	int i;
+
+	if (!list->count)
+		OSMO_STRBUF_PRINTF(sb, "(no-bearer-services)");
+
+	for (i = 0; i < list->count; i++) {
+		if (i)
+			OSMO_STRBUF_PRINTF(sb, ",");
+
+		OSMO_STRBUF_APPEND(sb, csd_bs_to_str_buf, list->bs[i]);
+	}
+	return sb.chars_needed;
+}
+
+char *csd_bs_list_to_str_c(void *ctx, const struct csd_bs_list *list)
+{
+	OSMO_NAME_C_IMPL(ctx, 128, "csd_bs_list_to_str_c-ERROR", csd_bs_list_to_str_buf, list)
+}
+
+const char *csd_bs_list_to_str(const struct csd_bs_list *list)
+{
+	return csd_bs_list_to_str_c(OTC_SELECT, list);
+}
+
+bool csd_bs_list_has_bs(const struct csd_bs_list *list, enum csd_bs bs)
+{
+	int i;
+
+	for (i = 0; i < list->count; i++) {
+		if (list->bs[i] == bs)
+			return true;
+	}
+
+	return false;
+}
+
+void csd_bs_list_add_bs(struct csd_bs_list *list, enum csd_bs bs)
+{
+	int i;
+
+	if (!bs)
+		return;
+
+	for (i = 0; i < list->count; i++) {
+		if (list->bs[i] == bs)
+			return;
+	}
+
+	list->bs[i] = bs;
+	list->count++;
+}
+
+void csd_bs_list_remove(struct csd_bs_list *list, enum csd_bs bs)
+{
+	int i;
+	bool found = false;
+
+	for (i = 0; i < list->count; i++) {
+		if (list->bs[i] == bs) {
+			found = true;
+			list->count--;
+			continue;
+		}
+		if (i && found)
+			list->bs[i-1] = list->bs[i];
+	}
+}
+
+void csd_bs_list_intersection(struct csd_bs_list *dest, const struct csd_bs_list *other)
+{
+	int i;
+
+	for (i = 0; i < dest->count; i++) {
+		if (csd_bs_list_has_bs(other, dest->bs[i]))
+			continue;
+		csd_bs_list_remove(dest, dest->bs[i]);
+		i--;
+	}
+}
+
+int csd_bs_list_to_gsm0808_channel_type(struct gsm0808_channel_type *ct, const struct csd_bs_list *list)
+{
+	int i;
+	int rc;
+
+	*ct = (struct gsm0808_channel_type){
+		.ch_indctr = GSM0808_CHAN_DATA,
+	};
+
+	OSMO_ASSERT(list->count);
+
+	if (csd_bs_is_transp(list->bs[0])) {
+		ct->data_transparent = true;
+		ct->data_rate = csd_bs_to_gsm0808_data_rate_transp(list->bs[0]);
+	} else {
+		ct->data_rate = csd_bs_to_gsm0808_data_rate_non_transp(list->bs[0]);
+	}
+
+	if (ct->data_rate < 0)
+		return -EINVAL;
+
+	/* Other possible data rates allowed (3GPP TS 48.008 § 3.2.2.11, 5a) */
+	if (!ct->data_transparent && list->count > 1) {
+		for (i = 1; i < list->count; i++) {
+			if (!csd_bs_is_transp(list->bs[i]))
+				continue;
+
+			rc = csd_bs_to_gsm0808_data_rate_non_transp_allowed(list->bs[i]);
+			if (rc < 0) {
+				LOGP(DMSC, LOGL_DEBUG, "Failed to convert %s to allowed r i/f rate\n",
+				     csd_bs_to_str(list->bs[i]));
+				continue;
+			}
+
+			ct->data_rate_allowed |= rc;
+		}
+		if (ct->data_rate_allowed)
+			ct->data_rate_allowed_is_set = true;
+	}
+
+	ct->ch_rate_type = GSM0808_SPEECH_FULL_BM;
+
+	return 0;
+}
+
+int csd_bs_list_to_bearer_cap(struct gsm_mncc_bearer_cap *cap, const struct csd_bs_list *list)
+{
+	*cap = (struct gsm_mncc_bearer_cap){
+		.transfer = GSM_MNCC_BCAP_UNR_DIG,
+	};
+	enum csd_bs bs;
+	int i;
+
+	for (i = 0; i < list->count; i++) {
+		bs = list->bs[i];
+
+		cap->data.rate_adaption = GSM48_BCAP_RA_V110_X30;
+		cap->data.async = bs_map[bs].async;
+		cap->data.transp = bs_map[bs].transp;
+
+		switch (bs_map[bs].rate) {
+		case 300:
+			cap->data.user_rate = GSM48_BCAP_UR_300;
+			break;
+		case 1200:
+			cap->data.user_rate = GSM48_BCAP_UR_1200;
+			break;
+		case 2400:
+			cap->data.user_rate = GSM48_BCAP_UR_2400;
+			break;
+		case 4800:
+			cap->data.user_rate = GSM48_BCAP_UR_4800;
+			break;
+		case 9600:
+			cap->data.user_rate = GSM48_BCAP_UR_9600;
+			break;
+		}
+
+		/* FIXME: handle more than one list entry */
+		return 1;
+	}
+
+	return 0;
+}
+
+void csd_bs_list_from_bearer_cap(struct csd_bs_list *list, const struct gsm_mncc_bearer_cap *cap)
+{
+	*list = (struct csd_bs_list){};
+
+	switch (cap->data.transp) {
+	case GSM48_BCAP_TR_TRANSP:
+		csd_bs_list_add_bs(list, csd_bs_from_bearer_cap(cap, true));
+		break;
+	case GSM48_BCAP_TR_RLP: /* NT */
+		csd_bs_list_add_bs(list, csd_bs_from_bearer_cap(cap, false));
+		break;
+	case GSM48_BCAP_TR_TR_PREF:
+		csd_bs_list_add_bs(list, csd_bs_from_bearer_cap(cap, true));
+		csd_bs_list_add_bs(list, csd_bs_from_bearer_cap(cap, false));
+		break;
+	case GSM48_BCAP_TR_RLP_PREF:
+		csd_bs_list_add_bs(list, csd_bs_from_bearer_cap(cap, false));
+		csd_bs_list_add_bs(list, csd_bs_from_bearer_cap(cap, true));
+		break;
+	}
+
+	if (!list->count) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to get bearer service from bearer capabilities ra=%d, async=%d,"
+		     " transp=%d, user_rate=%d\n", cap->data.rate_adaption, cap->data.async, cap->data.transp,
+		     cap->data.user_rate);
+		return;
+	}
+}