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/Makefile.am b/src/libmsc/Makefile.am
index 9577475..fba7b53 100644
--- a/src/libmsc/Makefile.am
+++ b/src/libmsc/Makefile.am
@@ -29,6 +29,8 @@
cell_id_list.c \
codec_filter.c \
codec_mapping.c \
+ csd_bs.c \
+ csd_filter.c \
sccp_ran.c \
msc_vty.c \
db.c \
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;
+ }
+}
diff --git a/src/libmsc/csd_filter.c b/src/libmsc/csd_filter.c
new file mode 100644
index 0000000..0f428cf
--- /dev/null
+++ b/src/libmsc/csd_filter.c
@@ -0,0 +1,157 @@
+/* Filter/overlay bearer service selections across MS, RAN and CN limitations */
+/*
+ * (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 <osmocom/mgcp_client/mgcp_client.h>
+
+#include <osmocom/msc/csd_filter.h>
+
+static void add_all_geran_bs(struct csd_bs_list *list)
+{
+ /* See 3GPP TS 122.002 Bearer Services */
+ /* In order of preference. TODO: make configurable */
+
+ /* GSM-R */
+ csd_bs_list_add_bs(list, CSD_BS_24_T_V110_2k4);
+ csd_bs_list_add_bs(list, CSD_BS_25_T_V110_4k8);
+ csd_bs_list_add_bs(list, CSD_BS_26_T_V110_9k6);
+
+ /* Other */
+ csd_bs_list_add_bs(list, CSD_BS_21_T_V110_0k3);
+ csd_bs_list_add_bs(list, CSD_BS_22_T_V110_1k2);
+ csd_bs_list_add_bs(list, CSD_BS_21_NT_V110_0k3);
+ csd_bs_list_add_bs(list, CSD_BS_22_NT_V110_1k2);
+ csd_bs_list_add_bs(list, CSD_BS_24_NT_V110_2k4);
+ csd_bs_list_add_bs(list, CSD_BS_25_NT_V110_4k8);
+ csd_bs_list_add_bs(list, CSD_BS_26_NT_V110_9k6);
+ csd_bs_list_add_bs(list, CSD_BS_31_T_V110_1k2);
+ csd_bs_list_add_bs(list, CSD_BS_32_T_V110_2k4);
+ csd_bs_list_add_bs(list, CSD_BS_33_T_V110_4k8);
+ csd_bs_list_add_bs(list, CSD_BS_34_T_V110_9k6);
+}
+
+static void add_all_utran_bs(struct csd_bs_list *list)
+{
+ /* See 3GPP TS 122.002 Bearer Services */
+ /* In order of preference. TODO: make configurable */
+ csd_bs_list_add_bs(list, CSD_BS_21_NT_V110_0k3);
+ csd_bs_list_add_bs(list, CSD_BS_22_NT_V110_1k2);
+ csd_bs_list_add_bs(list, CSD_BS_24_NT_V110_2k4);
+ csd_bs_list_add_bs(list, CSD_BS_25_NT_V110_4k8);
+ csd_bs_list_add_bs(list, CSD_BS_26_NT_V110_9k6);
+}
+
+void csd_filter_set_ran(struct csd_filter *filter, enum osmo_rat_type ran_type)
+{
+ filter->ran = (struct csd_bs_list){};
+
+ switch (ran_type) {
+ default:
+ case OSMO_RAT_GERAN_A:
+ add_all_geran_bs(&filter->ran);
+ break;
+ case OSMO_RAT_UTRAN_IU:
+ add_all_utran_bs(&filter->ran);
+ break;
+ }
+}
+
+int csd_filter_run(struct csd_filter *filter, struct sdp_msg *result, const struct sdp_msg *remote)
+{
+ struct csd_bs_list *r = &result->bearer_services;
+ enum csd_bs a = filter->assignment;
+
+ *r = filter->ran;
+
+ if (filter->ms.count)
+ csd_bs_list_intersection(r, &filter->ms);
+ if (filter->bss.count)
+ csd_bs_list_intersection(r, &filter->bss);
+ if (remote->bearer_services.count)
+ csd_bs_list_intersection(r, &remote->bearer_services);
+
+ /* Future: If osmo-msc were able to trigger a re-assignment [...] see
+ * comment in codec_filter_run(). */
+
+ if (a) {
+ *r = (struct csd_bs_list){};
+ csd_bs_list_add_bs(r, a);
+ }
+
+ result->audio_codecs.count = 1;
+ result->audio_codecs.codec[0] = (struct sdp_audio_codec){
+ .payload_type = CODEC_CLEARMODE,
+ .subtype_name = "CLEARMODE",
+ .rate = 8000,
+ };
+
+ return 0;
+}
+
+
+int csd_filter_to_str_buf(char *buf, size_t buflen, const struct csd_filter *filter,
+ const struct sdp_msg *result, const struct sdp_msg *remote)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ OSMO_STRBUF_APPEND(sb, sdp_msg_to_str_buf, result);
+ OSMO_STRBUF_PRINTF(sb, " (from:");
+
+ if (filter->assignment) {
+ OSMO_STRBUF_PRINTF(sb, " assigned=");
+ OSMO_STRBUF_APPEND(sb, csd_bs_to_str_buf, filter->assignment);
+ }
+
+ if (remote->bearer_services.count || osmo_sockaddr_str_is_nonzero(&remote->rtp)) {
+ OSMO_STRBUF_PRINTF(sb, " remote=");
+ OSMO_STRBUF_APPEND(sb, sdp_msg_to_str_buf, remote);
+ }
+
+ if (filter->ms.count) {
+ OSMO_STRBUF_PRINTF(sb, " MS={");
+ OSMO_STRBUF_APPEND(sb, csd_bs_list_to_str_buf, &filter->ms);
+ OSMO_STRBUF_PRINTF(sb, "}");
+ }
+
+ if (filter->bss.count) {
+ OSMO_STRBUF_PRINTF(sb, " bss={");
+ OSMO_STRBUF_APPEND(sb, csd_bs_list_to_str_buf, &filter->bss);
+ OSMO_STRBUF_PRINTF(sb, "}");
+ }
+
+ OSMO_STRBUF_PRINTF(sb, " RAN={");
+ OSMO_STRBUF_APPEND(sb, csd_bs_list_to_str_buf, &filter->ran);
+ OSMO_STRBUF_PRINTF(sb, "}");
+
+ OSMO_STRBUF_PRINTF(sb, ")");
+
+ return sb.chars_needed;
+}
+
+char *csd_filter_to_str_c(void *ctx, const struct csd_filter *filter, const struct sdp_msg *result, const struct sdp_msg *remote)
+{
+ OSMO_NAME_C_IMPL(ctx, 128, "csd_filter_to_str_c-ERROR", csd_filter_to_str_buf, filter, result, remote)
+}
+
+const char *csd_filter_to_str(const struct csd_filter *filter, const struct sdp_msg *result, const struct sdp_msg *remote)
+{
+ return csd_filter_to_str_c(OTC_SELECT, filter, result, remote);
+}
diff --git a/src/libmsc/gsm_04_08_cc.c b/src/libmsc/gsm_04_08_cc.c
index 4bc87af..1d99421 100644
--- a/src/libmsc/gsm_04_08_cc.c
+++ b/src/libmsc/gsm_04_08_cc.c
@@ -808,55 +808,86 @@
trans_cc_filter_set_bss(trans, trans->msc_a);
if (setup->fields & MNCC_F_BEARER_CAP)
trans->bearer_cap.transfer = setup->bearer_cap.transfer;
- /* sdp.remote: if SDP is included in the MNCC, take that as definitive list of remote audio codecs. */
- rx_mncc_sdp(trans, setup->msg_type, setup->sdp);
- /* sdp.remote: if there is no SDP information or we failed to parse it, try using the Bearer Capability from
- * MNCC, if any. */
- if (!trans->cc.remote.audio_codecs.count && (setup->fields & MNCC_F_BEARER_CAP)) {
- trans->cc.remote = (struct sdp_msg){};
- trans_cc_set_remote_from_bc(trans, &setup->bearer_cap);
- LOG_TRANS_CAT(trans, DMNCC, LOGL_DEBUG, "rx %s Bearer Cap: remote=%s\n",
- get_mncc_name(setup->msg_type), sdp_msg_to_str(&trans->cc.remote));
+
+ switch (trans->bearer_cap.transfer) {
+ case GSM48_BCAP_ITCAP_SPEECH:
+ /* sdp.remote: if SDP is included in the MNCC, take that as definitive list of remote audio codecs. */
+ rx_mncc_sdp(trans, setup->msg_type, setup->sdp);
+ /* sdp.remote: if there is no SDP information or we failed to parse it, try using the Bearer Capability from
+ * MNCC, if any. */
+ if (!trans->cc.remote.audio_codecs.count && (setup->fields & MNCC_F_BEARER_CAP)) {
+ trans->cc.remote = (struct sdp_msg){};
+ trans_cc_set_remote_from_bc(trans, &setup->bearer_cap);
+ LOG_TRANS_CAT(trans, DMNCC, LOGL_DEBUG, "rx %s Bearer Cap: remote=%s\n",
+ get_mncc_name(setup->msg_type), sdp_msg_to_str(&trans->cc.remote));
+ }
+ if (!trans->cc.remote.audio_codecs.count)
+ LOG_TRANS(trans, LOGL_INFO,
+ "Got no information of remote audio codecs: neither SDP nor Bearer Capability. Trying anyway.\n");
+ break;
+ case GSM48_BCAP_ITCAP_UNR_DIG_INF:
+ sdp_audio_codecs_set_csd(&trans->cc.codecs.ms);
+ break;
+ default:
+ LOG_TRANS(trans, LOGL_ERROR, "Handling of information transfer capability %d not implemented\n",
+ trans->bearer_cap.transfer);
}
- if (!trans->cc.remote.audio_codecs.count)
- LOG_TRANS(trans, LOGL_INFO,
- "Got no information of remote audio codecs: neither SDP nor Bearer Capability. Trying anyway.\n");
trans_cc_filter_run(trans);
- /* Compose Bearer Capability information that reflects only the codecs (Speech Versions) remaining after
- * intersecting MS, BSS and remote call leg restrictions. To store in trans for later use, and to include in
- * the outgoing CC Setup message. */
- bearer_cap = (struct gsm_mncc_bearer_cap){
- .speech_ver = { -1 },
- };
- sdp_audio_codecs_to_bearer_cap(&bearer_cap, &trans->cc.local.audio_codecs);
- rc = bearer_cap_set_radio(&bearer_cap);
- if (rc) {
- LOG_TRANS(trans, LOGL_ERROR, "Error composing Bearer Capability for CC Setup\n");
- trans_free(trans);
- msgb_free(msg);
- return rc;
+ /* Compose Bearer Capability information that reflects only the codecs (Speech Versions) / CSD bearer services
+ * remaining after intersecting MS, BSS and remote call leg restrictions. To store in trans for later use, and
+ * to include in the outgoing CC Setup message. */
+ switch (trans->bearer_cap.transfer) {
+ case GSM48_BCAP_ITCAP_SPEECH:
+ bearer_cap = (struct gsm_mncc_bearer_cap){
+ .speech_ver = { -1 },
+ };
+ sdp_audio_codecs_to_bearer_cap(&bearer_cap, &trans->cc.local.audio_codecs);
+ rc = bearer_cap_set_radio(&bearer_cap);
+ if (rc) {
+ LOG_TRANS(trans, LOGL_ERROR, "Error composing Bearer Capability for CC Setup\n");
+ trans_free(trans);
+ msgb_free(msg);
+ return rc;
+ }
+ /* If no resulting codecs remain, error out. We cannot find a codec that matches both call legs. If the MGW were
+ * able to transcode, we could use non-identical codecs on each conn of the MGW endpoint, but we are aiming for
+ * finding a matching codec. */
+ if (bearer_cap.speech_ver[0] == -1) {
+ LOG_TRANS(trans, LOGL_ERROR, "%s: no codec match possible: %s\n",
+ get_mncc_name(setup->msg_type),
+ codec_filter_to_str(&trans->cc.codecs, &trans->cc.local, &trans->cc.remote));
+
+ /* incompatible codecs */
+ rc = mncc_release_ind(trans->net, trans, trans->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_INCOMPAT_DEST /* TODO: correct cause code? */);
+ trans->callref = 0;
+ trans_free(trans);
+ msgb_free(msg);
+ return rc;
+ }
+ break;
+ case GSM48_BCAP_ITCAP_UNR_DIG_INF:
+ if (csd_bs_list_to_bearer_cap(&bearer_cap, &trans->cc.local.bearer_services) == 0) {
+ LOG_TRANS(trans, LOGL_ERROR, "Error composing Bearer Capability for CC Setup\n");
+
+ /* incompatible codecs */
+ rc = mncc_release_ind(trans->net, trans, trans->callref,
+ GSM48_CAUSE_LOC_PRN_S_LU,
+ GSM48_CC_CAUSE_INCOMPAT_DEST /* TODO: correct cause code? */);
+ trans->callref = 0;
+ trans_free(trans);
+ msgb_free(msg);
+ return rc;
+ }
+ break;
}
+
/* Create a copy of the bearer capability in the transaction struct, so we can use this information later */
trans->bearer_cap = bearer_cap;
- /* If no resulting codecs remain, error out. We cannot find a codec that matches both call legs. If the MGW were
- * able to transcode, we could use non-identical codecs on each conn of the MGW endpoint, but we are aiming for
- * finding a matching codec. */
- if (bearer_cap.speech_ver[0] == -1) {
- LOG_TRANS(trans, LOGL_ERROR, "%s: no codec match possible: %s\n",
- get_mncc_name(setup->msg_type),
- codec_filter_to_str(&trans->cc.codecs, &trans->cc.local, &trans->cc.remote));
- /* incompatible codecs */
- rc = mncc_release_ind(trans->net, trans, trans->callref,
- GSM48_CAUSE_LOC_PRN_S_LU,
- GSM48_CC_CAUSE_INCOMPAT_DEST /* TODO: correct cause code? */);
- trans->callref = 0;
- trans_free(trans);
- msgb_free(msg);
- return rc;
- }
gsm48_encode_bearer_cap(msg, 0, &bearer_cap);
/* facility */
diff --git a/src/libmsc/msc_a.c b/src/libmsc/msc_a.c
index ca38206..11b242e 100644
--- a/src/libmsc/msc_a.c
+++ b/src/libmsc/msc_a.c
@@ -640,23 +640,48 @@
trans_cc_filter_run(cc_trans);
LOG_TRANS(cc_trans, LOGL_DEBUG, "Sending Assignment Command\n");
- if (!cc_trans->cc.local.audio_codecs.count) {
- LOG_TRANS(cc_trans, LOGL_ERROR, "Assignment not possible, no matching codec: %s\n",
- codec_filter_to_str(&cc_trans->cc.codecs, &cc_trans->cc.local, &cc_trans->cc.remote));
+ switch (cc_trans->bearer_cap.transfer) {
+ case GSM48_BCAP_ITCAP_SPEECH:
+ if (!cc_trans->cc.local.audio_codecs.count) {
+ LOG_TRANS(cc_trans, LOGL_ERROR, "Assignment not possible, no matching codec: %s\n",
+ codec_filter_to_str(&cc_trans->cc.codecs, &cc_trans->cc.local, &cc_trans->cc.remote));
+ call_leg_release(msc_a->cc.call_leg);
+ return;
+ }
+
+ /* Compose 48.008 Channel Type from the current set of codecs
+ * determined from both local and remote codec capabilities. */
+ if (sdp_audio_codecs_to_gsm0808_channel_type(&channel_type, &cc_trans->cc.local.audio_codecs)) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot compose Channel Type (Permitted Speech) from codecs: %s\n",
+ codec_filter_to_str(&cc_trans->cc.codecs, &cc_trans->cc.local, &cc_trans->cc.remote));
+ trans_free(cc_trans);
+ return;
+ }
+ break;
+ case GSM48_BCAP_ITCAP_UNR_DIG_INF:
+ if (!cc_trans->cc.local.bearer_services.count) {
+ LOG_TRANS(cc_trans, LOGL_ERROR, "Assignment not possible, no matching bearer service: %s\n",
+ csd_filter_to_str(&cc_trans->cc.csd, &cc_trans->cc.local, &cc_trans->cc.remote));
+ call_leg_release(msc_a->cc.call_leg);
+ return;
+ }
+
+ /* Compose 48.008 Channel Type from the current set of bearer
+ * services determined from local and remote capabilities. */
+ if (csd_bs_list_to_gsm0808_channel_type(&channel_type, &cc_trans->cc.local.bearer_services)) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot compose channel type from: %s\n",
+ csd_filter_to_str(&cc_trans->cc.csd, &cc_trans->cc.local, &cc_trans->cc.remote));
+ return;
+ }
+ break;
+ default:
+ LOG_TRANS(cc_trans, LOGL_ERROR, "Assignment not possible for information transfer capability %d\n",
+ cc_trans->bearer_cap.transfer);
call_leg_release(msc_a->cc.call_leg);
return;
}
- /* Compose 48.008 Channel Type from the current set of codecs determined from both local and remote codec
- * capabilities. */
- if (sdp_audio_codecs_to_gsm0808_channel_type(&channel_type, &cc_trans->cc.local.audio_codecs)) {
- LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot compose Channel Type (Permitted Speech) from codecs: %s\n",
- codec_filter_to_str(&cc_trans->cc.codecs, &cc_trans->cc.local, &cc_trans->cc.remote));
- trans_free(cc_trans);
- return;
- }
-
- /* The RAN side RTP address is known, so the voice Assignment can commence. */
+ /* The RAN side RTP address is known, so the voice/CSD Assignment can commence. */
msg = (struct ran_msg){
.msg_type = RAN_MSG_ASSIGNMENT_COMMAND,
.assignment_command = {
diff --git a/src/libmsc/sdp_msg.c b/src/libmsc/sdp_msg.c
index d636222..cd721d3 100644
--- a/src/libmsc/sdp_msg.c
+++ b/src/libmsc/sdp_msg.c
@@ -657,6 +657,10 @@
OSMO_STRBUF_PRINTF(sb, OSMO_SOCKADDR_STR_FMT, OSMO_SOCKADDR_STR_FMT_ARGS(&sdp->rtp));
OSMO_STRBUF_PRINTF(sb, "{");
OSMO_STRBUF_APPEND(sb, sdp_audio_codecs_to_str_buf, &sdp->audio_codecs);
+ if (sdp->bearer_services.count) {
+ OSMO_STRBUF_PRINTF(sb, ",");
+ OSMO_STRBUF_APPEND(sb, csd_bs_list_to_str_buf, &sdp->bearer_services);
+ }
OSMO_STRBUF_PRINTF(sb, "}");
return sb.chars_needed;
}
@@ -670,3 +674,15 @@
{
return sdp_msg_to_str_c(OTC_SELECT, sdp);
}
+
+void sdp_audio_codecs_set_csd(struct sdp_audio_codecs *ac)
+{
+ *ac = (struct sdp_audio_codecs){
+ .count = 1,
+ .codec = {{
+ .payload_type = 120,
+ .subtype_name = "CLEARMODE",
+ .rate = 8000,
+ }},
+ };
+}
diff --git a/src/libmsc/transaction_cc.c b/src/libmsc/transaction_cc.c
index cb1424b..35ec5dc 100644
--- a/src/libmsc/transaction_cc.c
+++ b/src/libmsc/transaction_cc.c
@@ -24,32 +24,52 @@
#include <osmocom/msc/transaction_cc.h>
#include <osmocom/msc/codec_filter.h>
+#include <osmocom/msc/csd_filter.h>
void trans_cc_filter_init(struct gsm_trans *trans)
{
trans->cc.codecs = (struct codec_filter){};
+ trans->cc.csd = (struct csd_filter){};
}
void trans_cc_filter_set_ran(struct gsm_trans *trans, enum osmo_rat_type ran_type)
{
codec_filter_set_ran(&trans->cc.codecs, ran_type);
+ csd_filter_set_ran(&trans->cc.csd, ran_type);
}
void trans_cc_filter_set_bss(struct gsm_trans *trans, struct msc_a *msc_a)
{
codec_filter_set_bss(&trans->cc.codecs, &msc_a->cc.compl_l3_codec_list_bss_supported);
+
+ /* For CSD, there is no list of supported bearer services passed in
+ * Complete Layer 3. TODO: make it configurable? */
}
void trans_cc_filter_run(struct gsm_trans *trans)
{
- codec_filter_run(&trans->cc.codecs, &trans->cc.local, &trans->cc.remote);
- LOG_TRANS(trans, LOGL_DEBUG, "codecs: %s\n",
- codec_filter_to_str(&trans->cc.codecs, &trans->cc.local, &trans->cc.remote));
+ switch (trans->bearer_cap.transfer) {
+ case GSM48_BCAP_ITCAP_SPEECH:
+ codec_filter_run(&trans->cc.codecs, &trans->cc.local, &trans->cc.remote);
+ LOG_TRANS(trans, LOGL_DEBUG, "codecs: %s\n",
+ codec_filter_to_str(&trans->cc.codecs, &trans->cc.local, &trans->cc.remote));
+ break;
+ case GSM48_BCAP_ITCAP_UNR_DIG_INF:
+ csd_filter_run(&trans->cc.csd, &trans->cc.local, &trans->cc.remote);
+ LOG_TRANS(trans, LOGL_DEBUG, "codec/BS: %s\n",
+ csd_filter_to_str(&trans->cc.csd, &trans->cc.local, &trans->cc.remote));
+ break;
+ default:
+ LOG_TRANS(trans, LOGL_ERROR, "Handling of information transfer capability %d not implemented\n",
+ trans->bearer_cap.transfer);
+ break;
+ }
}
void trans_cc_filter_set_ms_from_bc(struct gsm_trans *trans, const struct gsm_mncc_bearer_cap *bcap)
{
trans->cc.codecs.ms = (struct sdp_audio_codecs){0};
+ trans->cc.csd.ms = (struct csd_bs_list){0};
if (!bcap)
return;
@@ -58,6 +78,10 @@
case GSM48_BCAP_ITCAP_SPEECH:
sdp_audio_codecs_from_bearer_cap(&trans->cc.codecs.ms, bcap);
break;
+ case GSM48_BCAP_ITCAP_UNR_DIG_INF:
+ sdp_audio_codecs_set_csd(&trans->cc.codecs.ms);
+ csd_bs_list_from_bearer_cap(&trans->cc.csd.ms, bcap);
+ break;
default:
LOG_TRANS(trans, LOGL_ERROR, "Handling of information transfer capability %d not implemented\n",
bcap->transfer);
@@ -68,6 +92,7 @@
void trans_cc_set_remote_from_bc(struct gsm_trans *trans, const struct gsm_mncc_bearer_cap *bcap)
{
trans->cc.remote.audio_codecs = (struct sdp_audio_codecs){0};
+ trans->cc.remote.bearer_services = (struct csd_bs_list){0};
if (!bcap)
return;
@@ -76,6 +101,10 @@
case GSM48_BCAP_ITCAP_SPEECH:
sdp_audio_codecs_from_bearer_cap(&trans->cc.remote.audio_codecs, bcap);
break;
+ case GSM48_BCAP_ITCAP_UNR_DIG_INF:
+ sdp_audio_codecs_set_csd(&trans->cc.remote.audio_codecs);
+ csd_bs_list_from_bearer_cap(&trans->cc.remote.bearer_services, bcap);
+ break;
default:
LOG_TRANS(trans, LOGL_ERROR, "Handling of information transfer capability %d not implemented\n",
bcap->transfer);