| /* 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, uint8_t *ch_rate_type) |
| { |
| switch (bs_map[bs].rate) { |
| case 300: |
| *ch_rate_type = GSM0808_DATA_FULL_PREF; |
| return GSM0808_DATA_RATE_TRANSP_600; |
| case 1200: |
| *ch_rate_type = GSM0808_DATA_FULL_PREF; |
| return GSM0808_DATA_RATE_TRANSP_1k2; |
| case 2400: |
| *ch_rate_type = GSM0808_DATA_FULL_PREF; |
| return GSM0808_DATA_RATE_TRANSP_2k4; |
| case 4800: |
| *ch_rate_type = GSM0808_DATA_FULL_PREF; |
| return GSM0808_DATA_RATE_TRANSP_4k8; |
| case 9600: |
| *ch_rate_type = GSM0808_DATA_FULL_BM; |
| return GSM0808_DATA_RATE_TRANSP_9k6; |
| } |
| return -EINVAL; |
| } |
| |
| static int csd_bs_to_gsm0808_data_rate_non_transp(enum csd_bs bs, uint8_t *ch_rate_type) |
| { |
| uint16_t rate = bs_map[bs].rate; |
| |
| if (rate < 6000) { |
| *ch_rate_type = GSM0808_DATA_FULL_PREF; |
| return GSM0808_DATA_RATE_NON_TRANSP_6k0; |
| } |
| if (rate < 12000) { |
| *ch_rate_type = GSM0808_DATA_FULL_BM; |
| 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; |
| |
| /* 3.1kHz CSD calls won't have the rate adaptation field set |
| but do require rate adaptation. */ |
| if (cap->data.interm_rate && !ra) |
| ra = GSM48_BCAP_RA_V110_X30; |
| |
| 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; |
| if (found && i + 1 < list->count) |
| list->bs[i] = list->bs[i + 1]; |
| } |
| |
| if (found) |
| list->count--; |
| } |
| |
| 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, |
| }; |
| |
| if (!list->count) |
| return -EINVAL; |
| |
| if (csd_bs_is_transp(list->bs[0])) { |
| ct->data_transparent = true; |
| rc = csd_bs_to_gsm0808_data_rate_transp(list->bs[0], &ct->ch_rate_type); |
| } else { |
| rc = csd_bs_to_gsm0808_data_rate_non_transp(list->bs[0], &ct->ch_rate_type); |
| } |
| |
| if (rc < 0) |
| return -EINVAL; |
| |
| ct->data_rate = rc; |
| |
| /* 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; |
| } |
| |
| 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, |
| .mode = GSM48_BCAP_TMOD_CIRCUIT, |
| .coding = GSM48_BCAP_CODING_GSM_STD, |
| .radio = GSM48_BCAP_RRQ_FR_ONLY, |
| }; |
| 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.sig_access = GSM48_BCAP_SA_I440_I450; |
| cap->data.async = bs_map[bs].async; |
| if (bs_map[bs].transp) |
| cap->data.transp = GSM48_BCAP_TR_TRANSP; |
| else |
| cap->data.transp = GSM48_BCAP_TR_RLP; |
| |
| /* FIXME: proper values for sync/async (current: 8N1) */ |
| cap->data.nr_data_bits = 8; |
| cap->data.parity = GSM48_BCAP_PAR_NONE; |
| cap->data.nr_stop_bits = 1; |
| cap->data.modem_type = GSM48_BCAP_MT_NONE; |
| |
| switch (bs_map[bs].rate) { |
| case 300: |
| cap->data.user_rate = GSM48_BCAP_UR_300; |
| cap->data.interm_rate = GSM48_BCAP_IR_8k; |
| break; |
| case 1200: |
| cap->data.user_rate = GSM48_BCAP_UR_1200; |
| cap->data.interm_rate = GSM48_BCAP_IR_8k; |
| break; |
| case 2400: |
| cap->data.user_rate = GSM48_BCAP_UR_2400; |
| cap->data.interm_rate = GSM48_BCAP_IR_8k; |
| break; |
| case 4800: |
| cap->data.user_rate = GSM48_BCAP_UR_4800; |
| cap->data.interm_rate = GSM48_BCAP_IR_8k; |
| break; |
| case 9600: |
| cap->data.user_rate = GSM48_BCAP_UR_9600; |
| cap->data.interm_rate = GSM48_BCAP_IR_16k; |
| break; |
| default: |
| LOGP(DMSC, LOGL_ERROR, |
| "%s(): bs=%d (rate=%u) is not implemented\n", |
| __func__, bs, bs_map[bs].rate); |
| continue; |
| } |
| |
| /* 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; |
| } |
| } |