osmith has uploaded this change for review. ( 
https://gerrit.osmocom.org/c/osmo-msc/+/33369 )


Change subject: Add initial CSD support with external MNCC
......................................................................

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
---
M include/osmocom/msc/Makefile.am
A include/osmocom/msc/csd_bs.h
A include/osmocom/msc/csd_filter.h
M include/osmocom/msc/sdp_msg.h
M include/osmocom/msc/transaction.h
M src/libmsc/Makefile.am
A src/libmsc/csd_bs.c
A src/libmsc/csd_filter.c
M src/libmsc/gsm_04_08_cc.c
M src/libmsc/msc_a.c
M src/libmsc/sdp_msg.c
M src/libmsc/transaction_cc.c
12 files changed, 936 insertions(+), 58 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/osmo-msc refs/changes/69/33369/1

diff --git a/include/osmocom/msc/Makefile.am b/include/osmocom/msc/Makefile.am
index 948021a..3286c37 100644
--- a/include/osmocom/msc/Makefile.am
+++ b/include/osmocom/msc/Makefile.am
@@ -3,6 +3,8 @@
        cell_id_list.h \
        codec_filter.h \
        codec_mapping.h \
+       csd_bs.h \
+       csd_filter.h \
        db.h \
        debug.h \
        e_link.h \
diff --git a/include/osmocom/msc/csd_bs.h b/include/osmocom/msc/csd_bs.h
new file mode 100644
index 0000000..eee8692
--- /dev/null
+++ b/include/osmocom/msc/csd_bs.h
@@ -0,0 +1,54 @@
+/* 3GPP TS 122.002 Bearer Services */
+#pragma once
+
+#include <osmocom/gsm/mncc.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+
+enum csd_bs {
+       CSD_BS_NONE,
+
+       /* 3.1.1.1.2 */
+       CSD_BS_21_T_V110_0k3,
+       CSD_BS_22_T_V110_1k2,
+       CSD_BS_24_T_V110_2k4,
+       CSD_BS_25_T_V110_4k8,
+       CSD_BS_26_T_V110_9k6,
+
+       /* 3.1.1.2.2 */
+       CSD_BS_21_NT_V110_0k3,
+       CSD_BS_22_NT_V110_1k2,
+       CSD_BS_24_NT_V110_2k4,
+       CSD_BS_25_NT_V110_4k8,
+       CSD_BS_26_NT_V110_9k6,
+
+       /* 3.1.2.1.2 */
+       CSD_BS_31_T_V110_1k2,
+       CSD_BS_32_T_V110_2k4,
+       CSD_BS_33_T_V110_4k8,
+       CSD_BS_34_T_V110_9k6,
+
+       CSD_BS_MAX,
+};
+
+struct csd_bs_list {
+       unsigned int count;
+       enum csd_bs bs[CSD_BS_MAX];
+};
+
+void csd_bs_list_add_bs(struct csd_bs_list *list, enum csd_bs bs);
+int csd_bs_list_to_bearer_cap(struct gsm_mncc_bearer_cap *cap, const struct 
csd_bs_list *list);
+void csd_bs_list_from_bearer_cap(struct csd_bs_list *list, const struct 
gsm_mncc_bearer_cap *cap);
+
+int csd_bs_to_str_buf(char *buf, size_t buflen, enum csd_bs bs);
+char *csd_bs_to_str_c(void *ctx, enum csd_bs bs);
+const char *csd_bs_to_str(enum csd_bs bs);
+
+int csd_bs_list_to_str_buf(char *buf, size_t buflen, const struct csd_bs_list 
*list);
+char *csd_bs_list_to_str_c(void *ctx, const struct csd_bs_list *list);
+const char *csd_bs_list_to_str(const struct csd_bs_list *list);
+
+void csd_bs_list_add_bs(struct csd_bs_list *list, enum csd_bs bs);
+void csd_bs_list_remove(struct csd_bs_list *list, enum csd_bs bs);
+void csd_bs_list_intersection(struct csd_bs_list *dest, const struct 
csd_bs_list *other);
+
+int csd_bs_list_to_gsm0808_channel_type(struct gsm0808_channel_type *ct, const 
struct csd_bs_list *list);
diff --git a/include/osmocom/msc/csd_filter.h b/include/osmocom/msc/csd_filter.h
new file mode 100644
index 0000000..51ffff7
--- /dev/null
+++ b/include/osmocom/msc/csd_filter.h
@@ -0,0 +1,53 @@
+/* Filter/overlay data rates for CSD, across MS, RAN and CN limitations */
+/*
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <[email protected]>
+ * All Rights Reserved
+ *
+ * Author: Oliver Smith
+ *
+ * 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.
+ */
+#pragma once
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/mncc.h>
+#include <osmocom/mgcp_client/mgcp_client.h>
+
+#include <osmocom/msc/csd_bs.h>
+#include <osmocom/msc/sdp_msg.h>
+
+/* Combine various data rate selections to obtain a resulting set allowed by
+ * all of them. Members reflect the different entities/stages that select data
+ * rates in CSD. Call csd_filter_run() and obtain the resulting set in
+ * csd_filter.result. */
+struct csd_filter {
+       /* The fixed set available on the RAN type, per definition. */
+       struct csd_bs_list ran;
+       /* The services advertised by the MS Bearer Capabilities */
+       struct csd_bs_list ms;
+       /* If known, the set the current RAN cell allows / has available. This
+        * may not be available if the BSC does not issue this information
+        * early enough. Should be ignored if empty. */
+       struct csd_bs_list bss;
+
+       /* After a channel was assigned, this reflects the chosen BS. */
+       enum csd_bs assignment;
+};
+
+void csd_filter_set_ran(struct csd_filter *filter, enum osmo_rat_type 
ran_type);
+int csd_filter_run(struct csd_filter *filter, struct sdp_msg *result, const 
struct sdp_msg *remote);
+
+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);
+char *csd_filter_to_str_c(void *ctx, const struct csd_filter *filter, const 
struct sdp_msg *result, const struct sdp_msg *remote);
+const char *csd_filter_to_str(const struct csd_filter *filter, const struct 
sdp_msg *result, const struct sdp_msg *remote);
diff --git a/include/osmocom/msc/sdp_msg.h b/include/osmocom/msc/sdp_msg.h
index 1b905b7..e62f22b 100644
--- a/include/osmocom/msc/sdp_msg.h
+++ b/include/osmocom/msc/sdp_msg.h
@@ -4,6 +4,8 @@
 #include <osmocom/core/utils.h>
 #include <osmocom/core/sockaddr_str.h>

+#include <osmocom/msc/csd_bs.h>
+
 extern const struct value_string sdp_msg_payload_type_names[];
 static inline const char *sdp_msg_payload_type_name(unsigned int payload_type)
 { return get_value_string(sdp_msg_payload_type_names, payload_type); }
@@ -36,6 +38,7 @@
        unsigned int ptime;
        enum sdp_mode_e mode;
        struct sdp_audio_codecs audio_codecs;
+       struct csd_bs_list bearer_services;
 };

 #define foreach_sdp_audio_codec(/* struct sdp_audio_codec* */ CODEC, \
@@ -80,3 +83,5 @@
 int sdp_msg_to_str_buf(char *buf, size_t buflen, const struct sdp_msg *sdp);
 char *sdp_msg_to_str_c(void *ctx, const struct sdp_msg *sdp);
 const char *sdp_msg_to_str(const struct sdp_msg *sdp);
+
+void sdp_audio_codecs_set_csd(struct sdp_audio_codecs *ac);
diff --git a/include/osmocom/msc/transaction.h 
b/include/osmocom/msc/transaction.h
index 36aab78..d2a463d 100644
--- a/include/osmocom/msc/transaction.h
+++ b/include/osmocom/msc/transaction.h
@@ -9,6 +9,7 @@
 #include <osmocom/msc/msc_a.h>
 #include <osmocom/msc/debug.h>
 #include <osmocom/msc/codec_filter.h>
+#include <osmocom/msc/csd_filter.h>
 #include <osmocom/gsm/gsm0411_smc.h>
 #include <osmocom/gsm/gsm0411_smr.h>

@@ -107,8 +108,9 @@
                        struct osmo_lcls *lcls;
                        /* SDP as last received from the remote call leg. */
                        struct sdp_msg remote;
-                       /* Track codec choices from BSS and remote call leg */
+                       /* Track codec/CSD choices from BSS and remote call leg 
*/
                        struct codec_filter codecs;
+                       struct csd_filter csd;
                        /* Resulting choice from codecs/bearer services and the
                         * local RTP address to be sent to the remote call leg. 
*/
                        struct sdp_msg local;
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 <[email protected]>
+ * 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..20c0ae1
--- /dev/null
+++ b/src/libmsc/csd_filter.c
@@ -0,0 +1,167 @@
+/* Filter/overlay bearer service selections across MS, RAN and CN limitations 
*/
+/*
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <[email protected]>
+ * 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 */
+       static const enum csd_bs bs_geran[] = {
+               /* GSM-R */
+               CSD_BS_24_T_V110_2k4,
+               CSD_BS_25_T_V110_4k8,
+               CSD_BS_26_T_V110_9k6,
+               /* Other */
+               CSD_BS_21_T_V110_0k3,
+               CSD_BS_22_T_V110_1k2,
+               CSD_BS_21_NT_V110_0k3,
+               CSD_BS_22_NT_V110_1k2,
+               CSD_BS_24_NT_V110_2k4,
+               CSD_BS_25_NT_V110_4k8,
+               CSD_BS_26_NT_V110_9k6,
+               CSD_BS_31_T_V110_1k2,
+               CSD_BS_32_T_V110_2k4,
+               CSD_BS_33_T_V110_4k8,
+               CSD_BS_34_T_V110_9k6,
+       };
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(bs_geran); i++)
+               csd_bs_list_add_bs(list, bs_geran[i]);
+}
+
+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 */
+       static const enum csd_bs bs_utran[] = {
+               CSD_BS_21_NT_V110_0k3,
+               CSD_BS_22_NT_V110_1k2,
+               CSD_BS_24_NT_V110_2k4,
+               CSD_BS_25_NT_V110_4k8,
+               CSD_BS_26_NT_V110_9k6,
+       };
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(bs_utran); i++)
+               csd_bs_list_add_bs(list, bs_utran[i]);
+}
+
+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);

--
To view, visit https://gerrit.osmocom.org/c/osmo-msc/+/33369
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-msc
Gerrit-Branch: master
Gerrit-Change-Id: Ide8b8321e0401dcbe35da2ec9cee0abca821d99a
Gerrit-Change-Number: 33369
Gerrit-PatchSet: 1
Gerrit-Owner: osmith <[email protected]>
Gerrit-MessageType: newchange

Reply via email to