gerrit-log
Threads by month
- ----- 2026 -----
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
July 2023
- 1 participants
- 3051 discussions
17 Jul '23
laforge has submitted this change. ( https://gerrit.osmocom.org/c/osmo-msc/+/33516 )
Change subject: ASCI: Add callref to assignment command
......................................................................
ASCI: Add callref to assignment command
The (optional) call reference is required to assign a calling subscriber
to a voice group/bcast channel. The BSC can then determine to which
existing VGCS/VBS channel the MS is assigned to.
This IE is part of the GSM standard TS 48.008 (see §3.2.1.1)
Change-Id: I7955c6e0eebc930f85f360dda46be17cbd39e181
Related: OS#4854
---
M include/osmocom/msc/ran_msg.h
M src/libmsc/ran_msg_a.c
2 files changed, 24 insertions(+), 8 deletions(-)
Approvals:
laforge: Looks good to me, approved
neels: Looks good to me, approved
pespin: Looks good to me, but someone else must approve
Jenkins Builder: Verified
diff --git a/include/osmocom/msc/ran_msg.h b/include/osmocom/msc/ran_msg.h
index 0dadd4f..dc1483c 100644
--- a/include/osmocom/msc/ran_msg.h
+++ b/include/osmocom/msc/ran_msg.h
@@ -109,6 +109,8 @@
bool call_id_present;
uint32_t call_id;
struct osmo_lcls *lcls;
+ bool callref_present;
+ struct gsm0808_group_callref callref;
};
struct ran_cipher_mode_command {
diff --git a/src/libmsc/ran_msg_a.c b/src/libmsc/ran_msg_a.c
index 570a8d1..2533045 100644
--- a/src/libmsc/ran_msg_a.c
+++ b/src/libmsc/ran_msg_a.c
@@ -1375,13 +1375,6 @@
return 0;
}
-static void _gsm0808_assignment_extend_osmux(struct msgb *msg, uint8_t cid)
-{
- OSMO_ASSERT(msg->l3h[1] == msgb_l3len(msg) - 2); /*TL not in len */
- msgb_tv_put(msg, GSM0808_IE_OSMO_OSMUX_CID, cid);
- msg->l3h[1] = msgb_l3len(msg) - 2;
-}
-
/* Compose a BSSAP Assignment Command.
* Passing an RTP address is optional.
* The msub is passed merely for error logging. */
@@ -1468,8 +1461,13 @@
return NULL;
}
+ /* Append optional IEs: Group Call Reference and Osmux CID */
+ OSMO_ASSERT(msg->l3h[1] == msgb_l3len(msg) - 2); /* TL not in len */
+ if (ac->callref_present)
+ gsm0808_enc_group_callref(msg, &ac->callref);
if (ac->osmux_present)
- _gsm0808_assignment_extend_osmux(msg, ac->osmux_cid);
+ msgb_tv_put(msg, GSM0808_IE_OSMO_OSMUX_CID, ac->osmux_cid);
+ msg->l3h[1] = msgb_l3len(msg) - 2;
return msg;
}
--
To view, visit https://gerrit.osmocom.org/c/osmo-msc/+/33516
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: osmo-msc
Gerrit-Branch: master
Gerrit-Change-Id: I7955c6e0eebc930f85f360dda46be17cbd39e181
Gerrit-Change-Number: 33516
Gerrit-PatchSet: 12
Gerrit-Owner: jolly <andreas(a)eversberg.eu>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: neels <nhofmeyr(a)sysmocom.de>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-MessageType: merged
1
0
[L] Change in osmo-msc[master]: ASCI: Add simple implementation of Group Call Register
by laforge 17 Jul '23
by laforge 17 Jul '23
17 Jul '23
laforge has submitted this change. ( https://gerrit.osmocom.org/c/osmo-msc/+/33510 )
Change subject: ASCI: Add simple implementation of Group Call Register
......................................................................
ASCI: Add simple implementation of Group Call Register
This is a built-in data structure to store and handle voice group calls.
The GCR will be used by VGCS/VBS call control.
(Chg-Id: I9947403fde8212b66758104443c60aaacc8b1e7b)
The GCR will be used by VTY code.
(Chg-Id: I5bd034a62fc8b483f550d29103c2f7587198f590)
Change-Id: Ia74a4a865f943c5fb388cd28f9406005c92e663e
Related: OS#4854
---
M include/osmocom/msc/Makefile.am
A include/osmocom/msc/asci_gcr.h
M include/osmocom/msc/gsm_data.h
M src/libmsc/Makefile.am
A src/libmsc/asci_gcr.c
M src/libmsc/msc_net_init.c
6 files changed, 256 insertions(+), 0 deletions(-)
Approvals:
laforge: Looks good to me, approved
pespin: Looks good to me, but someone else must approve
Jenkins Builder: Verified
diff --git a/include/osmocom/msc/Makefile.am b/include/osmocom/msc/Makefile.am
index 3286c37..3c54dee 100644
--- a/include/osmocom/msc/Makefile.am
+++ b/include/osmocom/msc/Makefile.am
@@ -55,4 +55,5 @@
vlr.h \
vlr_sgs.h \
vty.h \
+ asci_gcr.h \
$(NULL)
diff --git a/include/osmocom/msc/asci_gcr.h b/include/osmocom/msc/asci_gcr.h
new file mode 100644
index 0000000..f2160c1
--- /dev/null
+++ b/include/osmocom/msc/asci_gcr.h
@@ -0,0 +1,55 @@
+/* Group Call Register (GCR) */
+/*
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Andreas Eversberg
+ *
+ * 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/>.
+ */
+#pragma once
+
+/* Group Call Register */
+struct gcr {
+ struct llist_head list;
+ enum trans_type trans_type;
+ char group_id[9];
+ uint16_t timeout;
+ bool mute_talker;
+ struct llist_head bss_list;
+};
+
+struct gcr_bss {
+ struct llist_head list;
+ int pc;
+ struct llist_head cell_list;
+};
+
+struct gcr_cell {
+ struct llist_head list;
+ uint16_t cell_id;
+};
+
+struct gcr_cell *gcr_add_cell(struct gcr_bss *bss, uint16_t cell_id);
+struct gcr_cell *gcr_find_cell(struct gcr_bss *bss, uint16_t cell_id);
+void gcr_rm_cell(struct gcr_bss *bss, uint16_t cell_id);
+struct gcr_bss *gcr_add_bss(struct gcr *gcr, int pc);
+struct gcr_bss *gcr_find_bss(struct gcr *gcr, int pc);
+void gcr_rm_bss(struct gcr *gcr, int pc);
+struct gcr *gcr_create(struct gsm_network *gsmnet, enum trans_type trans_type, const char *group_id);
+void gcr_destroy(struct gcr *gcr);
+struct gcr *gcr_by_group_id(struct gsm_network *gsmnet, enum trans_type trans_type, const char *group_id);
+struct gcr *gcr_by_callref(struct gsm_network *gsmnet, enum trans_type trans_type, uint32_t callref);
diff --git a/include/osmocom/msc/gsm_data.h b/include/osmocom/msc/gsm_data.h
index 2be4c35..6c0dac9 100644
--- a/include/osmocom/msc/gsm_data.h
+++ b/include/osmocom/msc/gsm_data.h
@@ -269,6 +269,11 @@
/* SMS queue config parameters */
struct sms_queue_config *sms_queue_cfg;
+
+ /* ASCI feature support */
+ struct {
+ struct llist_head gcr_lists;
+ } asci;
};
struct smpp_esme;
diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am
index fba7b53..c4e134c 100644
--- a/src/libmsc/Makefile.am
+++ b/src/libmsc/Makefile.am
@@ -76,6 +76,7 @@
sgs_iface.c \
sgs_server.c \
sgs_vty.c \
+ asci_gcr.c \
$(NULL)
if BUILD_IU
diff --git a/src/libmsc/asci_gcr.c b/src/libmsc/asci_gcr.c
new file mode 100644
index 0000000..2208151
--- /dev/null
+++ b/src/libmsc/asci_gcr.c
@@ -0,0 +1,175 @@
+/* Group Call Register (GCR) */
+/*
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Andreas Eversberg
+ *
+ * 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/msc/gsm_data.h>
+#include <osmocom/msc/transaction.h>
+
+#include <osmocom/msc/asci_gcr.h>
+
+#define GCR_DEFAULT_TIMEOUT 60
+
+static uint32_t pow10[9] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
+
+/* Add cell to BSS list. */
+struct gcr_cell *gcr_add_cell(struct gcr_bss *bss, uint16_t cell_id)
+{
+ struct gcr_cell *c;
+
+ c = talloc_zero(bss, struct gcr_cell);
+ if (!c)
+ return NULL;
+ c->cell_id = cell_id;
+ llist_add_tail(&c->list, &bss->cell_list);
+
+ return c;
+}
+
+/* Find cell entry in BSS list. */
+struct gcr_cell *gcr_find_cell(struct gcr_bss *bss, uint16_t cell_id)
+{
+ struct gcr_cell *c;
+
+ llist_for_each_entry(c, &bss->cell_list, list) {
+ if (c->cell_id == cell_id)
+ return c;
+ }
+
+ return NULL;
+}
+
+/* Remove cell entry from BSS list. */
+void gcr_rm_cell(struct gcr_bss *bss, uint16_t cell_id)
+{
+ struct gcr_cell *c = gcr_find_cell(bss, cell_id);
+
+ if (c) {
+ llist_del(&c->list);
+ talloc_free(c);
+ }
+}
+
+/* Add BSS to GCR list. */
+struct gcr_bss *gcr_add_bss(struct gcr *gcr, int pc)
+{
+ struct gcr_bss *b;
+
+ b = talloc_zero(gcr, struct gcr_bss);
+ if (!b)
+ return NULL;
+ INIT_LLIST_HEAD(&b->cell_list);
+ b->pc = pc;
+ llist_add_tail(&b->list, &gcr->bss_list);
+
+ return b;
+}
+
+/* Find BSS entry in GCR list. */
+struct gcr_bss *gcr_find_bss(struct gcr *gcr, int pc)
+{
+ struct gcr_bss *b;
+
+ llist_for_each_entry(b, &gcr->bss_list, list) {
+ if (b->pc == pc)
+ return b;
+ }
+
+ return NULL;
+}
+
+/* Remove BSS entry from GCR list. */
+void gcr_rm_bss(struct gcr *gcr, int pc)
+{
+ struct gcr_bss *b = gcr_find_bss(gcr, pc);
+
+ if (b) {
+ /* All cell definitons will be removed, as they are attached to BSS. */
+ llist_del(&b->list);
+ talloc_free(b);
+ }
+}
+
+/* Create a new (empty) GCR list. */
+struct gcr *gcr_create(struct gsm_network *gsmnet, enum trans_type trans_type, const char *group_id)
+{
+ struct gcr *gcr;
+
+ gcr = talloc_zero(gsmnet, struct gcr);
+ if (!gcr)
+ return NULL;
+
+ INIT_LLIST_HEAD(&gcr->bss_list);
+ gcr->trans_type = trans_type;
+ gcr->timeout = GCR_DEFAULT_TIMEOUT;
+ gcr->mute_talker = true;
+ osmo_strlcpy(gcr->group_id, group_id, sizeof(gcr->group_id));
+ llist_add_tail(&gcr->list, &gsmnet->asci.gcr_lists);
+
+ return gcr;
+}
+
+/* Destroy a GCR list. */
+void gcr_destroy(struct gcr *gcr)
+{
+ /* All BSS definitons will be removed, as they are attached to GCR. */
+ llist_del(&gcr->list);
+ talloc_free(gcr);
+}
+
+/* Find GCR list by group ID. */
+struct gcr *gcr_by_group_id(struct gsm_network *gsmnet, enum trans_type trans_type, const char *group_id)
+{
+ struct gcr *gcr;
+
+ llist_for_each_entry(gcr, &gsmnet->asci.gcr_lists, list) {
+ if (gcr->trans_type == trans_type && !strcmp(gcr->group_id, group_id))
+ return gcr;
+ }
+
+ return NULL;
+}
+
+/* Find GCR list by callref. */
+struct gcr *gcr_by_callref(struct gsm_network *gsmnet, enum trans_type trans_type, uint32_t callref)
+{
+ struct gcr *most_specific_gcr = NULL, *gcr;
+ int a, b;
+ size_t most_specific_len = 0, l;
+
+ llist_for_each_entry(gcr, &gsmnet->asci.gcr_lists, list) {
+ /* Compare only the digits in Group ID with the digits in callref.
+ * callref is an integer. Only the remainder, based on Group ID length, is checked. */
+ l = strlen(gcr->group_id);
+ a = atoi(gcr->group_id);
+ OSMO_ASSERT(l < ARRAY_SIZE(pow10));
+ b = callref % pow10[l];
+ if (gcr->trans_type == trans_type && a == b) {
+ /* Get most specific GROUP ID, no matter what order they are stored. */
+ if (l > most_specific_len) {
+ most_specific_gcr = gcr;
+ most_specific_len = l;
+ }
+ }
+ }
+
+ return most_specific_gcr;
+}
diff --git a/src/libmsc/msc_net_init.c b/src/libmsc/msc_net_init.c
index 45500b2..f63acf8 100644
--- a/src/libmsc/msc_net_init.c
+++ b/src/libmsc/msc_net_init.c
@@ -75,6 +75,7 @@
INIT_LLIST_HEAD(&net->trans_list);
INIT_LLIST_HEAD(&net->upqueue);
INIT_LLIST_HEAD(&net->neighbor_ident_list);
+ INIT_LLIST_HEAD(&net->asci.gcr_lists);
/* init statistics */
net->msc_ctrs = rate_ctr_group_alloc(net, &msc_ctrg_desc, 0);
--
To view, visit https://gerrit.osmocom.org/c/osmo-msc/+/33510
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: osmo-msc
Gerrit-Branch: master
Gerrit-Change-Id: Ia74a4a865f943c5fb388cd28f9406005c92e663e
Gerrit-Change-Number: 33510
Gerrit-PatchSet: 12
Gerrit-Owner: jolly <andreas(a)eversberg.eu>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-CC: neels <nhofmeyr(a)sysmocom.de>
Gerrit-MessageType: merged
1
0
[L] Change in osmo-msc[master]: ASCI: Add functions to transcode VGCS/VBS messages on A-interface
by laforge 17 Jul '23
by laforge 17 Jul '23
17 Jul '23
laforge has submitted this change. ( https://gerrit.osmocom.org/c/osmo-msc/+/33505 )
Change subject: ASCI: Add functions to transcode VGCS/VBS messages on A-interface
......................................................................
ASCI: Add functions to transcode VGCS/VBS messages on A-interface
Change-Id: I6b1f088201e7ef4a58762937855a1d358973882c
Related: OS#4854
---
M include/osmocom/msc/ran_msg.h
M src/libmsc/ran_msg.c
M src/libmsc/ran_msg_a.c
3 files changed, 558 insertions(+), 0 deletions(-)
Approvals:
laforge: Looks good to me, approved
neels: Looks good to me, approved
pespin: Looks good to me, but someone else must approve
Jenkins Builder: Verified
diff --git a/include/osmocom/msc/ran_msg.h b/include/osmocom/msc/ran_msg.h
index 3e0ac60..0dadd4f 100644
--- a/include/osmocom/msc/ran_msg.h
+++ b/include/osmocom/msc/ran_msg.h
@@ -69,6 +69,26 @@
RAN_MSG_HANDOVER_SUCCEEDED,
RAN_MSG_HANDOVER_COMPLETE,
RAN_MSG_HANDOVER_FAILURE,
+ RAN_MSG_VGCS_VBS_SETUP,
+ RAN_MSG_VGCS_VBS_SETUP_ACK,
+ RAN_MSG_VGCS_VBS_SETUP_REFUSE,
+ RAN_MSG_VGCS_VBS_ASSIGN_REQ,
+ RAN_MSG_VGCS_VBS_ASSIGN_RES,
+ RAN_MSG_VGCS_VBS_ASSIGN_FAIL,
+ RAN_MSG_VGCS_VBS_QUEUING_IND,
+ RAN_MSG_UPLINK_REQUEST,
+ RAN_MSG_UPLINK_REQUEST_ACK,
+ RAN_MSG_UPLINK_REQUEST_CNF,
+ RAN_MSG_UPLINK_APPLICATION_DATA,
+ RAN_MSG_UPLINK_RELEASE_IND,
+ RAN_MSG_UPLINK_REJECT_CMD,
+ RAN_MSG_UPLINK_RELEASE_CMD,
+ RAN_MSG_UPLINK_SEIZED_CMD,
+ RAN_MSG_VGCS_ADDITIONAL_INFO,
+ RAN_MSG_VGCS_VBS_AREA_CELL_INFO,
+ RAN_MSG_VGCS_VBS_ASSIGN_STATUS,
+ RAN_MSG_VGCS_SMS,
+ RAN_MSG_NOTIFICATION_DATA,
};
extern const struct value_string ran_msg_type_names[];
@@ -270,6 +290,33 @@
} handover_failure;
struct ran_handover_request handover_request;
struct ran_handover_request_ack handover_request_ack;
+ struct gsm0808_vgcs_vbs_setup vgcs_vbs_setup;
+ struct gsm0808_vgcs_vbs_setup_ack vgcs_vbs_setup_ack;
+ struct {
+ enum gsm0808_cause cause;
+ } vgcs_vbs_setup_refuse;
+ struct gsm0808_vgcs_vbs_assign_req vgcs_vbs_assign_req;
+ struct gsm0808_vgcs_vbs_assign_res vgcs_vbs_assign_res;
+ struct gsm0808_vgcs_vbs_assign_fail vgcs_vbs_assign_fail;
+ struct gsm0808_uplink_request uplink_request;
+ struct gsm0808_uplink_request_ack uplink_request_ack;
+ struct gsm0808_uplink_request_cnf uplink_request_cnf;
+ struct gsm0808_uplink_app_data uplink_app_data;
+ struct gsm0808_uplink_release_ind uplink_release_ind;
+ struct gsm0808_uplink_seized_cmd uplink_seized_cmd;
+ struct gsm0808_uplink_reject_cmd uplink_reject_cmd;
+ struct {
+ enum gsm0808_cause cause;
+ } uplink_release_cmd;
+ struct {
+ struct gsm0808_talker_identity talker_identity;
+ } vgcs_additional_info;
+ struct gsm0808_vgcs_vbs_area_cell_info vgcs_vbs_area_cell_info;
+ struct gsm0808_vgcs_vbs_assign_stat vgcs_vbs_assign_stat;
+ struct {
+ struct gsm0808_sms_to_vgcs sms_to_vgcs;
+ } vgcs_sms;
+ struct gsm0808_notification_data notification_data;
};
};
diff --git a/src/libmsc/ran_msg.c b/src/libmsc/ran_msg.c
index 11564d8..14f250d 100644
--- a/src/libmsc/ran_msg.c
+++ b/src/libmsc/ran_msg.c
@@ -51,6 +51,25 @@
{ RAN_MSG_HANDOVER_DETECT, "HANDOVER_DETECT" },
{ RAN_MSG_HANDOVER_COMPLETE, "HANDOVER_COMPLETE" },
{ RAN_MSG_HANDOVER_FAILURE, "HANDOVER_FAILURE" },
+ { RAN_MSG_VGCS_VBS_SETUP, "VGCS_VBS_SETUP" },
+ { RAN_MSG_VGCS_VBS_SETUP_ACK, "VGCS_VBS_SETUP_ACK" },
+ { RAN_MSG_VGCS_VBS_SETUP_REFUSE, "VGCS_VBS_SETUP_REFUSE" },
+ { RAN_MSG_VGCS_VBS_ASSIGN_REQ, "VGCS_VBS_ASSIGN_REQ" },
+ { RAN_MSG_VGCS_VBS_ASSIGN_RES, "VGCS_VBS_ASSIGN_RES" },
+ { RAN_MSG_VGCS_VBS_ASSIGN_FAIL, "VGCS_VBS_ASSIGN_FAIL" },
+ { RAN_MSG_VGCS_VBS_QUEUING_IND, "VGCS_VBS_QUEUING_IND" },
+ { RAN_MSG_UPLINK_REQUEST, "UPLINK_REQUEST" },
+ { RAN_MSG_UPLINK_REQUEST_ACK, "UPLINK_REQUEST_ACK" },
+ { RAN_MSG_UPLINK_REQUEST_CNF, "UPLINK_REQUEST_CNF" },
+ { RAN_MSG_UPLINK_APPLICATION_DATA, "UPLINK_APPLICATION_DATA" },
+ { RAN_MSG_UPLINK_RELEASE_IND, "UPLINK_RELEASE_IND" },
+ { RAN_MSG_UPLINK_REJECT_CMD, "UPLINK_REJECT_CMD" },
+ { RAN_MSG_UPLINK_RELEASE_CMD, "UPLINK_RELEASE_CMD" },
+ { RAN_MSG_UPLINK_SEIZED_CMD, "UPLINK_SEIZED_CMD" },
+ { RAN_MSG_VGCS_ADDITIONAL_INFO, "VGCS_ADDITIONAL_INFO" },
+ { RAN_MSG_VGCS_VBS_AREA_CELL_INFO, "VGCS_VBS_AREA_CELL_INFO" },
+ { RAN_MSG_VGCS_VBS_ASSIGN_STATUS, "VGCS_VBS_ASSIGN_STATUS" },
+ { RAN_MSG_VGCS_SMS, "VGCS_SMS" },
{}
};
diff --git a/src/libmsc/ran_msg_a.c b/src/libmsc/ran_msg_a.c
index c49adf4..570a8d1 100644
--- a/src/libmsc/ran_msg_a.c
+++ b/src/libmsc/ran_msg_a.c
@@ -742,6 +742,438 @@
return ran_decoded(ran_dec, &ran_dec_msg);
}
+static int ran_a_decode_vgcs_vbs_setup_ack(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_VGCS_VBS_SETUP_ACK,
+ .msg_name = "BSSMAP VGCS/VBS SETUP ACKNOWLEDGE",
+ };
+ struct gsm0808_vgcs_vbs_setup_ack *r = &ran_dec_msg.vgcs_vbs_setup_ack;
+ int rc;
+
+ const struct tlv_p_entry *ie_flags = TLVP_GET(tp, GSM0808_IE_VGCS_FEATURE_FLAGS);
+
+ /* VGCS Feature Flags, 3.2.2.88 */
+ if (ie_flags) {
+ rc = gsm0808_dec_vgcs_feature_flags(&r->flags, ie_flags->val, ie_flags->len);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Unable to decode VGCS/VBS Feature Flags\n");
+ return -EINVAL;
+ }
+ r->vgcs_feature_flags_present = true;
+ }
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_vgcs_vbs_setup_refuse(struct ran_dec *ran_dec, const struct msgb *msg,
+ const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_VGCS_VBS_SETUP_REFUSE,
+ .msg_name = "BSSMAP VGCS/VBS SETUP REFUSE",
+ };
+
+ const struct tlv_p_entry *ie_cause = TLVP_GET(tp, GSM0808_IE_CAUSE);
+
+ /* Cause, 3.2.2.5 */
+ if (!ie_cause || ie_cause->len < 1) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Cause\n");
+ return -EINVAL;
+ }
+ ran_dec_msg.vgcs_vbs_setup_refuse.cause = ie_cause->val[0];
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_vgcs_vbs_assign_res(struct ran_dec *ran_dec, const struct msgb *msg,
+ const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_VGCS_VBS_ASSIGN_RES,
+ .msg_name = "BSSMAP VGCS/VBS ASSIGNMENT RESULT",
+ };
+ struct gsm0808_vgcs_vbs_assign_res *r = &ran_dec_msg.vgcs_vbs_assign_res;
+ int rc;
+
+ const struct tlv_p_entry *ie_channel_type = TLVP_GET(tp, GSM0808_IE_CHANNEL_TYPE);
+ const struct tlv_p_entry *ie_cell_id = TLVP_GET(tp, GSM0808_IE_CELL_IDENTIFIER);
+ const struct tlv_p_entry *ie_chosen_channel = TLVP_GET(tp, GSM0808_IE_CHOSEN_CHANNEL);
+ const struct tlv_p_entry *ie_cic = TLVP_GET(tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE);
+ const struct tlv_p_entry *ie_circuit_pool = TLVP_GET(tp, GSM0808_IE_CIRCUIT_POOL);
+ const struct tlv_p_entry *ie_aoip = TLVP_GET(tp, GSM0808_IE_AOIP_TRASP_ADDR);
+ const struct tlv_p_entry *ie_call_id = TLVP_GET(tp, GSM0808_IE_CALL_ID);
+
+ /* Channel Type, 3.2.2.11 */
+ if (!ie_channel_type) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Channel Type\n");
+ return -EINVAL;
+ }
+ if (gsm0808_dec_channel_type(&r->channel_type, ie_channel_type->val, ie_channel_type->len) <= 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Failed to decode Channel Type IE\n");
+ return -EINVAL;
+ }
+
+ /* Cell Identifier, 3.2.2.17 */
+ if (!ie_cell_id) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Cell Identifier\n");
+ return -EINVAL;
+ }
+ rc = gsm0808_dec_cell_id(&r->cell_identifier, ie_cell_id->val, ie_cell_id->len);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc);
+ return -EINVAL;
+ }
+
+ /* Chosen Channel, 3.2.2.33 */
+ if (ie_chosen_channel) {
+ r->chosen_channel = ie_chosen_channel->val[0];
+ r->chosen_channel_present = true;
+ }
+
+ /* Circuit Identity Code, 3.2.2.2 */
+ if (ie_cic) {
+ if (ie_cic->len != 2) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Circuit Identity Code has invalid length.\n");
+ return -EINVAL;
+ }
+ r->cic = *(uint16_t *)ie_cic->val;
+ r->cic_present = true;
+ }
+
+ /* Circuit Pool, 3.2.2.45 */
+ if (ie_circuit_pool) {
+ r->circuit_pool = ie_circuit_pool->val[0];
+ r->circuit_pool_present = true;
+ }
+
+ /* AoIP Transport Layer Address (BSS), 3.2.2.102 */
+ if (ie_aoip) {
+ if (gsm0808_dec_aoip_trasp_addr(&r->aoip_transport_layer, ie_aoip->val, ie_aoip->len) < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode AoIP transport address\n");
+ return -EINVAL;
+ }
+ r->aoip_transport_layer_present = true;
+ }
+
+ if (ie_call_id) {
+ if (ie_call_id->len != 4) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Call Identifier has invalid length.\n");
+ return -EINVAL;
+ }
+ r->call_id = osmo_load32le(ie_call_id->val);
+ r->call_id_present = true;
+ }
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_vgcs_vbs_assign_fail(struct ran_dec *ran_dec, const struct msgb *msg,
+ const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_VGCS_VBS_ASSIGN_FAIL,
+ .msg_name = "BSSMAP VGCS/VBS ASSIGNMENT FAILURE",
+ };
+ struct gsm0808_vgcs_vbs_assign_fail *r = &ran_dec_msg.vgcs_vbs_assign_fail;
+ int rc;
+
+ const struct tlv_p_entry *ie_cause = TLVP_GET(tp, GSM0808_IE_CAUSE);
+ const struct tlv_p_entry *ie_circuit_pool = TLVP_GET(tp, GSM0808_IE_CIRCUIT_POOL);
+ const struct tlv_p_entry *ie_circuit_pool_list = TLVP_GET(tp, GSM0808_IE_CIRCUIT_POOL_LIST);
+ const struct tlv_p_entry *ie_codec_list_bss_supported = TLVP_GET(tp, GSM0808_IE_SPEECH_CODEC_LIST);
+
+ /* Cause, 3.2.2.5 */
+ if (!ie_cause || ie_cause->len < 1) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Cause\n");
+ return -EINVAL;
+ }
+ r->cause = ie_cause->val[0];
+
+ /* Circuit Pool, 3.2.2.45 */
+ if (ie_circuit_pool) {
+ r->circuit_pool = ie_circuit_pool->val[0];
+ r->circuit_pool_present = true;
+ }
+
+ /* Circuit Pool List, 3.2.2.46 */
+ if (ie_circuit_pool_list && ie_circuit_pool_list->len) {
+ if (ie_circuit_pool_list->len > CIRCUIT_POOL_LIST_MAXLEN) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Circuit Pool List has invalid length.\n");
+ return -EINVAL;
+ }
+ memcpy(r->cpl.pool, ie_circuit_pool_list->val, ie_circuit_pool_list->len);
+ r->cpl.list_len = ie_circuit_pool_list->len;
+ r->cpl_present = true;
+ }
+
+ /* Codec List (BSS Supported) 3.2.2.103 */
+ if (ie_codec_list_bss_supported) {
+ rc = gsm0808_dec_speech_codec_list(&r->codec_list_bss_supported,
+ ie_codec_list_bss_supported->val, ie_codec_list_bss_supported->len);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR,
+ "Complete Layer 3 Information: unable to decode IE Codec List (BSS Supported)"
+ " (rc=%d), continuing anyway\n", rc);
+ /* This IE is not critical, do not abort with error. */
+ } else
+ r->codec_list_present = true;
+ }
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_vgcs_vbs_queuing_ind(struct ran_dec *ran_dec, const struct msgb *msg,
+ const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_VGCS_VBS_QUEUING_IND,
+ .msg_name = "BSSMAP VGCS/VBS QUEUING INDICATION",
+ };
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_uplink_request(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_UPLINK_REQUEST,
+ .msg_name = "BSSMAP UPLINK REQUEST",
+ };
+ struct gsm0808_uplink_request *r = &ran_dec_msg.uplink_request;
+ int rc;
+
+ const struct tlv_p_entry *ie_talker_priority = TLVP_GET(tp, GSM0808_IE_TALKER_PRIORITY);
+ const struct tlv_p_entry *ie_cell_id = TLVP_GET(tp, GSM0808_IE_CELL_IDENTIFIER);
+ const struct tlv_p_entry *ie_l3_info = TLVP_GET(tp, GSM0808_IE_LAYER_3_INFORMATION);
+ const struct tlv_p_entry *ie_mi = TLVP_GET(tp, GSM0808_IE_MOBILE_IDENTITY);
+
+ /* Talker Priority, 3.2.2.89 */
+ if (ie_talker_priority) {
+ r->talker_priority = ie_talker_priority->val[0] & 0x03;
+ r->talker_priority_present = true;
+ }
+
+ /* Cell Identifier, 3.2.2.17 */
+ if (ie_cell_id) {
+ rc = gsm0808_dec_cell_id(&r->cell_identifier, ie_cell_id->val, ie_cell_id->len);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc);
+ return -EINVAL;
+ }
+ }
+
+ /* Layer 3 Information, 3.2.2.24 */
+ if (ie_l3_info && ie_l3_info->len) {
+ if (ie_l3_info->len > LAYER_3_INFORMATION_MAXLEN) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Call Identifier has invalid length.\n");
+ return -EINVAL;
+ }
+ memcpy(r->l3.l3, ie_l3_info->val, ie_l3_info->len);
+ r->l3.l3_len = ie_l3_info->len;
+ r->l3_present = true;
+ }
+
+ /* Mobile Identity, 3.2.2.41 */
+ if (ie_mi) {
+ if (osmo_mobile_identity_decode(&r->mi, ie_mi->val, ie_mi->len, false)) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Mobile Identity gave rc=%d\n", rc);
+ return -EINVAL;
+ }
+ r->mi_present = true;
+ }
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_uplink_request_cnf(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_UPLINK_REQUEST_CNF,
+ .msg_name = "BSSMAP UPLINK REQUEST CONFIRM",
+ };
+ struct gsm0808_uplink_request_cnf *r = &ran_dec_msg.uplink_request_cnf;
+ int rc;
+
+ const struct tlv_p_entry *ie_cell_id = TLVP_GET(tp, GSM0808_IE_CELL_IDENTIFIER);
+ const struct tlv_p_entry *ie_talker_identity = TLVP_GET(tp, GSM0808_IE_TALKER_IDENTITY);
+ const struct tlv_p_entry *ie_l3_info = TLVP_GET(tp, GSM0808_IE_LAYER_3_INFORMATION);
+
+ /* Cell Identifier, 3.2.2.17 */
+ if (!ie_cell_id) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Cell Identifier\n");
+ return -EINVAL;
+ }
+ rc = gsm0808_dec_cell_id(&r->cell_identifier, ie_cell_id->val, ie_cell_id->len);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc);
+ return -EINVAL;
+ }
+
+ /* Talker Identity, 3.2.2.91 */
+ if (ie_talker_identity) {
+ rc = gsm0808_dec_talker_identity(&r->talker_identity, ie_talker_identity->val, ie_talker_identity->len);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Talker Identity gave rc=%d\n", rc);
+ return -EINVAL;
+ }
+ r->talker_identity_present = true;
+ }
+
+ /* Layer 3 Information, 3.2.2.24 */
+ if (!ie_l3_info) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Layer 3 Information\n");
+ return -EINVAL;
+ }
+ if (ie_l3_info->len > LAYER_3_INFORMATION_MAXLEN) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Call Identifier has invalid length.\n");
+ return -EINVAL;
+ }
+ memcpy(r->l3.l3, ie_l3_info->val, ie_l3_info->len);
+ r->l3.l3_len = ie_l3_info->len;
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_uplink_application_data(struct ran_dec *ran_dec, const struct msgb *msg,
+ const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_UPLINK_APPLICATION_DATA,
+ .msg_name = "BSSMAP UPLINK APPLICATION DATA",
+ };
+ struct gsm0808_uplink_app_data *r = &ran_dec_msg.uplink_app_data;
+ int rc;
+
+ const struct tlv_p_entry *ie_cell_id = TLVP_GET(tp, GSM0808_IE_CELL_IDENTIFIER);
+ const struct tlv_p_entry *ie_l3_info = TLVP_GET(tp, GSM0808_IE_LAYER_3_INFORMATION);
+ const struct tlv_p_entry *ie_app_data = TLVP_GET(tp, GSM0808_IE_APP_DATA);
+
+ /* Cell Identifier, 3.2.2.17 */
+ if (!ie_cell_id) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Cell Identifier\n");
+ return -EINVAL;
+ }
+ rc = gsm0808_dec_cell_id(&r->cell_identifier, ie_cell_id->val, ie_cell_id->len);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc);
+ return -EINVAL;
+ }
+
+ /* Layer 3 Information, 3.2.2.24 */
+ if (!ie_l3_info) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Layer 3 Information\n");
+ return -EINVAL;
+ }
+ if (ie_l3_info->len > LAYER_3_INFORMATION_MAXLEN) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Call Identifier has invalid length.\n");
+ return -EINVAL;
+ }
+ memcpy(r->l3.l3, ie_l3_info->val, ie_l3_info->len);
+ r->l3.l3_len = ie_l3_info->len;
+
+ /* Application Data Information, 3.2.2.100 */
+ if (!ie_app_data || ie_app_data->len < 1) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Application Data Information\n");
+ return -EINVAL;
+ }
+ r->bt_ind = ie_app_data->val[0] & 0x01;
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_uplink_release_ind(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_UPLINK_RELEASE_IND,
+ .msg_name = "BSSMAP UPLINK RELEASE INDICATION",
+ };
+ struct gsm0808_uplink_release_ind *r = &ran_dec_msg.uplink_release_ind;
+
+ const struct tlv_p_entry *ie_cause = TLVP_GET(tp, GSM0808_IE_CAUSE);
+ const struct tlv_p_entry *ie_talker_priority = TLVP_GET(tp, GSM0808_IE_TALKER_PRIORITY);
+
+ /* Cause, 3.2.2.5 */
+ if (!ie_cause || ie_cause->len < 1) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Cause\n");
+ return -EINVAL;
+ }
+ r->cause = ie_cause->val[0];
+
+ /* Talker Priority, 3.2.2.89 */
+ if (ie_talker_priority) {
+ r->talker_priority = ie_talker_priority->val[0] & 0x03;
+ r->talker_priority_present = true;
+ }
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
+static int ran_a_decode_vgcs_vbs_assign_status(struct ran_dec *ran_dec, const struct msgb *msg,
+ const struct tlv_parsed *tp)
+{
+ struct ran_msg ran_dec_msg = {
+ .msg_type = RAN_MSG_VGCS_VBS_ASSIGN_STATUS,
+ .msg_name = "BSSMAP VGCS/VBS ASSIGNMENT STATUS",
+ };
+ struct gsm0808_vgcs_vbs_assign_stat *r = &ran_dec_msg.vgcs_vbs_assign_stat;
+ int rc;
+
+ const struct tlv_p_entry *ie_cils_est = TLVP_GET(tp, GSM0808_IE_CELL_ID_LIST_SEG_EST_CELLS);
+ const struct tlv_p_entry *ie_cils_tbe = TLVP_GET(tp, GSM0808_IE_CELL_ID_LIST_SEG_CELLS_TBE);
+ const struct tlv_p_entry *ie_cils_rel = TLVP_GET(tp, GSM0808_IE_CELL_ID_LIST_SEG_REL_CELLS);
+ const struct tlv_p_entry *ie_cils_ne = TLVP_GET(tp, GSM0808_IE_CELL_ID_LIST_SEG_NE_CELLS);
+ const struct tlv_p_entry *ie_cell_status = TLVP_GET(tp, GSM0808_IE_VGCS_VBS_CELL_STATUS);
+
+ /* Cell Identifier List Segment, 3.2.2.27b */
+ if (ie_cils_est) {
+ rc = gsm0808_dec_cell_id_list_segment(&r->cils_est, ie_cils_est->val, ie_cils_est->len);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc);
+ return -EINVAL;
+ }
+ r->cils_est_present = true;
+ }
+
+ /* Cell Identifier List Segment, 3.2.2.27c */
+ if (ie_cils_tbe) {
+ rc = gsm0808_dec_cell_id_list_segment(&r->cils_tbe, ie_cils_tbe->val, ie_cils_tbe->len);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc);
+ return -EINVAL;
+ }
+ r->cils_tbe_present = true;
+ }
+
+ /* Cell Identifier List Segment, 3.2.2.27e */
+ if (ie_cils_rel) {
+ rc = gsm0808_dec_cell_id_list_segment(&r->cils_rel, ie_cils_rel->val, ie_cils_rel->len);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc);
+ return -EINVAL;
+ }
+ r->cils_rel_present = true;
+ }
+
+ /* Cell Identifier List Segment, 3.2.2.27f */
+ if (ie_cils_ne) {
+ rc = gsm0808_dec_cell_id_list_segment(&r->cils_ne, ie_cils_ne->val, ie_cils_ne->len);
+ if (rc < 0) {
+ LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc);
+ return -EINVAL;
+ }
+ r->cils_ne_present = true;
+ }
+
+ /* VGCS/VBS Cell Status, 3.2.2.94 */
+ if (ie_cell_status && ie_cell_status->len) {
+ r->cell_status = ie_cell_status->val[0] & 0x73;
+ r->cell_status_present = true;
+ }
+
+ return ran_decoded(ran_dec, &ran_dec_msg);
+}
+
static int ran_a_decode_bssmap(struct ran_dec *ran_dec, struct msgb *bssmap)
{
struct tlv_parsed tp[2];
@@ -813,6 +1245,26 @@
return ran_a_decode_sapi_n_reject(ran_dec, bssmap, tp);
case BSS_MAP_MSG_LCLS_NOTIFICATION:
return ran_a_decode_lcls_notification(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_VGCS_VBS_SETUP_ACK:
+ return ran_a_decode_vgcs_vbs_setup_ack(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_VGCS_VBS_SETUP_REFUSE:
+ return ran_a_decode_vgcs_vbs_setup_refuse(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RESULT:
+ return ran_a_decode_vgcs_vbs_assign_res(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_FAILURE:
+ return ran_a_decode_vgcs_vbs_assign_fail(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_VGCS_VBS_QUEUING_INDICATION:
+ return ran_a_decode_vgcs_vbs_queuing_ind(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_UPLINK_RQST:
+ return ran_a_decode_uplink_request(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_UPLINK_RQST_CONFIRMATION:
+ return ran_a_decode_uplink_request_cnf(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_UPLINK_APP_DATA:
+ return ran_a_decode_uplink_application_data(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_UPLINK_RELEASE_INDICATION:
+ return ran_a_decode_uplink_release_ind(ran_dec, bssmap, tp);
+ case BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_STATUS:
+ return ran_a_decode_vgcs_vbs_assign_status(ran_dec, bssmap, tp);
/* From current RAN peer, the Handover origin: */
case BSS_MAP_MSG_HANDOVER_REQUIRED:
@@ -1293,6 +1745,36 @@
case RAN_MSG_HANDOVER_FAILURE:
return ran_a_make_handover_failure(caller_fi, ran_enc_msg);
+ case RAN_MSG_VGCS_VBS_SETUP:
+ return gsm0808_create_vgcs_vbs_setup(&ran_enc_msg->vgcs_vbs_setup);
+
+ case RAN_MSG_VGCS_VBS_ASSIGN_REQ:
+ return gsm0808_create_vgcs_vbs_assign_req(&ran_enc_msg->vgcs_vbs_assign_req);
+
+ case RAN_MSG_UPLINK_REQUEST_ACK:
+ return gsm0808_create_uplink_request_ack(&ran_enc_msg->uplink_request_ack);
+
+ case RAN_MSG_UPLINK_REJECT_CMD:
+ return gsm0808_create_uplink_reject_cmd(&ran_enc_msg->uplink_reject_cmd);
+
+ case RAN_MSG_UPLINK_RELEASE_CMD:
+ return gsm0808_create_uplink_release_cmd(ran_enc_msg->uplink_release_cmd.cause);
+
+ case RAN_MSG_UPLINK_SEIZED_CMD:
+ return gsm0808_create_uplink_seized_cmd(&ran_enc_msg->uplink_seized_cmd);
+
+ case RAN_MSG_VGCS_ADDITIONAL_INFO:
+ return gsm0808_create_vgcs_additional_info(&ran_enc_msg->vgcs_additional_info.talker_identity);
+
+ case RAN_MSG_VGCS_VBS_AREA_CELL_INFO:
+ return gsm0808_create_vgcs_vbs_area_cell_info(&ran_enc_msg->vgcs_vbs_area_cell_info);
+
+ case RAN_MSG_VGCS_SMS:
+ return gsm0808_create_vgcs_sms(&ran_enc_msg->vgcs_sms.sms_to_vgcs);
+
+ case RAN_MSG_NOTIFICATION_DATA:
+ return gsm0808_create_notification_data(&ran_enc_msg->notification_data);
+
default:
LOG_RAN_A_ENC(caller_fi, LOGL_ERROR, "Unimplemented RAN-encode message type: %s\n",
ran_msg_type_name(ran_enc_msg->msg_type));
--
To view, visit https://gerrit.osmocom.org/c/osmo-msc/+/33505
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: osmo-msc
Gerrit-Branch: master
Gerrit-Change-Id: I6b1f088201e7ef4a58762937855a1d358973882c
Gerrit-Change-Number: 33505
Gerrit-PatchSet: 12
Gerrit-Owner: jolly <andreas(a)eversberg.eu>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: neels <nhofmeyr(a)sysmocom.de>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-MessageType: merged
1
0
[M] Change in osmo-msc[master]: ASCI: Add decoder for VGCS/VBS messages to msc_a.c
by laforge 17 Jul '23
by laforge 17 Jul '23
17 Jul '23
laforge has submitted this change. ( https://gerrit.osmocom.org/c/osmo-msc/+/33506 )
(
14 is the latest approved patch-set.
No files were changed between the latest approved patch-set and the submitted one.
)Change subject: ASCI: Add decoder for VGCS/VBS messages to msc_a.c
......................................................................
ASCI: Add decoder for VGCS/VBS messages to msc_a.c
VGCS/VBS messages from BSS are decoded and a receiver funktion for
the GCC/BCC (VGCS/VBS call control) is selected.
Change-Id: Ief6259ba3914eeaceb063b562a0bcbc48349ce60
Related: OS#4854
---
M src/libmsc/msc_a.c
1 file changed, 144 insertions(+), 0 deletions(-)
Approvals:
laforge: Looks good to me, but someone else must approve
neels: Looks good to me, approved
pespin: Looks good to me, but someone else must approve
Jenkins Builder: Verified
diff --git a/src/libmsc/msc_a.c b/src/libmsc/msc_a.c
index 5a5bb8a..7013176 100644
--- a/src/libmsc/msc_a.c
+++ b/src/libmsc/msc_a.c
@@ -48,6 +48,7 @@
#include <osmocom/msc/rtp_stream.h>
#include <osmocom/msc/msc_ho.h>
#include <osmocom/msc/codec_mapping.h>
+#include <osmocom/msc/msc_vgcs.h>
#define MSC_A_USE_WAIT_CLEAR_COMPLETE "wait-Clear-Complete"
@@ -1718,6 +1719,136 @@
return rc;
}
+static int msc_a_rx_vgcs_bss_decoded(struct osmo_fsm_inst *caller_fi, void *caller_data, const struct ran_msg *msg)
+{
+ struct vgcs_bss *bss = caller_data;
+ struct msc_a *msc_a = (bss->trans) ? bss->trans->msc_a : NULL;
+ int rc = 0;
+
+ switch (msg->msg_type) {
+ case RAN_MSG_VGCS_VBS_SETUP_ACK:
+ /* The BSS accepts VGCS/VBS and sends us supported features. */
+ vgcs_vbs_setup_ack(bss, msg);
+ break;
+ case RAN_MSG_VGCS_VBS_SETUP_REFUSE:
+ /* The BSS refuses VGCS/VBS. */
+ vgcs_vbs_setup_refuse(bss, msg);
+ break;
+ case RAN_MSG_UPLINK_REQUEST:
+ /* A mobile station requests the uplink on a VGCS channel. */
+ vgcs_uplink_request(bss, msg);
+ break;
+ case RAN_MSG_UPLINK_REQUEST_CNF:
+ /* The uplink on a VGCS channel has been established. */
+ vgcs_uplink_request_cnf(bss, msg);
+ break;
+ case RAN_MSG_UPLINK_APPLICATION_DATA:
+ /* Application data received on the uplink of a VGCS channel. */
+ vgcs_app_data(bss, msg);
+ break;
+ case RAN_MSG_DTAP:
+ /* BSS confirms the release of the channel. */
+ vgcs_bss_dtap(bss, msg);
+ break;
+ case RAN_MSG_UPLINK_RELEASE_IND:
+ /* A mobile station releases the uplink on a VGCS channel. */
+ vgcs_uplink_release_ind(bss, msg);
+ break;
+ case RAN_MSG_CLEAR_REQUEST:
+ /* BSS indicated that the channel has been released. */
+ vgcs_vbs_clear_req(bss, msg);
+ break;
+ case RAN_MSG_CLEAR_COMPLETE:
+ /* BSS confirms the release of the channel. */
+ vgcs_vbs_clear_cpl(bss, msg);
+ break;
+ default:
+ LOG_MSC_A(msc_a, LOGL_ERROR, "VGCS message from BSS not implemented: %s\n",
+ ran_msg_type_name(msg->msg_type));
+ rc = -ENOTSUP;
+ break;
+ }
+ return rc;
+}
+
+int msc_a_rx_vgcs_bss(struct vgcs_bss *bss, struct ran_conn *from_conn, struct msgb *msg)
+{
+ struct ran_dec ran_dec;
+
+ /* Feed through the decoding mechanism ran_msg. The decoded message arrives in msc_a_rx_vgcs_decoded() */
+ ran_dec = (struct ran_dec) {
+ .caller_data = bss,
+ .decode_cb = msc_a_rx_vgcs_bss_decoded,
+ };
+ struct ran_peer *ran_peer = from_conn->ran_peer;
+ struct ran_infra *ran = ran_peer->sri->ran;
+ if (!ran->ran_dec_l2) {
+ LOGP(DMSC, LOGL_ERROR, "No ran_dec_l2() defined for RAN type %s\n",
+ osmo_rat_type_name(ran->type));
+ return -ENOTSUP;
+ }
+ return ran->ran_dec_l2(&ran_dec, msg);
+}
+
+static int msc_a_rx_vgcs_cell_decoded(struct osmo_fsm_inst *caller_fi, void *caller_data, const struct ran_msg *msg)
+{
+ struct vgcs_bss_cell *cell = caller_data;
+ struct msc_a *msc_a = (cell->bss && cell->bss->trans) ? cell->bss->trans->msc_a : NULL;
+ int rc = 0;
+
+ switch (msg->msg_type) {
+ case RAN_MSG_VGCS_VBS_ASSIGN_RES:
+ /* The BSS accepts VGCS/VBS channel assignment. */
+ vgcs_vbs_assign_result(cell, msg);
+ break;
+ case RAN_MSG_VGCS_VBS_ASSIGN_FAIL:
+ /* The BSS refuses VGCS/VBS channel assignment. */
+ vgcs_vbs_assign_fail(cell, msg);
+ break;
+ case RAN_MSG_VGCS_VBS_QUEUING_IND:
+ /* The BSS needs more time for VGCS/VBS channel assignment. */
+ vgcs_vbs_queuing_ind(cell);
+ break;
+ case RAN_MSG_VGCS_VBS_ASSIGN_STATUS:
+ /* The BSS gives cell status about VGCS/VBS channel. */
+ vgcs_vbs_assign_status(cell, msg);
+ break;
+ case RAN_MSG_CLEAR_REQUEST:
+ /* BSS indicated that the channel has been released. */
+ vgcs_vbs_clear_req_channel(cell, msg);
+ break;
+ case RAN_MSG_CLEAR_COMPLETE:
+ /* BSS confirms the release of the channel. */
+ vgcs_vbs_clear_cpl_channel(cell, msg);
+ break;
+ default:
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Message from BSS leg not implemented: %s\n",
+ ran_msg_type_name(msg->msg_type));
+ rc = -ENOTSUP;
+ break;
+ }
+ return rc;
+}
+
+int msc_a_rx_vgcs_cell(struct vgcs_bss_cell *cell, struct ran_conn *from_conn, struct msgb *msg)
+{
+ struct ran_dec ran_dec;
+
+ /* Feed through the decoding mechanism ran_msg. The decoded message arrives in msc_a_rx_vgcs_decoded() */
+ ran_dec = (struct ran_dec) {
+ .caller_data = cell,
+ .decode_cb = msc_a_rx_vgcs_cell_decoded,
+ };
+ struct ran_peer *ran_peer = from_conn->ran_peer;
+ struct ran_infra *ran = ran_peer->sri->ran;
+ if (!ran->ran_dec_l2) {
+ LOGP(DMSC, LOGL_ERROR, "No ran_dec_l2() defined for RAN type %s\n",
+ osmo_rat_type_name(ran->type));
+ return -ENOTSUP;
+ }
+ return ran->ran_dec_l2(&ran_dec, msg);
+}
+
static int msc_a_ran_dec_from_msc_t(struct msc_a *msc_a, struct msc_a_ran_dec_data *d)
{
struct msc_t *msc_t = msc_a_msc_t(msc_a);
--
To view, visit https://gerrit.osmocom.org/c/osmo-msc/+/33506
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: osmo-msc
Gerrit-Branch: master
Gerrit-Change-Id: Ief6259ba3914eeaceb063b562a0bcbc48349ce60
Gerrit-Change-Number: 33506
Gerrit-PatchSet: 18
Gerrit-Owner: jolly <andreas(a)eversberg.eu>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: neels <nhofmeyr(a)sysmocom.de>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-MessageType: merged
1
0
17 Jul '23
laforge has submitted this change. ( https://gerrit.osmocom.org/c/osmo-msc/+/33511 )
Change subject: ASCI: Add call control for VGCS/VBS
......................................................................
ASCI: Add call control for VGCS/VBS
Change-Id: I9947403fde8212b66758104443c60aaacc8b1e7b
Related: OS#4854
---
M include/osmocom/msc/Makefile.am
A include/osmocom/msc/msc_vgcs.h
M include/osmocom/msc/ran_conn.h
M include/osmocom/msc/transaction.h
M src/libmsc/Makefile.am
M src/libmsc/gsm_04_11_gsup.c
A src/libmsc/msc_vgcs.c
M src/libmsc/transaction.c
8 files changed, 3,025 insertions(+), 1 deletion(-)
Approvals:
laforge: Looks good to me, approved
pespin: Looks good to me, but someone else must approve
Jenkins Builder: Verified
diff --git a/include/osmocom/msc/Makefile.am b/include/osmocom/msc/Makefile.am
index 3c54dee..9fa06eb 100644
--- a/include/osmocom/msc/Makefile.am
+++ b/include/osmocom/msc/Makefile.am
@@ -25,6 +25,7 @@
msc_a_remote.h \
msc_common.h \
msc_ho.h \
+ msc_vgcs.h \
msc_i.h \
msc_i_remote.h \
msc_roles.h \
diff --git a/include/osmocom/msc/msc_vgcs.h b/include/osmocom/msc/msc_vgcs.h
new file mode 100644
index 0000000..2b2f45d
--- /dev/null
+++ b/include/osmocom/msc/msc_vgcs.h
@@ -0,0 +1,228 @@
+/* Handle a call via VGCS/VBCS (Voice Group/Broadcast Call Service). */
+/*
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Andreas Eversberg
+ *
+ * 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/>.
+ */
+#pragma once
+
+#include <osmocom/msc/transaction.h>
+
+#define GSM44068_ALLOC_SIZE 2048
+#define GSM44068_ALLOC_HEADROOM 256
+
+static inline struct msgb *gsm44068_msgb_alloc_name(const char *name)
+{
+ return msgb_alloc_headroom(GSM44068_ALLOC_SIZE, GSM44068_ALLOC_HEADROOM, name);
+}
+
+/* VGCS/VBS "call control" connection to each BSS */
+struct vgcs_bss {
+ struct llist_head list; /* List entry */
+ struct llist_head cell_list; /* List of cells */
+ struct gsm_trans *trans; /* Back pointer to transaction */
+ struct osmo_fsm_inst *fi; /* State machine of each BSS */
+ struct ran_conn *conn; /* RAN ("SCCP") connection */
+ enum trans_type trans_type; /* Transaction type */
+ uint32_t callref; /* Callref */
+ int pc; /* Point code for debug purpose */
+};
+
+/* VGCS/VBS "resource control" connection to each cell in BSS */
+struct vgcs_bss_cell {
+ struct llist_head list_bss; /* List entry in vgcs_bss */
+ struct llist_head list_mgw; /* List entry in MGW endpoint */
+ struct vgcs_bss *bss; /* Back pointer to vgcs_bss */
+ struct vgcs_mgw_ep *mgw; /* Back pointer to vgcs_mgw_ep */
+ struct osmo_fsm_inst *fi; /* State machine of each cell */
+ int cell_id; /* Id of cell (BTS) to use */
+ struct ran_conn *conn; /* RAN ("SCCP") connection */
+ enum trans_type trans_type; /* Transaction type */
+ uint32_t callref; /* Callref */
+ int call_id; /* Id of call (used for MGW connections) */
+ int pc; /* Point code for debug purpose */
+ bool assigned; /* Flags if assignment is complete */
+ struct rtp_stream *rtps; /* MGW connection process */
+};
+
+/* VGCS/VBS MGW endpoint for each call */
+struct vgcs_mgw_ep {
+ struct llist_head cell_list; /* List of cells with connections */
+ struct llist_head list; /* List entry */
+ struct osmo_fsm_inst *fi; /* State machine of each cell */
+ struct osmo_mgcpc_ep *mgw_ep; /* MGW endpoint */
+};
+
+/* Events for the GCC/BCC state machine.
+ * There is no primitive definition like MNGCC-* oder MNBCC-* in the standard. */
+enum vgcs_gcc_fsm_event {
+ /* The network sets up a call. */
+ VGCS_GCC_EV_NET_SETUP,
+ /* The network requests termination. */
+ VGCS_GCC_EV_NET_TERM,
+ /* The user sets up a call. */
+ VGCS_GCC_EV_USER_SETUP,
+ /* The user requests termination. */
+ VGCS_GCC_EV_USER_TERM,
+ /* BSS completed call establishment (all BSCs) */
+ VGCS_GCC_EV_BSS_ESTABLISHED,
+ /* Assignment was completed. */
+ VGCS_GCC_EV_BSS_ASSIGN_CPL,
+ /* Assignment failed. */
+ VGCS_GCC_EV_BSS_ASSIGN_FAIL,
+ /* BSS released call establishment (all BSCs) */
+ VGCS_GCC_EV_BSS_RELEASED,
+ /* Inactivity timeout */
+ VGCS_GCC_EV_TIMEOUT,
+};
+
+/* 3GPP TS 44.068 6.1.2.2 States of GCC/BCC */
+enum vgcs_gcc_fsm_state {
+ /* No call. Initial state when instance is created. */
+ VGCS_GCC_ST_N0_NULL = 0,
+ /* An MS wants to establish a call. */
+ VGCS_GCC_ST_N1_CALL_INITIATED,
+ /* Call established in at least one cell. */
+ VGCS_GCC_ST_N2_CALL_ACTIVE,
+ /* Channel activation is requested, CONNECT already sent to MS. */
+ VGCS_GCC_ST_N3_CALL_EST_PROC,
+ /* Call termination is requested, waiting for all cells to confirm. */
+ VGCS_GCC_ST_N4_TERMINATION_REQ,
+};
+
+const char *vgcs_bcc_gcc_state_name(struct osmo_fsm_inst *fi);
+
+/* Events for the VGCS/VBS "call control" state machine */
+enum vgcs_bss_fsm_event {
+ /* Start a VGCS/VBS call using VGCS/VBS SETUP message */
+ VGCS_BSS_EV_SETUP,
+ /* VGCS/VBS SETUP ACK is received */
+ VGCS_BSS_EV_SETUP_ACK,
+ /* VGCS/VBS SETUP REFUSE is received */
+ VGCS_BSS_EV_SETUP_REFUSE,
+ /* VGCS/VBS ASSIGNMENT complete or failed */
+ VGCS_BSS_EV_ACTIVE_OR_FAIL,
+ /* Talker request */
+ VGCS_BSS_EV_UL_REQUEST,
+ /* Talker established uplink */
+ VGCS_BSS_EV_UL_REQUEST_CNF,
+ /* Talker send app data */
+ VGCS_BSS_EV_UL_APP_DATA,
+ /* Talker send signaling data */
+ VGCS_BSS_EV_BSS_DTAP,
+ /* Talker becomes listener */
+ VGCS_BSS_EV_UL_RELEASE,
+ /* Release channel towards BSS */
+ VGCS_BSS_EV_CLEAR,
+ /* Channel closed from BSS */
+ VGCS_BSS_EV_CLOSE,
+ /* Release is complete */
+ VGCS_BSS_EV_RELEASED,
+};
+
+/* States of the VGCS/VBS "call control" state machine */
+enum vgcs_bss_fsm_state {
+ /* No call. Initial state when instance is created. */
+ VGCS_BSS_ST_NULL = 0,
+ /* VGCS/VBS SETUP is sent towards BSC */
+ VGCS_BSS_ST_SETUP,
+ /* VGCS/VBS ASSIGNMENT REQUEST is sent towards BSC */
+ VGCS_BSS_ST_ASSIGNMENT,
+ /* VGCS/VBS is establised */
+ VGCS_BSS_ST_ACTIVE,
+ /* CLEAR COMMAND was sent */
+ VGCS_BSS_ST_RELEASE,
+};
+
+/* Events for the VGCS/VBS "resource control" state machine */
+enum vgcs_cell_fsm_event {
+ /* RTP stream gone */
+ VGCS_CELL_EV_RTP_STREAM_GONE,
+ /* RTP stream remote addr available */
+ VGCS_CELL_EV_RTP_STREAM_ADDR_AVAILABLE,
+ /* RTP stream established */
+ VGCS_CELL_EV_RTP_STREAM_ESTABLISHED,
+ /* Start a VGCS/VBS channel using VGCS/VBS ASSIGNMENT message */
+ VGCS_CELL_EV_ASSIGN,
+ /* VGCS/VBS ASSIGNMENT RESULT is received */
+ VGCS_CELL_EV_ASSIGN_RES,
+ /* VGCS/VBS ASSIGNMENT FAILURE is received */
+ VGCS_CELL_EV_ASSIGN_FAIL,
+ /* Release channel towards BSS */
+ VGCS_CELL_EV_CLEAR,
+ /* Channel closed from BSS */
+ VGCS_CELL_EV_CLOSE,
+ /* Release is complete */
+ VGCS_CELL_EV_RELEASED,
+};
+
+/* States of the VGCS/VBS "resource control" state machine */
+enum vgcs_cell_fsm_state {
+ /* No call. Initial state when instance is created. */
+ VGCS_CELL_ST_NULL = 0,
+ /* VGCS/VBS ASSIGNMENT REQUEST is sent towards BSC */
+ VGCS_CELL_ST_ASSIGNMENT,
+ /* Channel is establised */
+ VGCS_CELL_ST_ACTIVE,
+ /* CLEAR COMMAND was sent */
+ VGCS_CELL_ST_RELEASE,
+};
+
+/* Events for the VGCS/VBS MGW endpoint state machine */
+enum vgcs_mgw_ep_fsm_event {
+ /* MGW endpoint gone */
+ VGCS_MGW_EP_EV_FREE,
+ /* Destroy MGW endpoint */
+ VGCS_MGW_EP_EV_CLEAR,
+};
+
+/* States of the VGCS/VBS MGW endpoint state machine */
+enum vgcs_mgw_ep_fsm_state {
+ VGCS_MGW_EP_ST_NULL = 0,
+ /* MGW endpoint allocated */
+ VGCS_MGW_EP_ST_ACTIVE,
+};
+
+const char *gsm44068_group_id_string(uint32_t callref);
+
+struct gcr;
+
+int gsm44068_rcv_rr(struct msc_a *msc_a, struct msgb *msg);
+int gsm44068_rcv_bcc_gcc(struct msc_a *msc_a, struct gsm_trans *trans, struct msgb *msg);
+const char *vgcs_vty_initiate(struct gsm_network *gsmnet, struct gcr *gcr);
+const char *vgcs_vty_terminate(struct gsm_network *gsmnet, struct gcr *gcr);
+void gsm44068_bcc_gcc_trans_free(struct gsm_trans *trans);
+
+void vgcs_vbs_setup_ack(struct vgcs_bss *bss, const struct ran_msg *ran_msg);
+void vgcs_vbs_setup_refuse(struct vgcs_bss *bss, const struct ran_msg *ran_msg);
+void vgcs_vbs_assign_result(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg);
+void vgcs_vbs_assign_fail(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg);
+void vgcs_vbs_queuing_ind(struct vgcs_bss_cell *cell);
+void vgcs_uplink_request(struct vgcs_bss *bss, const struct ran_msg *ran_msg);
+void vgcs_uplink_request_cnf(struct vgcs_bss *bss, const struct ran_msg *ran_msg);
+void vgcs_app_data(struct vgcs_bss *bss, const struct ran_msg *ran_msg);
+void vgcs_bss_dtap(struct vgcs_bss *bss, const struct ran_msg *ran_msg);
+void vgcs_uplink_release_ind(struct vgcs_bss *bss, const struct ran_msg *ran_msg);
+void vgcs_vbs_assign_status(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg);
+void vgcs_vbs_clear_req_channel(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg);
+void vgcs_vbs_clear_cpl_channel(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg);
+void vgcs_vbs_clear_req(struct vgcs_bss *bss, const struct ran_msg *ran_msg);
+void vgcs_vbs_clear_cpl(struct vgcs_bss *bss, const struct ran_msg *ran_msg);
+void vgcs_vbs_caller_assign_cpl(struct gsm_trans *trans);
+void vgcs_vbs_caller_assign_fail(struct gsm_trans *trans);
diff --git a/include/osmocom/msc/ran_conn.h b/include/osmocom/msc/ran_conn.h
index 7aa50df..cdaa026 100644
--- a/include/osmocom/msc/ran_conn.h
+++ b/include/osmocom/msc/ran_conn.h
@@ -18,9 +18,17 @@
uint32_t sccp_conn_id;
/* MSC role that this RAN connection belongs to. This will be either an msc_i (currently active
- * connection) or an msc_t (transitory new connection during Handover). */
+ * connection) or an msc_t (transitory new connection during Handover).
+ * Used for usual L3 ran_conn to a subscriber. */
struct osmo_fsm_inst *msc_role;
+ /* For VGCS/VBS, we have additional N connections to BSS. When receiving messages for a group call peer,
+ * dispatch to the VGCS management. */
+ struct {
+ void *bss;
+ void *cell;
+ } vgcs;
+
bool closing;
};
diff --git a/include/osmocom/msc/transaction.h b/include/osmocom/msc/transaction.h
index 356bd6c..a5a2e84 100644
--- a/include/osmocom/msc/transaction.h
+++ b/include/osmocom/msc/transaction.h
@@ -99,6 +99,25 @@
union {
struct {
+ /* State machine of setup process towards BSS */
+ struct osmo_fsm_inst *fi;
+ /* BSS list with all VGCS/VBS calls */
+ struct llist_head bss_list;
+ /* Inactivity timeout and timer */
+ int inactivity_to;
+ struct osmo_timer_list timer_inactivity;
+ /* If talker's downlink shall be muted */
+ bool mute_talker;
+ /* Indicator, if Uplink is used in one cell */
+ bool uplink_busy;
+ /* BSS that uses the uplink */
+ struct vgcs_bss *uplink_bss;
+ /* Cell that uses the uplink */
+ struct vgcs_bss_cell *uplink_cell;
+ /* If uplink is used by the originator */
+ bool uplink_originator;
+ } gcc;
+ struct {
/* current call state */
int state;
diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am
index c4e134c..2e62402 100644
--- a/src/libmsc/Makefile.am
+++ b/src/libmsc/Makefile.am
@@ -55,6 +55,7 @@
msc_t.c \
msc_t_remote.c \
msc_ho.c \
+ msc_vgcs.c \
neighbor_ident.c \
neighbor_ident_vty.c \
paging.c \
diff --git a/src/libmsc/gsm_04_11_gsup.c b/src/libmsc/gsm_04_11_gsup.c
index 7a47e93..0f18912 100644
--- a/src/libmsc/gsm_04_11_gsup.c
+++ b/src/libmsc/gsm_04_11_gsup.c
@@ -33,6 +33,7 @@
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/msub.h>
#include <osmocom/msc/gsup_client_mux.h>
+#include <osmocom/msc/msc_a.h>
/* Common helper for preparing to be encoded GSUP message */
static void gsup_sm_msg_init(struct osmo_gsup_message *gsup_msg,
diff --git a/src/libmsc/msc_vgcs.c b/src/libmsc/msc_vgcs.c
new file mode 100644
index 0000000..1af9aae
--- /dev/null
+++ b/src/libmsc/msc_vgcs.c
@@ -0,0 +1,2741 @@
+/* Handle VGCS/VBCS calls. (Voice Group/Broadcast Call Service). */
+/*
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info(a)sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Andreas Eversberg
+ *
+ * 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/>.
+ */
+
+/* The process consists of four state machines:
+ *
+ * The call control state machine "GCC" handles the voice group/broadcast call.
+ * There is one instance for every call. It is mainly controlled by the calling
+ * subscriber. The state machine is described in 3GPP TS 44.068 / 44.069.
+ * One SCCP connection to the calling subscriber is associated with the state
+ * machine. Once the calling subscriber leaves or is assigned to the VGCS/VBS
+ * channel, the association to the MSC-A role is removed and the SCCP connection
+ * is closed. The state machine with the transaction still exists until the end
+ * of the call.
+ *
+ * The BSS control state machine "vgcs_bss_fsm" handles the call in each BSC.
+ * There are as many instances as there are BSCs where the call is placed to.
+ * The instances are linked to the call control in a 1:n relation.
+ * One SCCP connection for every BSC is associated with the state machine.
+ * It sets up the call in the BSC and handles the uplink control and signaling
+ * with the talking phone.
+ *
+ * The resource controling state machine "vgcs_cell_fsm" handles the channel for
+ * each BTS that has a VGCS for the call. The instances are linked to the BSS
+ * control in a 1:n relation.
+ * One SCCP connection for every cell is associated with each list entry.
+ * It assigns the VGCS/VBS channel and the conference bridge in the MGW.
+ *
+ * The MGW endpoint state machine "vgcs_mgw_ep_fsm" handles the endpoint
+ * connection for each call. It controls the clearing of the MGW connections
+ * in case of endpoint failure. All instances of the resource controlling state
+ * machine are linked to this state machine in a 1:n relation.
+ *
+ * Setup of a call:
+ *
+ * When the calling subscriber dials a group/broadcast call, the GCR is checked
+ * for an existing Group ID. If it exists, the call is setup towards the a given
+ * list of MSCs for this Group ID. Also the channels are assigned for a given
+ * list of cells for this Group ID.
+ * The call can also be initiated via VTY.
+ *
+ * Then the calling subscriber is assigned to the VGCS channel of the same cell
+ * where the call was initialized. Afterwards the call is connected. The calling
+ * subscriber may then stay on the uplink or release it.
+ *
+ * Uplink control:
+ *
+ * Any BSC may indicate a talking subscriber. If there is no talking subscriber
+ * yet, the uplink is granted, otherwise it is rejected. If the uplink is in
+ * use on one BSC, all other BSCs will be blocked. If the uplink becomes free,
+ * all other BSCs will be unblocked.
+ *
+ * Termination of the call:
+ *
+ * The calling subscriber accesses the uplink. The it sends a termination
+ * request. This request is acknowledged by a termination command towards
+ * the calling subscriber. The call is cleared.
+ * The call can also be terminated via VTY and/or a timeout.
+ *
+ */
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/gsm/protocol/gsm_44_068.h>
+#include <osmocom/sigtran/sccp_helpers.h>
+#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
+
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/msc/sccp_ran.h>
+#include <osmocom/msc/ran_infra.h>
+#include <osmocom/msc/ran_peer.h>
+#include <osmocom/msc/ran_msg_a.h>
+#include <osmocom/msc/msub.h>
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/rtp_stream.h>
+#include <osmocom/msc/codec_mapping.h>
+#include <osmocom/msc/msc_vgcs.h>
+#include <osmocom/msc/asci_gcr.h>
+
+#define S(x) (1 << (x))
+
+#define LOG_GCC(trans, level, fmt, args...) \
+ LOGP((trans) ? ((trans->type == TRANS_GCC) ? DGCC : DBCC) : DASCI, level, \
+ (trans) ? ((trans->type == TRANS_GCC) ? ("GCC callref %s: " fmt) : ("BCC callref %s: " fmt)) : "%s" fmt, \
+ (trans) ? gsm44068_group_id_string(trans->callref) : "", ##args)
+#define LOG_BSS(bss, level, fmt, args...) \
+ LOGP(DASCI, level, \
+ (bss->trans_type == TRANS_GCC) ? ("GCC callref %s, BSS #%s: " fmt) : ("BCC callref %s, BSS #%s: " fmt), \
+ gsm44068_group_id_string(bss->callref), osmo_ss7_pointcode_print(NULL, bss->pc), ##args)
+#define LOG_CELL(cell, level, fmt, args...) \
+ LOGP(DASCI, level, \
+ (cell->trans_type == TRANS_GCC) ? ("GCC callref %s, BSS #%s, CID %d: " fmt) \
+ : ("BCC callref %s, BSS #%s, CID %d: " fmt), \
+ gsm44068_group_id_string(cell->callref), osmo_ss7_pointcode_print(NULL, cell->pc), cell->cell_id, ##args)
+
+static struct osmo_fsm vgcs_bcc_fsm;
+static struct osmo_fsm vgcs_gcc_fsm;
+static struct osmo_fsm vgcs_bss_fsm;
+static struct osmo_fsm vgcs_cell_fsm;
+static struct osmo_fsm vgcs_mgw_ep_fsm;
+
+static __attribute__((constructor)) void vgcs_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&vgcs_bcc_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&vgcs_gcc_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&vgcs_bss_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&vgcs_cell_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&vgcs_mgw_ep_fsm) == 0);
+}
+
+const char *gsm44068_group_id_string(uint32_t callref)
+{
+ static char string[9];
+
+ snprintf(string, sizeof(string), "%08u", callref);
+ string[sizeof(string) - 1] = '\0';
+
+ return string;
+}
+
+/* Resolve ran peer from point-code */
+static struct ran_peer *ran_peer_for_pc(struct gsm_network *msc_network, int pc)
+{
+ struct sccp_ran_inst *sri;
+ struct osmo_sccp_addr addr = {};
+ struct ran_peer *rp;
+
+ sri = msc_network->a.sri;
+ if (!osmo_sccp_get_ss7(sri->sccp)) {
+ LOGP(DASCI, LOGL_ERROR, "No SS7???\n");
+ return NULL;
+ }
+ osmo_sccp_make_addr_pc_ssn(&addr, pc, sri->ran->ssn);
+ rp = ran_peer_find_by_addr(sri, &addr);
+
+ return rp;
+}
+
+/* Encode message and send towards BSC. */
+int ran_encode_and_send(struct osmo_fsm_inst *fi, struct ran_msg *ran_msg, struct ran_conn *conn, bool initial)
+{
+ struct msgb *l3_msg;
+ int rc;
+
+ l3_msg = ran_a_encode(fi, ran_msg);
+ if (!l3_msg) {
+ LOGP(DASCI, LOGL_ERROR, "ran_a_encode() failed.\n");
+ return -EINVAL;
+ }
+ rc = ran_conn_down_l2_co(conn, l3_msg, initial);
+ msgb_free(l3_msg);
+
+ return rc;
+}
+
+/* Transmit DTAP message to talker
+ * This is used for sending group/broadcast call control messages. */
+int tx_dtap_to_talker(struct vgcs_bss *bss, struct msgb *l3_msg)
+{
+ struct ran_msg ran_msg;
+ struct gsm48_hdr *gh = msgb_l3(l3_msg) ? : l3_msg->data;
+ uint8_t pdisc = gsm48_hdr_pdisc(gh);
+ int rc;
+
+
+ LOG_BSS(bss, LOGL_DEBUG, "Sending DTAP: %s %s\n",
+ gsm48_pdisc_name(pdisc), gsm48_pdisc_msgtype_name(pdisc, gsm48_hdr_msg_type(gh)));
+
+ ran_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_DTAP,
+ .dtap = l3_msg,
+ };
+
+ rc = ran_encode_and_send(bss->fi, &ran_msg, bss->conn, false);
+
+ return rc;
+}
+
+/*
+ * GCC/BCC Message transcoding
+ */
+
+static void _add_cause_ie(struct msgb *msg, uint8_t cause, uint8_t *diag, uint8_t diag_len)
+{
+ uint8_t *ie = msgb_put(msg, 2 + diag_len);
+
+ ie[0] = 1 + diag_len;
+ ie[1] = cause;
+ if (diag && diag_len) {
+ ie[1] |= 0x80;
+ memcpy(ie + 2, diag, diag_len);
+ }
+}
+
+static void _add_callref_ie(struct msgb *msg, uint32_t callref, bool with_prio, uint8_t prio)
+{
+ uint32_t ie;
+
+ ie = callref << 5;
+ if (with_prio)
+ ie |= 0x10 | (prio << 1);
+ msgb_put_u32(msg, ie);
+}
+
+static int _msg_too_short(void)
+{
+ LOGP(DASCI, LOGL_ERROR, "MSG too short.\n");
+ return -EINVAL;
+}
+
+static int _ie_invalid(void)
+{
+ LOGP(DASCI, LOGL_ERROR, "IE invalid.\n");
+ return -EINVAL;
+}
+
+static int _rx_callref(uint8_t *ie, unsigned int remaining_len, uint32_t *callref, bool *with_prio, uint8_t *prio)
+{
+ uint8_t ie_len;
+
+ ie_len = sizeof(uint32_t);
+ if (remaining_len < ie_len)
+ return _msg_too_short();
+ *callref = osmo_load32be(ie) >> 5;
+ if (ie[3] & 0x10) {
+ *with_prio = true;
+ *prio = (ie[3] >> 1) & 0x7;
+ } else
+ *with_prio = false;
+
+ return ie_len;
+}
+
+/* 3GPP TS 44.068 Clause 8.1 */
+static int gsm44068_tx_connect(struct gsm_trans *trans, uint8_t pdisc, uint32_t callref, bool with_prio, uint8_t prio,
+ uint8_t oi, uint8_t talker_prio, bool with_sms, uint8_t sms_dc, uint8_t sms_gp)
+{
+ struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX CONNECT");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ uint8_t ie;
+
+ gh->proto_discr = pdisc;
+ gh->msg_type = OSMO_GSM44068_MSGT_CONNECT;
+ _add_callref_ie(msg, callref, with_prio, prio);
+ ie = (talker_prio << 4) | oi;
+ msgb_put_u8(msg, ie);
+ if (with_sms) {
+ ie = OSMO_GSM44068_IEI_SMS_INDICATIONS | (sms_dc << 1) | sms_gp;
+ msgb_put_u8(msg, ie);
+ }
+
+ /* Send to calling subscriber, depending on the link he is. */
+ if (trans->msc_a)
+ return msc_a_tx_dtap_to_i(trans->msc_a, msg);
+ if (trans->gcc.uplink_bss)
+ return tx_dtap_to_talker(trans->gcc.uplink_bss, msg);
+ msgb_free(msg);
+ return -EIO;
+}
+
+/* The Get Status procedure is not used by the current implementation.
+ * It is commented out, so it can be used in the future.
+ * The idea is to have a complete set of GCC/BCC message transcoding.
+ */
+#if 0
+/* 3GPP TS 44.068 Clause 8.2 */
+static int gsm44068_tx_get_status(struct gsm_trans *trans, uint8_t pdisc, struct osmo_mobile_identity *mi)
+{
+ struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX GET STATUS");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->proto_discr = pdisc;
+ gh->msg_type = OSMO_GSM44068_MSGT_GET_STATUS;
+ if (mi) {
+ uint8_t *l;
+ int rc;
+
+ l = msgb_tl_put(msg, OSMO_GSM44068_IEI_MOBILE_IDENTITY);
+ rc = osmo_mobile_identity_encode_msgb(msg, mi, false);
+ if (rc < 0) {
+ msgb_free(msg);
+ return -EINVAL;
+ }
+ *l = rc;
+ }
+
+ /* Send to calling subscriber, depending on the link he is. */
+ if (trans->msc_a)
+ return msc_a_tx_dtap_to_i(trans->msc_a, msg);
+ if (trans->gcc.uplink_bss)
+ return tx_dtap_to_talker(trans->gcc.uplink_bss, msg);
+ msgb_free(msg);
+ return -EIO;
+}
+#endif
+
+/* 3GPP TS 44.068 Clause 8.3 and 8.3a */
+static int gsm44068_rx_immediate_setup(struct msgb *msg, uint8_t *talker_prio, uint8_t *key_seq,
+ struct gsm48_classmark2 *cm2, struct osmo_mobile_identity *mi,
+ uint32_t *callref, bool *with_prio, uint8_t *prio, char *user_user)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int remaining_len = msgb_l3len(msg) - sizeof(*gh);
+ uint8_t *ie = gh->data;
+ uint8_t ie_len;
+ uint64_t otdi;
+ int i;
+ int rc;
+
+ /* Talker priority / Cyphering key sequence */
+ if (remaining_len < 1)
+ return _msg_too_short();
+ *talker_prio = ie[0] & 0x07;
+ *key_seq = (ie[0] >> 4) & 0x07;
+ remaining_len -= 1;
+ ie += 1;
+
+ /* Mobile station classmark 2 */
+ if (remaining_len < 4)
+ return _msg_too_short();
+ ie_len = ie[0];
+ if (remaining_len < ie_len + 1)
+ return _msg_too_short();
+ if (ie_len != 3)
+ return _ie_invalid();
+ memcpy(cm2, ie + 1, ie_len);
+ remaining_len -= ie_len + 1;
+ ie += ie_len + 1;
+
+ /* Mobile indentity */
+ if (gh->msg_type == OSMO_GSM44068_MSGT_IMMEDIATE_SETUP) {
+ /* IMMEDIATE SETUP uses IMSI/TMSI */
+ if (remaining_len < 2)
+ return _msg_too_short();
+ ie_len = ie[0];
+ if (remaining_len < ie_len + 1)
+ return _msg_too_short();
+ rc = osmo_mobile_identity_decode(mi, ie + 1, ie_len, false);
+ if (rc) {
+ LOGP(DMM, LOGL_ERROR, "Failure to decode Mobile Identity in GCC/BCC IMMEDDIATE SETUP"
+ " (rc=%d)\n", rc);
+ return -EINVAL;
+ }
+ remaining_len -= ie_len + 1;
+ ie += ie_len + 1;
+ } else {
+ /* IMMEDIATE SETUP 2 uses TMSI only */
+ if (remaining_len < 4)
+ return _msg_too_short();
+ mi->type = GSM_MI_TYPE_TMSI;
+ mi->tmsi = osmo_load32be(ie);
+ remaining_len -= 4;
+ ie += 4;
+ }
+
+ /* Call reference */
+ rc = _rx_callref(ie, remaining_len, callref, with_prio, prio);
+ if (rc < 0)
+ return rc;
+ remaining_len -= rc;
+ ie += rc;
+
+ /* OTID */
+ if (gh->msg_type == OSMO_GSM44068_MSGT_IMMEDIATE_SETUP_2 && user_user) {
+ ie_len = 5;
+ if (remaining_len < ie_len)
+ return _msg_too_short();
+ otdi = osmo_load32be(ie + 1) | ((uint64_t)ie[0] << 32);
+
+ for (i = 0; i < 12; i++) {
+ user_user[i] = (otdi % 10) + '0';
+ otdi /= 10;
+ }
+ user_user[i] = '\0';
+ remaining_len -= ie_len;
+ ie += ie_len;
+ } else if (user_user)
+ user_user[0] = '\0';
+
+ return 0;
+}
+
+/* 3GPP TS 44.068 Clause 8.4 */
+static int gsm44068_tx_set_parameter(struct gsm_trans *trans, uint8_t pdisc, uint8_t da, uint8_t ua, uint8_t comm,
+ uint8_t oi)
+{
+ struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX SET PARAMETER");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ uint8_t ie;
+
+ gh->proto_discr = pdisc;
+ gh->msg_type = OSMO_GSM44068_MSGT_SET_PARAMETER;
+ ie = (da << 3) | (ua << 2) | (comm << 1) | oi;
+ msgb_put_u8(msg, ie);
+
+ /* Send to calling subscriber, depending on the link he is. */
+ if (trans->msc_a)
+ return msc_a_tx_dtap_to_i(trans->msc_a, msg);
+ if (trans->gcc.uplink_bss)
+ return tx_dtap_to_talker(trans->gcc.uplink_bss, msg);
+ msgb_free(msg);
+ return -EIO;
+}
+
+/* 3GPP TS 44.068 Clause 8.5 */
+static int gsm44068_rx_setup(struct msgb *msg, bool *with_talker_prio, uint8_t *talker_prio,
+ uint32_t *callref, bool *with_prio, uint8_t *prio, char *user_user)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int remaining_len = msgb_l3len(msg) - sizeof(*gh);
+ uint8_t *ie = gh->data;
+ struct tlv_parsed tp;
+ struct tlv_p_entry *tlv;
+ int rc;
+
+ /* Call reference */
+ rc = _rx_callref(ie, remaining_len, callref, with_prio, prio);
+ if (rc < 0)
+ return rc;
+ remaining_len -= rc;
+ ie += rc;
+
+ rc = tlv_parse(&tp, &osmo_gsm44068_att_tlvdef, ie, remaining_len, 0, 0);
+ if (rc < 0)
+ return _ie_invalid();
+
+ /* User-user */
+ tlv = TLVP_GET(&tp, OSMO_GSM44068_IEI_USER_USER);
+ if (tlv && tlv->len && tlv->len <= 1 + 12 && user_user) {
+ memcpy(user_user, tlv->val, tlv->len - 1);
+ user_user[tlv->len - 1] = '\0';
+ }
+
+ /* Talker priority */
+ tlv = TLVP_GET(&tp, OSMO_GSM44068_IEI_TALKER_PRIORITY);
+ if (tlv && tlv->len) {
+ *with_talker_prio = true;
+ *talker_prio = tlv->val[0] & 0x07;
+ } else
+ *with_talker_prio = false;
+
+ return 0;
+}
+
+/* 3GPP TS 44.068 Clause 8.6 */
+static int gsm44068_rx_status(struct msgb *msg, uint8_t *cause, uint8_t *diag, uint8_t *diag_len,
+ bool *with_call_state, enum osmo_gsm44068_call_state *call_state,
+ bool *with_state_attrs, uint8_t *da, uint8_t *ua, uint8_t *comm, uint8_t *oi)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int remaining_len = msgb_l3len(msg) - sizeof(*gh);
+ uint8_t *ie = gh->data;
+ uint8_t ie_len;
+ struct tlv_parsed tp;
+ struct tlv_p_entry *tlv;
+ int rc;
+
+ /* Cause */
+ if (remaining_len < 2 || ie[0] < remaining_len - 2)
+ return _msg_too_short();
+ ie_len = ie[0];
+ if (remaining_len < ie_len + 1)
+ return _msg_too_short();
+ if (ie_len < 1)
+ return _ie_invalid();
+ *cause = ie[1] & 0x7f;
+ *diag_len = ie_len - 1;
+ if (*diag_len)
+ memcpy(diag, ie + 2, ie_len - 1);
+ remaining_len -= ie_len + 1;
+ ie += ie_len + 1;
+
+ rc = tlv_parse(&tp, &osmo_gsm44068_att_tlvdef, ie, remaining_len, 0, 0);
+ if (rc < 0)
+ return _ie_invalid();
+
+ /* Call state */
+ tlv = TLVP_GET(&tp, OSMO_GSM44068_IEI_CALL_STATE);
+ if (tlv) {
+ *with_call_state = true;
+ *call_state = tlv->val[0] & 0x7;
+ } else
+ *with_call_state = false;
+
+ /* State attributes */
+ tlv = TLVP_GET(&tp, OSMO_GSM44068_IEI_STATE_ATTRIBUTES);
+ if (tlv) {
+ *with_state_attrs = true;
+ *da = (tlv->val[0] >> 3) & 0x1;
+ *ua = (tlv->val[0] >> 2) & 0x1;
+ *comm = (tlv->val[0] >> 1) & 0x1;
+ *oi = tlv->val[0] & 0x1;
+ } else
+ *with_state_attrs = false;
+
+ return 0;
+}
+
+/* 3GPP TS 44.068 Clause 8.7 and 8.8 */
+static int gsm44068_tx_termination(struct msc_a *msc_a, struct vgcs_bss *bss, uint8_t pdisc, uint8_t msg_type,
+ uint8_t cause, uint8_t *diag, uint8_t diag_len)
+{
+ struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX TERMINATION");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ gh->proto_discr = pdisc;
+ gh->msg_type = msg_type;
+ _add_cause_ie(msg, cause, diag, diag_len);
+
+ /* Send to calling subscriber, depending on the link he is. */
+ if (msc_a)
+ return msc_a_tx_dtap_to_i(msc_a, msg);
+ if (bss)
+ return tx_dtap_to_talker(bss, msg);
+ msgb_free(msg);
+ return -EIO;
+}
+
+/* 3GPP TS 44.068 Clause 8.9 */
+static int gsm44068_rx_termination_req(struct msgb *msg, uint32_t *callref, bool *with_prio, uint8_t *prio,
+ bool *with_talker_prio, uint8_t *talker_prio)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int remaining_len = msgb_l3len(msg) - sizeof(*gh);
+ uint8_t *ie = gh->data;
+ struct tlv_parsed tp;
+ struct tlv_p_entry *tlv;
+ int rc;
+
+ /* Call reference */
+ rc = _rx_callref(ie, remaining_len, callref, with_prio, prio);
+ if (rc < 0)
+ return rc;
+ remaining_len -= rc;
+ ie += rc;
+
+ rc = tlv_parse(&tp, &osmo_gsm44068_att_tlvdef, ie, remaining_len, 0, 0);
+ if (rc < 0)
+ return _ie_invalid();
+
+ /* Talker priority */
+ tlv = TLVP_GET(&tp, OSMO_GSM44068_IEI_TALKER_PRIORITY);
+ if (tlv && tlv->len) {
+ *with_talker_prio = true;
+ *talker_prio = tlv->val[0] & 0x07;
+ } else
+ *with_talker_prio = false;
+
+ return 0;
+}
+
+/*
+ * GCC/BCC state machine - handles calling subscriber process
+ */
+
+static const struct value_string vgcs_gcc_fsm_event_names[] = {
+ OSMO_VALUE_STRING(VGCS_GCC_EV_NET_SETUP),
+ OSMO_VALUE_STRING(VGCS_GCC_EV_NET_TERM),
+ OSMO_VALUE_STRING(VGCS_GCC_EV_USER_SETUP),
+ OSMO_VALUE_STRING(VGCS_GCC_EV_USER_TERM),
+ OSMO_VALUE_STRING(VGCS_GCC_EV_BSS_ESTABLISHED),
+ OSMO_VALUE_STRING(VGCS_GCC_EV_BSS_ASSIGN_CPL),
+ OSMO_VALUE_STRING(VGCS_GCC_EV_BSS_ASSIGN_FAIL),
+ OSMO_VALUE_STRING(VGCS_GCC_EV_BSS_RELEASED),
+ OSMO_VALUE_STRING(VGCS_GCC_EV_TIMEOUT),
+ { }
+};
+
+static int gcc_establish_bss(struct gsm_trans *trans)
+{
+ struct gsm_network *net = trans->net;
+ struct vgcs_mgw_ep *mgw = NULL;
+ struct mgcp_client *mgcp_client;
+ struct gcr *gcr;
+ struct gcr_bss *b;
+ struct gcr_cell *c;
+ struct vgcs_bss *bss;
+ struct vgcs_bss_cell *cell;
+ struct osmo_fsm_inst *fi;
+ struct ran_peer *rp;
+
+ /* Failure should not happen, because it has been checked before. */
+ gcr = gcr_by_callref(trans->net, trans->type, trans->callref);
+ if (!gcr)
+ return -EINVAL;
+
+ /* Allocate MGW endpoint. */
+ mgcp_client = mgcp_client_pool_get(trans->net->mgw.mgw_pool);
+ if (!mgcp_client) {
+ LOG_GCC(trans, LOGL_ERROR, "No MGW client, please check config.\n");
+ goto err_mgw;
+ }
+ fi = osmo_fsm_inst_alloc(&vgcs_mgw_ep_fsm, net, NULL, LOGL_DEBUG, NULL);
+ if (!fi) {
+ LOG_GCC(trans, LOGL_ERROR, "No memory for VGCS MSG state machine.\n");
+ goto err_mgw;
+ }
+ osmo_fsm_inst_update_id(fi, "vgcs-mgw-ep");
+ osmo_fsm_inst_state_chg(fi, VGCS_MGW_EP_ST_ACTIVE, 0, 0);
+ mgw = talloc_zero(fi, struct vgcs_mgw_ep);
+ if (!mgw) {
+ LOG_GCC(trans, LOGL_ERROR, "No memory for MGW ep structure.\n");
+ osmo_fsm_inst_free(fi);
+ goto err_mgw;
+ }
+ mgw->fi = fi;
+ fi->priv = mgw;
+ INIT_LLIST_HEAD(&mgw->cell_list);
+ mgw->mgw_ep = osmo_mgcpc_ep_alloc(mgw->fi, VGCS_MGW_EP_EV_FREE,
+ mgcp_client, trans->net->mgw.tdefs, mgw->fi->id,
+ "%s", mgcp_client_rtpbridge_wildcard(mgcp_client));
+ if (!mgw->mgw_ep) {
+ LOG_GCC(trans, LOGL_ERROR, "No memory for MGW endpoint state machine.\n");
+ goto err_mgw;
+ }
+
+ /* Create BSS list structures. */
+ llist_for_each_entry(b, &gcr->bss_list, list) {
+ LOG_GCC(trans, LOGL_DEBUG, " -> BSS with PC %s.\n", osmo_ss7_pointcode_print(NULL, b->pc));
+ /* Resolve ran_peer. */
+ rp = ran_peer_for_pc(trans->net, b->pc);
+ if (!rp) {
+ LOG_GCC(trans, LOGL_ERROR, "Failed to resolve point code %s, skipping BSS!\n",
+ osmo_ss7_pointcode_print(NULL, b->pc));
+ continue;
+ }
+ /* Create state machine. */
+ fi = osmo_fsm_inst_alloc(&vgcs_bss_fsm, net, NULL, LOGL_DEBUG, NULL);
+ if (!fi) {
+ LOG_GCC(trans, LOGL_ERROR, "No memory for state machine.\n");
+ break;
+ }
+ /* Create call structure. */
+ bss = talloc_zero(fi, struct vgcs_bss);
+ if (!bss) {
+ LOG_GCC(trans, LOGL_ERROR, "No memory for BSS call structure.\n");
+ osmo_fsm_inst_free(fi);
+ break;
+ }
+ bss->fi = fi;
+ fi->priv = bss;
+ INIT_LLIST_HEAD(&bss->cell_list);
+ bss->trans = trans;
+ bss->trans_type = trans->type;
+ bss->callref = trans->callref;
+ bss->pc = b->pc;
+ /* Create ran connection. */
+ bss->conn = ran_conn_create_outgoing(rp);
+ if (!bss->conn) {
+ osmo_fsm_inst_free(bss->fi);
+ continue;
+ }
+ bss->conn->vgcs.bss = bss;
+ /* Create cell list structures. */
+ llist_for_each_entry(c, &b->cell_list, list) {
+ /* Create state machine. */
+ fi = osmo_fsm_inst_alloc(&vgcs_cell_fsm, net, NULL, LOGL_DEBUG, NULL);
+ if (!fi) {
+ LOG_GCC(trans, LOGL_ERROR, "No memory for state machine.\n");
+ break;
+ }
+ /* Create cell structure. */
+ cell = talloc_zero(fi, struct vgcs_bss_cell);
+ if (!cell) {
+ LOG_GCC(trans, LOGL_ERROR, "No memory for BSS cell structure.\n");
+ osmo_fsm_inst_free(fi);
+ break;
+ }
+ cell->fi = fi;
+ fi->priv = cell;
+ osmo_fsm_inst_update_id_f(cell->fi, "vgcs-cell-%d", c->cell_id);
+ cell->trans_type = trans->type;
+ cell->callref = trans->callref;
+ cell->pc = b->pc;
+ cell->cell_id = c->cell_id;
+ cell->call_id = trans->call_id;
+ /* Create ran connection. */
+ cell->conn = ran_conn_create_outgoing(rp);
+ if (!cell->conn) {
+ osmo_fsm_inst_free(cell->fi);
+ continue;
+ }
+ cell->conn->vgcs.cell = cell;
+ /* Attach to cell list of BSS and MGW endpoint */
+ llist_add_tail(&cell->list_bss, &bss->cell_list);
+ cell->bss = bss;
+ llist_add_tail(&cell->list_mgw, &mgw->cell_list);
+ cell->mgw = mgw;
+ }
+ /* No cell? */
+ if (llist_empty(&bss->cell_list)) {
+ osmo_fsm_inst_free(bss->fi);
+ break;
+ }
+ /* Attach to transaction list */
+ llist_add_tail(&bss->list, &trans->gcc.bss_list);
+ /* Trigger VGCS/VBS SETUP */
+ osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_SETUP, NULL);
+ }
+ /* No BSS? */
+ if (llist_empty(&trans->gcc.bss_list)) {
+ /* Also destroy MGW, because this list is empty too! */
+ goto err_mgw;
+ }
+ return 0;
+
+err_mgw:
+ if (mgw) {
+ if (mgw->mgw_ep) {
+ /* This will also free FSM instance and vgcs_mgw_ep structure. */
+ osmo_fsm_inst_dispatch(mgw->fi, VGCS_MGW_EP_EV_CLEAR, NULL);
+ return -EINVAL;
+ }
+ osmo_fsm_inst_free(mgw->fi);
+ }
+ return -EINVAL;
+}
+
+/* Send Assignment Request to the calling subscriber.
+ * This is used to assign the subscriber from early assigned channel to the VGCS/VBS channel. */
+static int gcc_assign(struct gsm_trans *trans)
+{
+ struct ran_msg tx_ran_msg;
+ struct gsm0808_channel_type channel_type;
+ struct vgcs_bss *bss = NULL, *b;
+
+ /* No assignment, because the calling subscriber is already assigned or there is no calling subscriber. */
+ if (!trans->msc_a)
+ return 0;
+
+ /* Check calling subscriber's MSC */
+ struct ran_conn *conn = msub_ran_conn(trans->msc_a->c.msub);
+ if (!conn) {
+ LOG_GCC(trans, LOGL_ERROR, "Calling subscriber has no ran_conn????\n");
+ return -EINVAL;
+ }
+ llist_for_each_entry(b, &trans->gcc.bss_list, list) {
+ if (osmo_sccp_addr_ri_cmp(&conn->ran_peer->peer_addr, &b->conn->ran_peer->peer_addr))
+ continue;
+ bss = b;
+ break;
+ }
+ if (!bss) {
+ LOG_GCC(trans, LOGL_ERROR, "Calling subscriber comes from BSC that has no VGCS call.\n");
+ return -EINVAL;
+ }
+
+ /* For now we support GSM/FR V1 only. This shall be supported by all MS. */
+ channel_type = (struct gsm0808_channel_type) {
+ .ch_indctr = GSM0808_CHAN_SPEECH,
+ .ch_rate_type = GSM0808_SPEECH_FULL_BM,
+ .perm_spch_len = 1,
+ .perm_spch[0] = GSM0808_PERM_FR1,
+ };
+
+ /* Send assignment to VGCS channel */
+ tx_ran_msg = (struct ran_msg) {
+ .msg_type = RAN_MSG_ASSIGNMENT_COMMAND,
+ .assignment_command = {
+ .channel_type = &channel_type,
+ .callref_present = true,
+ .callref = {
+ .sf = (trans->type == TRANS_GCC),
+ },
+ },
+ };
+ osmo_store32be_ext(trans->callref >> 3, &tx_ran_msg.assignment_command.callref.call_ref_hi, 3);
+ tx_ran_msg.assignment_command.callref.call_ref_lo = trans->callref & 0x7;
+ if (msc_a_ran_down(trans->msc_a, MSC_ROLE_I, &tx_ran_msg)) {
+ LOG_GCC(trans, LOGL_ERROR, "Cannot send Assignment\n");
+ return -EIO;
+ }
+
+ /* Assign Talker to BSS of the calling subscriber. */
+ trans->gcc.uplink_bss = bss;
+
+ return 0;
+}
+
+/* Send CONNECT to the calling subscriber. */
+static void gcc_connect(struct gsm_trans *trans)
+{
+ uint8_t pdisc = (trans->type == TRANS_GCC) ? GSM48_PDISC_GROUP_CC : GSM48_PDISC_BCAST_CC;
+ int rc;
+
+ /* Send CONNECT towards MS. */
+ rc = gsm44068_tx_connect(trans,
+ pdisc | (trans->transaction_id << 4),
+ trans->callref, 0, 0, 1, 0, 0, 0, 0);
+ if (rc < 0)
+ LOG_GCC(trans, LOGL_ERROR, "Failed to send CONNECT towards MS. Continue anyway.\n");
+}
+
+/* Release dedicated (SDCCH) channel of calling subscriber after assigning to VGCS */
+static void release_msc_a(struct gsm_trans *trans)
+{
+ struct msc_a *msc_a = trans->msc_a;
+
+ if (!msc_a)
+ return;
+
+ trans->msc_a = NULL;
+ switch (trans->type) {
+ case TRANS_GCC:
+ msc_a_put(msc_a, MSC_A_USE_GCC);
+ break;
+ case TRANS_BCC:
+ msc_a_put(msc_a, MSC_A_USE_BCC);
+ break;
+ default:
+ break;
+ }
+}
+
+/* Send TERMINATE to the calling/talking subscriber, then destroy transaction. */
+static void gcc_terminate_and_destroy(struct gsm_trans *trans, enum osmo_gsm44068_cause cause)
+{
+ uint8_t pdisc = (trans->type == TRANS_GCC) ? GSM48_PDISC_GROUP_CC : GSM48_PDISC_BCAST_CC;
+ int rc;
+
+ /* Send TERMINATION towards MS. */
+ rc = gsm44068_tx_termination(trans->msc_a, trans->gcc.uplink_bss,
+ pdisc | (trans->transaction_id << 4),
+ OSMO_GSM44068_MSGT_TERMINATION,
+ cause, NULL, 0);
+ if (rc < 0)
+ LOG_GCC(trans, LOGL_ERROR, "Failed to send TERMINATION towards MS. Continue anyway.\n");
+
+ /* Destroy transaction, note that also _gsm44068_gcc_trans_free() will be called by trans_free().
+ * There the complete state machine is destroyed. */
+ trans->callref = 0;
+ trans_free(trans);
+}
+
+/* Start inactivity timer.
+ * This timer is used to terminate the call, if the radio connection to the caller gets lost. */
+static void start_inactivity_timer(struct gsm_trans *trans)
+{
+ if (trans->gcc.inactivity_to) {
+ LOG_GCC(trans, LOGL_DEBUG, "Set inactivity timer to %d seconds.\n", trans->gcc.inactivity_to);
+ osmo_timer_schedule(&trans->gcc.timer_inactivity, trans->gcc.inactivity_to, 0);
+ }
+}
+
+static void stop_inactivity_timer(struct gsm_trans *trans)
+{
+ if (osmo_timer_pending(&trans->gcc.timer_inactivity)) {
+ LOG_GCC(trans, LOGL_DEBUG, "Stop pending inactivity timer.\n");
+ osmo_timer_del(&trans->gcc.timer_inactivity);
+ }
+}
+
+static void inactivity_timer_cb(void *data)
+{
+ struct gsm_trans *trans = data;
+
+ osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TIMEOUT, NULL);
+}
+
+/* Set the parameters of the talker. (downlink mute/unmute, uplink unmute, COMM=T, originator) */
+static int set_parameter(struct gsm_trans *trans)
+{
+ uint8_t pdisc = (trans->type == TRANS_GCC) ? GSM48_PDISC_GROUP_CC : GSM48_PDISC_BCAST_CC;
+ int rc;
+
+ rc = gsm44068_tx_set_parameter(trans, pdisc, !trans->gcc.mute_talker, 1, 1, trans->gcc.uplink_originator);
+ if (rc < 0)
+ LOG_GCC(trans, LOGL_ERROR, "Failed to send SET PARAMETER towards MS.\n");
+ return rc;
+}
+
+/* Check in which cell the uplink is used and set "uplink_cell". */
+static int set_uplink_cell(struct vgcs_bss *bss, struct gsm0808_cell_id *cell_id_ie, uint16_t cell_id)
+{
+ struct vgcs_bss_cell *cell;
+
+ if (cell_id_ie) {
+ /* Get cell ID to determine talker channel. */
+ switch (cell_id_ie->id_discr) {
+ case CELL_IDENT_CI:
+ cell_id = cell_id_ie->id.ci;
+ break;
+ case CELL_IDENT_LAC_AND_CI:
+ cell_id = cell_id_ie->id.lac_and_ci.ci;
+ break;
+ default:
+ LOG_BSS(bss, LOGL_DEBUG, "Cannot idenitfy cell, please fix!\n");
+ return -EINVAL;
+ }
+ }
+
+ /* Search for cell ID. */
+ bss->trans->gcc.uplink_cell = NULL;
+ llist_for_each_entry(cell, &bss->cell_list, list_bss) {
+ if (cell->cell_id == cell_id) {
+ LOG_BSS(bss, LOGL_DEBUG, "Talker is talking on cell %d.\n", cell->cell_id);
+ bss->trans->gcc.uplink_cell = cell;
+ return 0;
+ }
+ }
+
+ LOG_BSS(bss, LOGL_DEBUG, "Cell ID %d is not in list of current BSS, please fix!\n", cell_id);
+ return -EINVAL;
+}
+
+/* Set the MGW conference mode.
+ * All cells are listening to the conference. If there is a talker, this cell is also transmitting to the conference. */
+static int set_mgw_conference(struct gsm_trans *trans)
+{
+ struct vgcs_bss *bss;
+ struct vgcs_bss_cell *cell;
+ struct rtp_stream *rtps;
+ int rc;
+
+ /* All cells without talker are listening */
+ llist_for_each_entry(bss, &trans->gcc.bss_list, list) {
+ llist_for_each_entry(cell, &bss->cell_list, list_bss) {
+ if (!(rtps = cell->rtps))
+ continue;
+ if (rtps->crcx_conn_mode != MGCP_CONN_SEND_ONLY) {
+ LOG_CELL(cell, LOGL_DEBUG, "Setting cell %d into listening mode.\n", cell->cell_id);
+ rtp_stream_set_mode(rtps, MGCP_CONN_SEND_ONLY);
+ rc = rtp_stream_commit(rtps);
+ if (rc < 0)
+ LOG_CELL(cell, LOGL_ERROR, "Failed to commit parameters to RTP stream "
+ "for cell %d.\n", cell->cell_id);
+ }
+ }
+ }
+
+ if (trans->gcc.uplink_cell && trans->gcc.uplink_cell->rtps) {
+ cell = trans->gcc.uplink_cell;
+ rtps = cell->rtps;
+ LOG_CELL(cell, LOGL_DEBUG, "Setting cell %d into listening mode.\n", cell->cell_id);
+ rtp_stream_set_mode(rtps, MGCP_CONN_CONFECHO);
+ rc = rtp_stream_commit(rtps);
+ if (rc < 0)
+ LOG_CELL(cell, LOGL_ERROR, "Failed to commit parameters to RTP stream "
+ "for cell %d.\n", cell->cell_id);
+ }
+
+ return 0;
+}
+
+static void _assign_complete(struct gsm_trans *trans, bool send_connect)
+{
+ uint16_t cell_id;
+
+ /* Change state. */
+ osmo_fsm_inst_state_chg(trans->gcc.fi, VGCS_GCC_ST_N2_CALL_ACTIVE, 0, 0);
+ /* Get cell ID. */
+ if (trans->msc_a)
+ cell_id = trans->msc_a->via_cell.cell_identity;
+ else
+ LOG_GCC(trans, LOGL_ERROR, "Missing msc_a, please fix!\n");
+ /* Releasing dedicated channel. */
+ release_msc_a(trans);
+ /* Send CONNECT to the calling subscriber. */
+ if (send_connect)
+ gcc_connect(trans);
+ /* Set parameter. */
+ set_parameter(trans);
+ /* Start inactivity timer, if uplink is free. */
+ if (!trans->gcc.uplink_busy)
+ start_inactivity_timer(trans);
+ /* Set cell of current talker. */
+ set_uplink_cell(trans->gcc.uplink_bss, NULL, cell_id);
+ /* Set MGW conference. */
+ set_mgw_conference(trans);
+}
+
+#define CONNECT_OPTION false
+
+static void vgcs_gcc_fsm_n0_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_trans *trans = fi->priv;
+ int rc;
+
+ switch (event) {
+ case VGCS_GCC_EV_NET_SETUP:
+ /* Establish call towards all BSSs. */
+ LOG_GCC(trans, LOGL_DEBUG, "Setup by network, trying to establish cells.\n");
+ rc = gcc_establish_bss(trans);
+ if (rc < 0) {
+ LOG_GCC(trans, LOGL_NOTICE, "Failed to setup call to any cell.\n");
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE);
+ break;
+ }
+ /* Keep state until established or released. */
+ break;
+ case VGCS_GCC_EV_NET_TERM:
+ LOG_GCC(trans, LOGL_DEBUG, "Termination by network, destroying call.\n");
+ /* Destroy group call in all cells. */
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING);
+ break;
+ case VGCS_GCC_EV_USER_SETUP:
+ LOG_GCC(trans, LOGL_DEBUG, "Setup by MS, trying to establish cells.\n");
+ /* Change state. */
+ osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_N1_CALL_INITIATED, 0, 0);
+ /* Establish call towards all BSSs. */
+ rc = gcc_establish_bss(trans);
+ if (rc < 0) {
+ LOG_GCC(trans, LOGL_NOTICE, "Failed to setup call to any cell.\n");
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE);
+ break;
+ }
+ if (CONNECT_OPTION) {
+ /* Send CONNECT to the calling subscriber. */
+ gcc_connect(trans);
+ /* Change state. */
+ osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_N3_CALL_EST_PROC, 0, 0);
+ }
+ break;
+ case VGCS_GCC_EV_BSS_ESTABLISHED:
+ LOG_GCC(trans, LOGL_DEBUG, "All cells establised, for a group call, sending CONNECT to caller.\n");
+ /* Change state. */
+ osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_N2_CALL_ACTIVE, 0, 0);
+ /* Start inactivity timer, if uplink is free. */
+ if (!trans->gcc.uplink_busy)
+ start_inactivity_timer(trans);
+ break;
+ case VGCS_GCC_EV_BSS_RELEASED:
+ LOG_GCC(trans, LOGL_DEBUG, "All group call in all cells failed, destroying call.\n");
+ /* Send TERMINATE to the calling subscriber. */
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_gcc_fsm_n1_call_initiated(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_trans *trans = fi->priv;
+ int rc;
+
+ switch (event) {
+ case VGCS_GCC_EV_NET_TERM:
+ LOG_GCC(trans, LOGL_DEBUG, "Termination by network, destroying call.\n");
+ /* Destroy group call in all cells. */
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING);
+ break;
+ case VGCS_GCC_EV_USER_TERM:
+ LOG_GCC(trans, LOGL_DEBUG, "Termination by user, destroying call.\n");
+ /* Send TERMINATE to the calling subscriber and destroy group call in all cells. */
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING);
+ break;
+ case VGCS_GCC_EV_BSS_ESTABLISHED:
+ LOG_GCC(trans, LOGL_DEBUG, "All cells establised, for a group call, assign caller to VGCS.\n");
+ /* Send assignment to the calling subscriber. */
+ rc = gcc_assign(trans);
+ if (rc < 0) {
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE);
+ break;
+ }
+ break;
+ case VGCS_GCC_EV_BSS_ASSIGN_CPL:
+ LOG_GCC(trans, LOGL_DEBUG, "Assignment complete, sending CONNECT to caller, releasing channel.\n");
+ /* Handle assignment complete */
+ _assign_complete(trans, true);
+ break;
+ case VGCS_GCC_EV_BSS_ASSIGN_FAIL:
+ LOG_GCC(trans, LOGL_DEBUG, "Assignment failed, releasing call.\n");
+ /* Send TERMINATE to the calling subscriber. */
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE);
+ break;
+ case VGCS_GCC_EV_BSS_RELEASED:
+ LOG_GCC(trans, LOGL_DEBUG, "All group call in all cells failed, destroying call.\n");
+ /* Send TERMINATE to the calling subscriber. */
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_gcc_fsm_n2_call_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_trans *trans = fi->priv;
+
+ switch (event) {
+ case VGCS_GCC_EV_NET_TERM:
+ LOG_GCC(trans, LOGL_DEBUG, "Termination by network, destroying call.\n");
+ /* Destroy group call in all cells. */
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING);
+ break;
+ case VGCS_GCC_EV_USER_TERM:
+ LOG_GCC(trans, LOGL_DEBUG, "Termination by user, destroying call.\n");
+ /* Send TERMINATE to the calling subscriber and destroy group call in all cells. */
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING);
+ break;
+ case VGCS_GCC_EV_BSS_RELEASED:
+ LOG_GCC(trans, LOGL_DEBUG, "All group call in all cells failed, destroying call.\n");
+ /* Send TERMINATE to the calling subscriber. */
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE);
+ break;
+ case VGCS_GCC_EV_TIMEOUT:
+ LOG_GCC(trans, LOGL_DEBUG, "Termination by inactivity timer, destroying call.\n");
+ /* Destroy group call in all cells. */
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_gcc_fsm_n3_call_est_proc(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_trans *trans = fi->priv;
+ int rc;
+
+ switch (event) {
+ case VGCS_GCC_EV_NET_TERM:
+ LOG_GCC(trans, LOGL_DEBUG, "Termination by network, destroying call.\n");
+ /* Destroy group call in all cells. */
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING);
+ break;
+ case VGCS_GCC_EV_USER_TERM:
+ LOG_GCC(trans, LOGL_DEBUG, "Termination by user, destroying call.\n");
+ /* Send TERMINATE to the calling subscriber and destroy group call in all cells. */
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING);
+ break;
+ case VGCS_GCC_EV_BSS_ESTABLISHED:
+ LOG_GCC(trans, LOGL_DEBUG, "All cells establised, for a group call, assign caller to VGCS.\n");
+ /* Send assignment to the calling subscriber. */
+ rc = gcc_assign(trans);
+ if (rc < 0) {
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE);
+ break;
+ }
+ break;
+ case VGCS_GCC_EV_BSS_ASSIGN_CPL:
+ LOG_GCC(trans, LOGL_DEBUG, "Assignment complete, sending CONNECT to caller, releasing channel.\n");
+ /* Handle assignment complete */
+ _assign_complete(trans, false);
+ break;
+ case VGCS_GCC_EV_BSS_ASSIGN_FAIL:
+ LOG_GCC(trans, LOGL_DEBUG, "Assignment failed, releasing call.\n");
+ /* Send TERMINATE to the calling subscriber. */
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE);
+ break;
+ case VGCS_GCC_EV_BSS_RELEASED:
+ LOG_GCC(trans, LOGL_DEBUG, "All group call in all cells failed, destroying call.\n");
+ /* Send TERMINATE to the calling subscriber. */
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static const struct osmo_fsm_state vgcs_gcc_fsm_states[] = {
+ [VGCS_GCC_ST_N0_NULL] = {
+ .name = "NULL (N0)",
+ .in_event_mask = S(VGCS_GCC_EV_NET_SETUP) |
+ S(VGCS_GCC_EV_NET_TERM) |
+ S(VGCS_GCC_EV_USER_SETUP) |
+ S(VGCS_GCC_EV_BSS_ESTABLISHED) |
+ S(VGCS_GCC_EV_BSS_RELEASED),
+ .out_state_mask = S(VGCS_GCC_ST_N1_CALL_INITIATED) |
+ S(VGCS_GCC_ST_N2_CALL_ACTIVE),
+ .action = vgcs_gcc_fsm_n0_null,
+ },
+ [VGCS_GCC_ST_N1_CALL_INITIATED] = {
+ .name = "CALL INITATED (N1)",
+ .in_event_mask = S(VGCS_GCC_EV_NET_TERM) |
+ S(VGCS_GCC_EV_USER_TERM) |
+ S(VGCS_GCC_EV_BSS_ESTABLISHED) |
+ S(VGCS_GCC_EV_BSS_ASSIGN_CPL) |
+ S(VGCS_GCC_EV_BSS_ASSIGN_FAIL) |
+ S(VGCS_GCC_EV_BSS_RELEASED),
+ .out_state_mask = S(VGCS_GCC_ST_N0_NULL) |
+ S(VGCS_GCC_ST_N2_CALL_ACTIVE) |
+ S(VGCS_GCC_ST_N3_CALL_EST_PROC),
+ .action = vgcs_gcc_fsm_n1_call_initiated,
+ },
+ [VGCS_GCC_ST_N2_CALL_ACTIVE] = {
+ .name = "CALL ACTIVE (N2)",
+ .in_event_mask = S(VGCS_GCC_EV_NET_TERM) |
+ S(VGCS_GCC_EV_USER_TERM) |
+ S(VGCS_GCC_EV_BSS_RELEASED) |
+ S(VGCS_GCC_EV_TIMEOUT),
+ .out_state_mask = S(VGCS_GCC_ST_N0_NULL),
+ .action = vgcs_gcc_fsm_n2_call_active,
+ },
+ [VGCS_GCC_ST_N3_CALL_EST_PROC] = {
+ .name = "CALL EST PROCEEDING (N3)",
+ .in_event_mask = S(VGCS_GCC_EV_NET_TERM) |
+ S(VGCS_GCC_EV_USER_TERM) |
+ S(VGCS_GCC_EV_BSS_ESTABLISHED) |
+ S(VGCS_GCC_EV_BSS_ASSIGN_CPL) |
+ S(VGCS_GCC_EV_BSS_ASSIGN_FAIL) |
+ S(VGCS_GCC_EV_BSS_RELEASED),
+ .out_state_mask = S(VGCS_GCC_ST_N2_CALL_ACTIVE) |
+ S(VGCS_GCC_ST_N0_NULL),
+ .action = vgcs_gcc_fsm_n3_call_est_proc,
+ },
+ // We don't need a state to wait for the group call to be terminated in all cells
+};
+
+static struct osmo_fsm vgcs_bcc_fsm = {
+ .name = "bcc",
+ .states = vgcs_gcc_fsm_states,
+ .num_states = ARRAY_SIZE(vgcs_gcc_fsm_states),
+ .log_subsys = DBCC,
+ .event_names = vgcs_gcc_fsm_event_names,
+};
+
+static struct osmo_fsm vgcs_gcc_fsm = {
+ .name = "gcc",
+ .states = vgcs_gcc_fsm_states,
+ .num_states = ARRAY_SIZE(vgcs_gcc_fsm_states),
+ .log_subsys = DGCC,
+ .event_names = vgcs_gcc_fsm_event_names,
+};
+
+const char *vgcs_bcc_gcc_state_name(struct osmo_fsm_inst *fi)
+{
+ return vgcs_gcc_fsm_states[fi->state].name;
+}
+
+static int update_uplink_state(struct vgcs_bss *bss, bool uplink_busy);
+
+/* Receive RR messages from calling subscriber, prior assignment to VGCS/VBS. */
+int gsm44068_rcv_rr(struct msc_a *msc_a, struct msgb *msg)
+{
+ struct gsm_trans *trans = NULL;
+ struct gsm48_hdr *gh;
+ uint8_t msg_type;
+
+ gh = msgb_l3(msg);
+ msg_type = gsm48_hdr_msg_type(gh);
+
+ /* Find transaction. */
+ trans = trans_find_by_type(msc_a, TRANS_GCC);
+ if (!trans)
+ trans = trans_find_by_type(msc_a, TRANS_BCC);
+
+ if (!trans) {
+ LOG_GCC(trans, LOGL_ERROR, "No VGCS/VBS transaction.\n");
+ return -EINVAL;
+ }
+
+ /* In case the phone releases uplink prior being assigned to a VGCS */
+ if (msg_type == GSM48_MT_RR_UPLINK_RELEASE) {
+ struct vgcs_bss *bss;
+
+ LOG_GCC(trans, LOGL_INFO, "Received UPLINK RELEASE on initial channel.\n");
+ /* Clear the busy flag and unblock all cells. */
+ trans->gcc.uplink_bss = NULL;
+ trans->gcc.uplink_cell = NULL;
+ trans->gcc.uplink_busy = false;
+ llist_for_each_entry(bss, &trans->gcc.bss_list, list) {
+ /* Update uplink state. */
+ update_uplink_state(bss, trans->gcc.uplink_busy);
+ }
+ /* Start inactivity timer. */
+ start_inactivity_timer(bss->trans);
+ /* Next, the MS will switch to the VGCS as listener. Nothing else to do here. */
+ }
+
+ return 0;
+}
+
+/* Allocation of transaction for group call */
+static struct gsm_trans *trans_alloc_vgcs(struct gsm_network *net,
+ struct vlr_subscr *vsub,
+ enum trans_type trans_type, uint8_t transaction_id,
+ uint32_t callref,
+ struct gcr *gcr,
+ bool uplink_busy)
+{
+ struct gsm_trans *trans;
+
+ trans = trans_alloc(net, vsub, trans_type, transaction_id, callref);
+ if (!trans) {
+ LOG_GCC(trans, LOGL_ERROR, "No memory for trans.\n");
+ return NULL;
+ }
+ /* The uplink is busy when the call is started until the calling subscriber releases. */
+ trans->gcc.uplink_busy = uplink_busy;
+ trans->gcc.uplink_originator = true;
+ INIT_LLIST_HEAD(&trans->gcc.bss_list);
+ trans->gcc.inactivity_to = gcr->timeout;
+ trans->gcc.mute_talker = gcr->mute_talker;
+ trans->gcc.timer_inactivity.data = trans;
+ trans->gcc.timer_inactivity.cb = inactivity_timer_cb;
+ trans->gcc.fi = osmo_fsm_inst_alloc((trans_type == TRANS_GCC) ? &vgcs_gcc_fsm : &vgcs_bcc_fsm,
+ trans, trans, LOGL_DEBUG, NULL);
+ if (!trans->gcc.fi) {
+ LOG_GCC(trans, LOGL_ERROR, "No memory for state machine.\n");
+ trans_free(trans);
+ return NULL;
+ }
+
+ return trans;
+}
+
+/* Create transaction from incoming voice group/broadcast call. */
+static struct gsm_trans *trans_create_bcc_gcc(struct msc_a *msc_a, enum trans_type trans_type, uint8_t transaction_id,
+ uint8_t pdisc, uint8_t msg_type, uint32_t callref)
+{
+ struct gsm_network *net = msc_a_net(msc_a);
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+ struct gsm_trans *trans = NULL;
+ struct gcr *gcr;
+ int rc;
+
+ if (!msc_a) {
+ LOG_GCC(trans, LOGL_ERROR, "Invalid conn: no msc_a\n");
+ return NULL;
+ }
+ if (!vsub) {
+ LOG_GCC(trans, LOGL_ERROR, "Invalid conn: no subscriber\n");
+ return NULL;
+ }
+
+ /* An earlier CM Service Request for this CC message now has concluded */
+ if (!osmo_use_count_by(&msc_a->use_count,
+ (trans_type == TRANS_GCC) ? MSC_A_USE_CM_SERVICE_GCC : MSC_A_USE_CM_SERVICE_BCC))
+ LOG_MSC_A(msc_a, LOGL_ERROR,
+ "Creating new %s transaction without prior CM Service Request.\n",
+ get_value_string(trans_type_names, trans_type));
+ else
+ msc_a_put(msc_a,
+ (trans_type == TRANS_GCC) ? MSC_A_USE_CM_SERVICE_GCC : MSC_A_USE_CM_SERVICE_BCC);
+
+ /* A transaction must be created with a SETUP message. */
+ if (msg_type != OSMO_GSM44068_MSGT_IMMEDIATE_SETUP
+ && msg_type != OSMO_GSM44068_MSGT_SETUP
+ && msg_type != OSMO_GSM44068_MSGT_IMMEDIATE_SETUP_2) {
+ LOG_GCC(trans, LOGL_ERROR, "No transaction and message is not a SETUP.\n");
+ return NULL;
+ }
+
+ /* Check if callref already exists. */
+ trans = trans_find_by_callref(net, trans_type, callref);
+ if (trans) {
+ LOG_GCC(trans, LOGL_INFO, "Call to existing %s with callref %s, rejecting!\n",
+ trans_type_name(trans_type), gsm44068_group_id_string(callref));
+ rc = gsm44068_tx_termination(msc_a, NULL,
+ pdisc | (transaction_id << 4),
+ OSMO_GSM44068_MSGT_TERMINATION,
+ OSMO_GSM44068_CAUSE_BUSY, NULL, 0);
+ if (rc < 0)
+ LOG_GCC(trans, LOGL_ERROR, "Failed to send TERMINATION towards MS.\n");
+ return 0;
+ }
+
+ /* Check GCR for Group ID. */
+ gcr = gcr_by_callref(net, trans_type, callref);
+ if (!gcr) {
+ LOG_GCC(trans, LOGL_INFO, "No Group configured for %s callref %s, rejecting!\n",
+ trans_type_name(trans_type), gsm44068_group_id_string(callref));
+ // FIXME: Better cause value for a group that does not exist ?
+ rc = gsm44068_tx_termination(msc_a, NULL,
+ pdisc | (transaction_id << 4),
+ OSMO_GSM44068_MSGT_TERMINATION,
+ OSMO_GSM44068_CAUSE_REQUESTED_SERVICE_NOT_SUB, NULL, 0);
+ if (rc < 0)
+ LOG_GCC(trans, LOGL_ERROR, "Failed to send TERMINATION towards MS.\n");
+ return 0;
+ }
+
+ /* Create transaction, uplink is busy. */
+ trans = trans_alloc_vgcs(net, vsub, trans_type, transaction_id, callref, gcr, true);
+ if (!trans) {
+ rc = gsm44068_tx_termination(msc_a, NULL,
+ pdisc | (transaction_id << 4),
+ OSMO_GSM44068_MSGT_TERMINATION,
+ OSMO_GSM44068_CAUSE_NETWORK_FAILURE, NULL, 0);
+ if (rc < 0)
+ LOG_GCC(trans, LOGL_ERROR, "Failed to send TERMINATION towards MS.\n");
+ return NULL;
+ }
+
+ if (osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_TRANSACTION_ACCEPTED, trans)) {
+ LOG_MSC_A(msc_a, LOGL_ERROR, "Not allowed to accept %s transaction.\n",
+ get_value_string(trans_type_names, trans_type));
+ gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE);
+ return NULL;
+ }
+
+ /* Assign transaction */
+ msc_a_get(msc_a, (trans_type == TRANS_GCC) ? MSC_A_USE_GCC : MSC_A_USE_BCC);
+ trans->msc_a = msc_a;
+ trans->dlci = 0; /* main DCCH */
+
+ return trans;
+}
+
+/* Receive GCC/BCC messages from calling subscriber, depending on the PDISC used. */
+int gsm44068_rcv_bcc_gcc(struct msc_a *msc_a, struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t msg_type = gsm48_hdr_msg_type(gh);
+ uint8_t pdisc = gsm48_hdr_pdisc(gh);
+ uint8_t transaction_id = gsm48_hdr_trans_id_flip_ti(gh);
+ enum trans_type trans_type = (pdisc == GSM48_PDISC_GROUP_CC) ? TRANS_GCC : TRANS_BCC;
+
+ uint8_t key_seq;
+ bool talker_prio_requested;
+ bool with_talker_prio;
+ uint8_t talker_prio;
+ struct gsm48_classmark2 cm2;
+ struct osmo_mobile_identity mi;
+ uint32_t callref;
+ bool with_prio;
+ uint8_t prio;
+ char user_user[64] = "";
+ uint8_t cause;
+ uint8_t diag[256];
+ uint8_t diag_len;
+ bool with_call_state;
+ enum osmo_gsm44068_call_state call_state;
+ bool with_state_attrs;
+ uint8_t da, ua, comm, oi;
+ int rc = 0;
+
+ /* Remove sequence number (bit 7) from message type. */
+ msg_type &= 0xbf;
+
+ /* Parse messages. */
+ switch (msg_type) {
+ case OSMO_GSM44068_MSGT_SETUP:
+ rc = gsm44068_rx_setup(msg, &talker_prio_requested, &talker_prio, &callref, &with_prio, &prio,
+ user_user);
+ break;
+ case OSMO_GSM44068_MSGT_IMMEDIATE_SETUP:
+ case OSMO_GSM44068_MSGT_IMMEDIATE_SETUP_2:
+ rc = gsm44068_rx_immediate_setup(msg, &talker_prio, &key_seq, &cm2, &mi, &callref, &with_prio, &prio,
+ user_user);
+ break;
+ case OSMO_GSM44068_MSGT_STATUS:
+ rc = gsm44068_rx_status(msg, &cause, diag, &diag_len, &with_call_state, &call_state,
+ &with_state_attrs, &da, &ua, &comm, &oi);
+ break;
+ case OSMO_GSM44068_MSGT_TERMINATION_REQUEST:
+ rc = gsm44068_rx_termination_req(msg, &callref, &with_prio, &prio, &with_talker_prio, &talker_prio);
+ break;
+ default:
+ LOG_GCC(trans, LOGL_ERROR, "Invalid message type: 0x%02x\n", msg_type);
+ return -EINVAL;
+ }
+ if (rc < 0)
+ return rc;
+
+ /* Find transaction, if called from msc_a. */
+ if (!trans)
+ trans = trans_find_by_id(msc_a, trans_type, transaction_id);
+
+ /* Create transaction for SETUP message. */
+ if (!trans) {
+ trans = trans_create_bcc_gcc(msc_a, trans_type, transaction_id, pdisc, msg_type, callref);
+ if (!trans)
+ return -EINVAL;
+ } else {
+ /* A phone may not call while a VGCS is already active */
+ if (msg_type == OSMO_GSM44068_MSGT_IMMEDIATE_SETUP
+ || msg_type == OSMO_GSM44068_MSGT_SETUP
+ || msg_type == OSMO_GSM44068_MSGT_IMMEDIATE_SETUP_2) {
+ LOG_GCC(trans, LOGL_ERROR, "Received SETUP while call is already set up, rejecting.\n");
+ rc = gsm44068_tx_termination(msc_a, NULL,
+ pdisc | (transaction_id << 4),
+ OSMO_GSM44068_MSGT_TERMINATION,
+ OSMO_GSM44068_CAUSE_NETWORK_FAILURE, NULL, 0);
+ if (rc < 0)
+ LOG_GCC(trans, LOGL_ERROR, "Failed to send TERMINATION towards MS.\n");
+ return -EINVAL;
+ }
+ }
+
+ /* Handle received GCC messages (trigger state machine). */
+ switch (msg_type) {
+ case OSMO_GSM44068_MSGT_IMMEDIATE_SETUP:
+ case OSMO_GSM44068_MSGT_SETUP:
+ case OSMO_GSM44068_MSGT_IMMEDIATE_SETUP_2:
+ LOG_GCC(trans, LOGL_INFO, "Received SETUP.\n");
+ osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_USER_SETUP, NULL);
+ break;
+ case OSMO_GSM44068_MSGT_STATUS:
+ LOG_GCC(trans, LOGL_NOTICE, "Received STATUS with cause %d (%s).\n", cause,
+ get_value_string(osmo_gsm44068_cause_names, cause));
+ if (diag_len)
+ LOG_GCC(trans, LOGL_NOTICE, " -> diagnostics: %s\n", osmo_hexdump(diag, diag_len));
+ if (with_call_state)
+ LOG_GCC(trans, LOGL_NOTICE, " -> call state %s\n",
+ get_value_string(osmo_gsm44068_call_state_names, call_state));
+ break;
+ case OSMO_GSM44068_MSGT_TERMINATION_REQUEST:
+ LOG_GCC(trans, LOGL_INFO, "Received TERMINATRION REQUEST.\n");
+ if (callref != trans->callref) {
+ LOG_GCC(trans, LOGL_NOTICE, "Received callref 0x%x does not match!\n", callref);
+ break;
+ }
+ osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_USER_TERM, NULL);
+ break;
+ }
+
+ return 0;
+}
+
+static void bss_clear(struct vgcs_bss *bss, uint8_t cause, bool notify_trans);
+
+/* Call Control Specific transaction release.
+ * gets called by trans_free, DO NOT CALL YOURSELF! */
+void gsm44068_bcc_gcc_trans_free(struct gsm_trans *trans)
+{
+ struct vgcs_bss *bss, *bss2;
+
+ /* Change state. */
+ osmo_fsm_inst_state_chg(trans->gcc.fi, VGCS_GCC_ST_N0_NULL, 0, 0);
+
+ /* Free FSM. */
+ if (trans->gcc.fi)
+ osmo_fsm_inst_term(trans->gcc.fi, OSMO_FSM_TERM_REGULAR, NULL);
+
+ /* Remove relations to cells.
+ * We must loop safe, because bss_clear() will detach every call control instance from list. */
+ llist_for_each_entry_safe(bss, bss2, &trans->gcc.bss_list, list)
+ osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_CLEAR, NULL);
+
+ /* Stop inactivity timer. */
+ stop_inactivity_timer(trans);
+}
+
+/* Create a new call from VTY command. */
+const char *vgcs_vty_initiate(struct gsm_network *gsmnet, struct gcr *gcr)
+{
+ enum trans_type trans_type;
+ uint32_t callref;
+ struct gsm_trans *trans;
+
+ /* Get callref from stored suffix. Caller cannot choose a prefix. */
+ trans_type = gcr->trans_type;
+ callref = atoi(gcr->group_id);
+
+ /* Check if callref already exists. */
+ trans = trans_find_by_callref(gsmnet, trans_type, callref);
+ if (trans) {
+ LOG_GCC(trans, LOGL_INFO, "Call to existing %s with callref %s, rejecting!\n",
+ trans_type_name(trans_type), gsm44068_group_id_string(callref));
+ return "Call already exists.";
+ }
+
+ /* Create transaction, uplink is free. */
+ trans = trans_alloc_vgcs(gsmnet, NULL, trans_type, 0, callref, gcr, false);
+ if (!trans) {
+ LOG_GCC(trans, LOGL_ERROR, "No memory for trans.\n");
+ return "Failed to create call.";
+ }
+
+ LOG_GCC(trans, LOGL_INFO, "VTY initiates call.\n");
+ osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_NET_SETUP, NULL);
+
+ return NULL;
+}
+
+/* Destroy a call from VTY command. */
+const char *vgcs_vty_terminate(struct gsm_network *gsmnet, struct gcr *gcr)
+{
+ enum trans_type trans_type;
+ uint32_t callref;
+ struct gsm_trans *trans;
+
+ /* Get callref from stored suffix. Caller cannot choose a prefix. */
+ trans_type = gcr->trans_type;
+ callref = atoi(gcr->group_id);
+
+ /* Check if callref exists. */
+ trans = trans_find_by_callref(gsmnet, trans_type, callref);
+ if (!trans)
+ return "Call does not exist.";
+
+ LOG_GCC(trans, LOGL_INFO, "VTY terminates call.\n");
+ osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_NET_TERM, NULL);
+
+ return NULL;
+}
+
+/*
+ * BSS state machine - handles all BSS "call control" instances
+ */
+
+static const struct value_string vgcs_bss_fsm_event_names[] = {
+ OSMO_VALUE_STRING(VGCS_BSS_EV_SETUP),
+ OSMO_VALUE_STRING(VGCS_BSS_EV_SETUP_ACK),
+ OSMO_VALUE_STRING(VGCS_BSS_EV_SETUP_REFUSE),
+ OSMO_VALUE_STRING(VGCS_BSS_EV_ACTIVE_OR_FAIL),
+ OSMO_VALUE_STRING(VGCS_BSS_EV_UL_REQUEST),
+ OSMO_VALUE_STRING(VGCS_BSS_EV_UL_REQUEST_CNF),
+ OSMO_VALUE_STRING(VGCS_BSS_EV_UL_APP_DATA),
+ OSMO_VALUE_STRING(VGCS_BSS_EV_BSS_DTAP),
+ OSMO_VALUE_STRING(VGCS_BSS_EV_UL_RELEASE),
+ OSMO_VALUE_STRING(VGCS_BSS_EV_CLEAR),
+ OSMO_VALUE_STRING(VGCS_BSS_EV_CLOSE),
+ OSMO_VALUE_STRING(VGCS_BSS_EV_RELEASED),
+ { }
+};
+
+/* Blocks or unblocks uplinks of a BSS. */
+static int update_uplink_state(struct vgcs_bss *bss, bool uplink_busy)
+{
+ struct ran_msg ran_msg;
+ int rc;
+
+ if (uplink_busy) {
+ /* Send UPLINK SEIZED COMMAND to BSS. */
+ LOG_BSS(bss, LOGL_DEBUG, "Sending (VGCS) UPLINK SEIZED COMMAND towards BSS.\n");
+ ran_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_UPLINK_SEIZED_CMD,
+ .uplink_seized_cmd = {
+ .cause = GSM0808_CAUSE_CALL_CONTROL,
+ },
+ };
+ } else {
+ /* Send UPLINK RELEASE COMMAND to BSS. */
+ LOG_BSS(bss, LOGL_DEBUG, "Sending (VGCS) UPLINK RELEASE COMMAND towards BSS.\n");
+ ran_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_UPLINK_RELEASE_CMD,
+ .uplink_release_cmd = {
+ .cause = GSM0808_CAUSE_CALL_CONTROL,
+ },
+ };
+ }
+
+ rc = ran_encode_and_send(bss->fi, &ran_msg, bss->conn, false);
+
+ return rc;
+}
+
+/* Clear the connection towards BSS.
+ * The instance is removed soon, so it is detached from transaction and cells. */
+static void bss_clear(struct vgcs_bss *bss, uint8_t cause, bool notify_trans)
+{
+ struct ran_msg ran_msg;
+ struct gsm_trans *trans = bss->trans;
+ struct vgcs_bss_cell *cell, *cell2;
+
+ /* Must detach us from transaction. */
+ if (bss->trans) {
+ /* Remove pointer to talking BSS and cell. */
+ if (bss == bss->trans->gcc.uplink_bss) {
+ bss->trans->gcc.uplink_bss = NULL;
+ bss->trans->gcc.uplink_cell = NULL;
+ }
+ llist_del(&bss->list);
+ bss->trans = NULL;
+ }
+
+ /* Change state. */
+ osmo_fsm_inst_state_chg(bss->fi, VGCS_BSS_ST_RELEASE, 0, 0);
+
+ /* Send Clear Command to BSS. */
+ ran_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_CLEAR_COMMAND,
+ .clear_command = {
+ .gsm0808_cause = cause,
+ },
+ };
+ if (bss->conn) {
+ LOG_BSS(bss, LOGL_DEBUG, "Sending CLEAR COMMAND for call controling channel.\n");
+ ran_encode_and_send(bss->fi, &ran_msg, bss->conn, false);
+ }
+
+ /* Trigger clear of all cells. Be safe, because the process will remove cells from list. */
+ llist_for_each_entry_safe(cell, cell2, &bss->cell_list, list_bss)
+ osmo_fsm_inst_dispatch(cell->fi, VGCS_CELL_EV_CLEAR, NULL);
+
+ /* Detach us from all BSS, if still linked */
+ llist_for_each_entry_safe(cell, cell2, &bss->cell_list, list_bss) {
+ llist_del(&cell->list_bss);
+ cell->bss = NULL;
+ }
+
+ /* If all BS are gone, notify calling subscriber process. */
+ if (notify_trans && trans && llist_empty(&trans->gcc.bss_list)) {
+ LOG_BSS(bss, LOGL_DEBUG, "Notify calling user process, that all BSSs are cleared.\n");
+ osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_BSS_RELEASED, NULL);
+ }
+}
+
+/* When finally the BSS connection is released. (CLEAR COMPLETE response)
+ * The instance is removed, so it is detached from transaction and cells, if not already. */
+static void bss_destroy(struct vgcs_bss *bss)
+{
+ struct vgcs_bss_cell *cell, *cell2;
+
+ LOG_BSS(bss, LOGL_DEBUG, "Removing BSS call controling instance.\n");
+
+ /* Must detach us from transaction, if not already. */
+ if (bss->trans) {
+ /* Remove pointer to talking BSS and cell. */
+ if (bss == bss->trans->gcc.uplink_bss) {
+ bss->trans->gcc.uplink_bss = NULL;
+ bss->trans->gcc.uplink_cell = NULL;
+ }
+ llist_del(&bss->list);
+ bss->trans = NULL;
+ }
+
+ /* Detach us from RAN connection. */
+ if (bss->conn) {
+ if (bss->conn->vgcs.bss == bss)
+ bss->conn->vgcs.bss = NULL;
+ if (bss->conn->vgcs.cell == bss)
+ bss->conn->vgcs.cell = NULL;
+ ran_conn_close(bss->conn);
+ bss->conn = NULL;
+ }
+
+ /* Detach us from all BSS, if still linked */
+ llist_for_each_entry_safe(cell, cell2, &bss->cell_list, list_bss) {
+ llist_del(&cell->list_bss);
+ cell->bss = NULL;
+ }
+
+ /* Free FSM. (should be allocated) */
+ osmo_fsm_inst_state_chg(bss->fi, VGCS_BSS_ST_NULL, 0, 0);
+ osmo_fsm_inst_term(bss->fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+/* Get identity of talker.
+ * This is required to detect if the talker is the calling subscriber. */
+static int talker_identity(struct vgcs_bss *bss, uint8_t *l3, int l3_len)
+{
+ struct osmo_mobile_identity mi;
+ int rc;
+
+ puts(osmo_hexdump(l3, l3_len));
+ rc = osmo_mobile_identity_decode_from_l3_buf(&mi, l3, l3_len, false);
+ if (rc < 0) {
+ LOG_BSS(bss, LOGL_DEBUG, "Talker's Identity cannot be decoded.\n");
+ return rc;
+ }
+
+ switch (mi.type) {
+ case GSM_MI_TYPE_IMSI:
+ if (!bss->trans->vsub)
+ break;
+ LOG_BSS(bss, LOGL_DEBUG, "Talker's sends IMSI %s, originator has IMSI %s.\n",
+ mi.imsi, bss->trans->vsub->imsi);
+ if (!strcmp(mi.imsi, bss->trans->vsub->imsi))
+ return 1;
+ break;
+ case GSM_MI_TYPE_TMSI:
+ if (!bss->trans->vsub)
+ break;
+ LOG_BSS(bss, LOGL_DEBUG, "Talker's sends TMSI 0x%08x, originator has TMSI 0x%08x.\n",
+ mi.tmsi, bss->trans->vsub->tmsi);
+ if (mi.tmsi == bss->trans->vsub->tmsi)
+ return 1;
+ break;
+ default:
+ LOG_BSS(bss, LOGL_DEBUG, "Talker's Identity is not IMSI nor TMSI.\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void vgcs_bss_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct vgcs_bss *bss = fi->priv;
+ struct ran_msg ran_msg;
+
+ switch (event) {
+ case VGCS_BSS_EV_SETUP:
+ /* Change state. */
+ osmo_fsm_inst_state_chg(fi, VGCS_BSS_ST_SETUP, 0, 0);
+ /* Send VGCS/VBS SETUP to BSS. */
+ LOG_BSS(bss, LOGL_DEBUG, "Sending VGCS/VBS SETUP towards BSS.\n");
+ ran_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_VGCS_VBS_SETUP,
+ .vgcs_vbs_setup = {
+ .callref = {
+ .sf = (bss->trans->type == TRANS_GCC),
+ },
+ .vgcs_feature_flags_present = true,
+ },
+ };
+ osmo_store32be_ext(bss->callref >> 3, &ran_msg.vgcs_vbs_setup.callref.call_ref_hi, 3);
+ ran_msg.vgcs_vbs_setup.callref.call_ref_lo = bss->callref & 0x7;
+ /* First message, so we must set "initial" to "true". */
+ ran_encode_and_send(fi, &ran_msg, bss->conn, true);
+ break;
+ case VGCS_BSS_EV_CLEAR:
+ /* The calling user process requested clearing of VGCS/VBS call. */
+ LOG_BSS(bss, LOGL_DEBUG, "Received clearing from calling user process.\n");
+ bss_clear(bss, GSM0808_CAUSE_CALL_CONTROL, false);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_bss_fsm_setup(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct vgcs_bss *bss = fi->priv;
+ struct vgcs_bss_cell *cell, *cell2;
+
+ switch (event) {
+ case VGCS_BSS_EV_SETUP_ACK:
+ /* Receive VGCS/VBS SETUP ACK from BSS. */
+ LOG_BSS(bss, LOGL_DEBUG, "Received VGCS/VBS SETUP ACK from BSS.\n");
+ /* Send current uplink state to this BSS. */
+ if (bss->trans)
+ update_uplink_state(bss, bss->trans->gcc.uplink_busy);
+ /* Change state. */
+ osmo_fsm_inst_state_chg(fi, VGCS_BSS_ST_ASSIGNMENT, 0, 0);
+ /* Trigger VGCS/VBS ASSIGNMENT */
+ llist_for_each_entry_safe(cell, cell2, &bss->cell_list, list_bss)
+ osmo_fsm_inst_dispatch(cell->fi, VGCS_CELL_EV_ASSIGN, NULL);
+ /* If all failed, clear call. */
+ if (llist_empty(&bss->cell_list)) {
+ LOG_BSS(bss, LOGL_NOTICE, "All VGCS/VBS assignments failed.\n");
+ bss_clear(bss, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC, true);
+ break;
+ }
+ break;
+ case VGCS_BSS_EV_SETUP_REFUSE:
+ /* Received VGCS/VBS SETUP REFUSE from BSS. */
+ LOG_BSS(bss, LOGL_NOTICE, "Received VGCS/VBS SETUP REFUSE from BSS.\n");
+ bss_clear(bss, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC, true);
+ break;
+ case VGCS_BSS_EV_CLEAR:
+ /* The calling user process requested clearing of VGCS/VBS call. */
+ LOG_BSS(bss, LOGL_DEBUG, "Received clearing from calling user process.\n");
+ bss_clear(bss, GSM0808_CAUSE_CALL_CONTROL, false);
+ break;
+ case VGCS_BSS_EV_CLOSE:
+ /* The SCCP connection from the MSC has been closed. */
+ LOG_BSS(bss, LOGL_NOTICE, "Received SCCP connecting closing from MSC.\n");
+ if (bss->conn) {
+ bss->conn->vgcs.bss = NULL;
+ bss->conn = NULL;
+ }
+ bss_clear(bss, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC, true);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_bss_fsm_assignment(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct vgcs_bss *bss = fi->priv;
+ struct vgcs_bss_cell *c;
+ bool assigned;
+
+ switch (event) {
+ case VGCS_BSS_EV_ACTIVE_OR_FAIL:
+ /* If all gone, clear call. */
+ if (llist_empty(&bss->cell_list)) {
+ LOG_BSS(bss, LOGL_NOTICE, "All VGCS/VBS assignments failed.\n");
+ bss_clear(bss, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC, true);
+ break;
+ }
+ /* Is there a response for all cells?
+ * This means that all the channels have a positive response
+ * There is no channel with negative response, because a
+ * negative response will remove the channel. */
+ assigned = true;
+ llist_for_each_entry(c, &bss->cell_list, list_bss) {
+ if (!c->assigned)
+ assigned = false;
+ }
+ if (!assigned)
+ break;
+ LOG_BSS(bss, LOGL_DEBUG, "All VGCS/VBS assignments have responded.\n");
+ /* Change state. */
+ osmo_fsm_inst_state_chg(fi, VGCS_BSS_ST_ACTIVE, 0, 0);
+ /* Notify calling subscriber process. */
+ LOG_BSS(bss, LOGL_DEBUG, "Notify calling user process, that all BSSs are connected.\n");
+ if (bss->trans)
+ osmo_fsm_inst_dispatch(bss->trans->gcc.fi, VGCS_GCC_EV_BSS_ESTABLISHED, NULL);
+ break;
+ case VGCS_BSS_EV_CLEAR:
+ /* The calling user process requested clearing of VGCS/VBS call. */
+ LOG_BSS(bss, LOGL_DEBUG, "Received clearing from calling user process.\n");
+ bss_clear(bss, GSM0808_CAUSE_CALL_CONTROL, false);
+ break;
+ case VGCS_BSS_EV_CLOSE:
+ /* The SCCP connection from the MSC has been closed. */
+ LOG_BSS(bss, LOGL_NOTICE, "Received SCCP connecting closing from MSC.\n");
+ if (bss->conn) {
+ bss->conn->vgcs.bss = NULL;
+ bss->conn = NULL;
+ }
+ bss_clear(bss, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC, true);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_bss_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct vgcs_bss *bss = fi->priv, *other;
+ struct ran_msg *rx_ran_msg = data;
+ struct ran_msg tx_ran_msg;
+ int rc;
+
+ switch (event) {
+ case VGCS_BSS_EV_UL_REQUEST:
+ LOG_BSS(bss, LOGL_DEBUG, "Listener changed to talker.\n");
+ if (!bss->trans)
+ break;
+ /* Someone is talking. Check if there is no other uplink already busy.
+ * This should not happen, since all other cells are blocked (SEIZED) as soon as the uplink was
+ * requested. This may happen due to a race condition, where the uplink was requested before the
+ * UPLINK SEIZED COMMAND has been received by BSS. */
+ if (bss->trans->gcc.uplink_busy) {
+ /* Send UPLINK REJECT COMMAND to BSS. */
+ LOG_BSS(bss, LOGL_DEBUG, "Sending (VGCS) UPLINK REJECT COMMAND towards BSS.\n");
+ tx_ran_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_UPLINK_REJECT_CMD,
+ .uplink_reject_cmd = {
+ .cause = GSM0808_CAUSE_CALL_CONTROL,
+ },
+ };
+ ran_encode_and_send(fi, &tx_ran_msg, bss->conn, false);
+ break;
+ }
+ /* Send UPLINK REQUEST ACKNOWLEDGE to BSS. */
+ LOG_BSS(bss, LOGL_DEBUG, "Sending (VGCS) UPLINK REQUEST ACKNOWLEDGE towards BSS.\n");
+ tx_ran_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_UPLINK_REQUEST_ACK,
+ };
+ ran_encode_and_send(fi, &tx_ran_msg, bss->conn, false);
+ /* Set the busy flag and block all other cells. */
+ bss->trans->gcc.uplink_bss = bss;
+ bss->trans->gcc.uplink_busy = true;
+ bss->trans->gcc.uplink_originator = false;
+ llist_for_each_entry(other, &bss->trans->gcc.bss_list, list) {
+ if (other == bss)
+ continue;
+ /* Update uplink state. */
+ update_uplink_state(bss, bss->trans->gcc.uplink_busy);
+ }
+ /* Stop inactivity timer. */
+ stop_inactivity_timer(bss->trans);
+ break;
+ case VGCS_BSS_EV_UL_REQUEST_CNF:
+ LOG_BSS(bss, LOGL_DEBUG, "Talker established uplink.\n");
+ if (!bss->trans)
+ break;
+ if (!bss->trans->gcc.uplink_busy || bss->trans->gcc.uplink_bss != bss) {
+ LOG_BSS(bss, LOGL_ERROR, "Got UL REQUEST CNF, but we did not granted uplink.\n");
+ break;
+ }
+ /* Determine if talker is the originator of the call. */
+ rc = talker_identity(bss, rx_ran_msg->uplink_request_cnf.l3.l3,
+ rx_ran_msg->uplink_request_cnf.l3.l3_len);
+ if (rc > 0) {
+ bss->trans->gcc.uplink_originator = true;
+ LOG_BSS(bss, LOGL_DEBUG, "Talker is the originator of the call.\n");
+ }
+ /* Set parameter. */
+ set_parameter(bss->trans);
+ /* Set cell of current talker. */
+ set_uplink_cell(bss, &rx_ran_msg->uplink_request_cnf.cell_identifier, 0);
+ /* Set MGW conference. */
+ set_mgw_conference(bss->trans);
+ break;
+ case VGCS_BSS_EV_UL_APP_DATA:
+ LOG_BSS(bss, LOGL_DEBUG, "Talker sends application data on uplink.\n");
+ if (!bss->trans)
+ break;
+ if (!bss->trans->gcc.uplink_busy || bss->trans->gcc.uplink_bss != bss) {
+ LOG_BSS(bss, LOGL_ERROR, "Got UP APP DATA, but we did not granted uplink.\n");
+ break;
+ }
+ // FIXME: Use L3 info and feed to app.
+ break;
+ case VGCS_BSS_EV_BSS_DTAP:
+ LOG_BSS(bss, LOGL_DEBUG, "Talker sends DTAP message.\n");
+ if (!bss->trans)
+ break;
+ if (!bss->trans->gcc.uplink_busy || bss->trans->gcc.uplink_bss != bss) {
+ LOG_BSS(bss, LOGL_ERROR, "Got DTAP from BSS, but we did not granted uplink.\n");
+ break;
+ }
+ gsm44068_rcv_bcc_gcc(NULL, bss->trans, rx_ran_msg->dtap);
+ break;
+ case VGCS_BSS_EV_UL_RELEASE:
+ LOG_BSS(bss, LOGL_DEBUG, "Talker released uplink.\n");
+ if (!bss->trans)
+ break;
+ if (bss->trans->type == TRANS_BCC) {
+ LOG_BSS(bss, LOGL_DEBUG, "This is a broadcast call, terminating call.\n");
+ gcc_terminate_and_destroy(bss->trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING);
+ break;
+ }
+ if (!bss->trans->gcc.uplink_busy) {
+ LOG_BSS(bss, LOGL_NOTICE, "Got uplink release, but no uplink busy.\n");
+ break;
+ }
+ /* Talker release the uplink. Ignore, if not from the current talking cell. */
+ if (bss->trans->gcc.uplink_bss != bss) {
+ LOG_BSS(bss, LOGL_NOTICE, "Got uplink release, but uplink busy in other cell.\n");
+ break;
+ }
+ /* Clear the busy flag and unblock all other cells. */
+ bss->trans->gcc.uplink_bss = NULL;
+ bss->trans->gcc.uplink_cell = NULL;
+ bss->trans->gcc.uplink_busy = false;
+ llist_for_each_entry(other, &bss->trans->gcc.bss_list, list) {
+ if (other == bss)
+ continue;
+ /* Update uplink state. */
+ if (bss->trans)
+ update_uplink_state(bss, bss->trans->gcc.uplink_busy);
+ }
+ /* Set MGW conference. */
+ set_mgw_conference(bss->trans);
+ /* Start inactivity timer. */
+ start_inactivity_timer(bss->trans);
+ break;
+ case VGCS_BSS_EV_CLEAR:
+ /* The calling user process requested clearing of VGCS/VBS call. */
+ LOG_BSS(bss, LOGL_DEBUG, "Received clearing from calling user process.\n");
+ bss_clear(bss, GSM0808_CAUSE_CALL_CONTROL, false);
+ break;
+ case VGCS_BSS_EV_CLOSE:
+ /* The SCCP connection from the MSC has been closed. */
+ LOG_BSS(bss, LOGL_NOTICE, "Received SCCP connecting closing from MSC.\n");
+ if (bss->conn) {
+ bss->conn->vgcs.bss = NULL;
+ bss->conn = NULL;
+ }
+ bss_clear(bss, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC, true);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_bss_fsm_release(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct vgcs_bss *bss = fi->priv;
+
+ switch (event) {
+ case VGCS_BSS_EV_CLOSE:
+ /* The SCCP connection from the MSC has been closed while waitring fro CLEAR COMPLETE. */
+ LOG_BSS(bss, LOGL_NOTICE, "Received SCCP closing collision.\n");
+ bss_destroy(bss);
+ break;
+ case VGCS_BSS_EV_RELEASED:
+ LOG_BSS(bss, LOGL_DEBUG, "Received CLEAR COMPLETE from BSS, we are done!\n");
+ bss_destroy(bss);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static const struct osmo_fsm_state vgcs_bss_fsm_states[] = {
+ [VGCS_BSS_ST_NULL] = {
+ .name = "NULL",
+ .in_event_mask = S(VGCS_BSS_EV_SETUP) |
+ S(VGCS_BSS_EV_CLEAR),
+ .out_state_mask = S(VGCS_BSS_ST_SETUP),
+ .action = vgcs_bss_fsm_null,
+ },
+ [VGCS_BSS_ST_SETUP] = {
+ .name = "SETUP sent",
+ .in_event_mask = S(VGCS_BSS_EV_SETUP_ACK) |
+ S(VGCS_BSS_EV_SETUP_REFUSE) |
+ S(VGCS_BSS_EV_CLEAR) |
+ S(VGCS_BSS_EV_CLOSE),
+ .out_state_mask = S(VGCS_BSS_ST_ASSIGNMENT) |
+ S(VGCS_BSS_ST_RELEASE),
+ .action = vgcs_bss_fsm_setup,
+ },
+ [VGCS_BSS_ST_ASSIGNMENT] = {
+ .name = "ASSIGNMENT Sent",
+ .in_event_mask = S(VGCS_BSS_EV_ACTIVE_OR_FAIL) |
+ S(VGCS_BSS_EV_CLEAR) |
+ S(VGCS_BSS_EV_CLOSE),
+ .out_state_mask = S(VGCS_BSS_ST_ACTIVE) |
+ S(VGCS_BSS_ST_RELEASE),
+ .action = vgcs_bss_fsm_assignment,
+ },
+ [VGCS_BSS_ST_ACTIVE] = {
+ .name = "VGCS/VBS Active",
+ .in_event_mask = S(VGCS_BSS_EV_UL_REQUEST) |
+ S(VGCS_BSS_EV_UL_REQUEST_CNF) |
+ S(VGCS_BSS_EV_UL_APP_DATA) |
+ S(VGCS_BSS_EV_BSS_DTAP) |
+ S(VGCS_BSS_EV_UL_RELEASE) |
+ S(VGCS_BSS_EV_CLEAR) |
+ S(VGCS_BSS_EV_CLOSE),
+ .out_state_mask = S(VGCS_BSS_ST_RELEASE),
+ .action = vgcs_bss_fsm_active,
+ },
+ [VGCS_BSS_ST_RELEASE] = {
+ .name = "Releasing VGCS/VBS control",
+ .in_event_mask = S(VGCS_BSS_EV_CLEAR) |
+ S(VGCS_BSS_EV_RELEASED),
+ .out_state_mask = S(VGCS_BSS_ST_NULL),
+ .action = vgcs_bss_fsm_release,
+ },
+};
+
+static struct osmo_fsm vgcs_bss_fsm = {
+ .name = "vgcs_bss",
+ .states = vgcs_bss_fsm_states,
+ .num_states = ARRAY_SIZE(vgcs_bss_fsm_states),
+ .log_subsys = DASCI,
+ .event_names = vgcs_bss_fsm_event_names,
+};
+
+/* The BSS accepts VGCS/VBS and sends us supported features. */
+void vgcs_vbs_setup_ack(struct vgcs_bss *bss, const struct ran_msg *ran_msg)
+{
+ if (!bss->trans)
+ return;
+ osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_SETUP_ACK, (void *)ran_msg);
+}
+
+/* The BSS refuses VGCS/VBS. */
+void vgcs_vbs_setup_refuse(struct vgcs_bss *bss, const struct ran_msg *ran_msg)
+{
+ if (!bss->trans)
+ return;
+ osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_SETUP_REFUSE, (void *)ran_msg);
+}
+
+/* The BSS needs more time for VGCS/VBS channel assignment. */
+void vgcs_vbs_queuing_ind(struct vgcs_bss_cell *cell)
+{
+ if (!cell->bss)
+ return;
+}
+
+/* A mobile station requests the uplink on a VGCS channel. */
+void vgcs_uplink_request(struct vgcs_bss *bss, const struct ran_msg *ran_msg)
+{
+ if (!bss->trans)
+ return;
+ osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_UL_REQUEST, (void *)ran_msg);
+}
+
+/* The uplink on a VGCS channel has been established. */
+void vgcs_uplink_request_cnf(struct vgcs_bss *bss, const struct ran_msg *ran_msg)
+{
+ if (!bss->trans)
+ return;
+ osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_UL_REQUEST_CNF, (void *)ran_msg);
+}
+
+/* Application data received on the uplink of a VGCS channel. */
+void vgcs_app_data(struct vgcs_bss *bss, const struct ran_msg *ran_msg)
+{
+ if (!bss->trans)
+ return;
+ osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_UL_APP_DATA, (void *)ran_msg);
+}
+
+/* Application data received on the uplink of a VGCS channel. */
+void vgcs_bss_dtap(struct vgcs_bss *bss, const struct ran_msg *ran_msg)
+{
+ if (!bss->trans)
+ return;
+ osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_BSS_DTAP, (void *)ran_msg);
+}
+
+/* A mobile station releases the uplink on a VGCS channel. */
+void vgcs_uplink_release_ind(struct vgcs_bss *bss, const struct ran_msg *ran_msg)
+{
+ if (!bss->trans)
+ return;
+ osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_UL_RELEASE, (void *)ran_msg);
+}
+
+/* The BSS gives cell status about VGCS/VBS channel. */
+void vgcs_vbs_assign_status(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg)
+{
+ if (!cell->bss)
+ return;
+}
+
+void vgcs_vbs_caller_assign_cpl(struct gsm_trans *trans)
+{
+ osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_BSS_ASSIGN_CPL, NULL);
+}
+
+void vgcs_vbs_caller_assign_fail(struct gsm_trans *trans)
+{
+ osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_BSS_ASSIGN_FAIL, NULL);
+}
+
+/* BSS indicated that the channel has been released. */
+void vgcs_vbs_clear_req(struct vgcs_bss *bss, const struct ran_msg *ran_msg)
+{
+ osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_CLOSE, (void *)ran_msg);
+}
+
+/* BSS indicated that the channel has been released. */
+void vgcs_vbs_clear_cpl(struct vgcs_bss *bss, const struct ran_msg *ran_msg)
+{
+ osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_RELEASED, (void *)ran_msg);
+}
+
+/*
+ * Cell resource state machine - handles all "resource control" instances
+ */
+
+static const struct value_string vgcs_cell_fsm_event_names[] = {
+ OSMO_VALUE_STRING(VGCS_CELL_EV_RTP_STREAM_GONE),
+ OSMO_VALUE_STRING(VGCS_CELL_EV_RTP_STREAM_ADDR_AVAILABLE),
+ OSMO_VALUE_STRING(VGCS_CELL_EV_RTP_STREAM_ESTABLISHED),
+ OSMO_VALUE_STRING(VGCS_CELL_EV_ASSIGN),
+ OSMO_VALUE_STRING(VGCS_CELL_EV_ASSIGN_RES),
+ OSMO_VALUE_STRING(VGCS_CELL_EV_ASSIGN_FAIL),
+ OSMO_VALUE_STRING(VGCS_CELL_EV_CLEAR),
+ OSMO_VALUE_STRING(VGCS_CELL_EV_CLOSE),
+ OSMO_VALUE_STRING(VGCS_CELL_EV_RELEASED),
+ { }
+};
+
+static void cell_destroy(struct vgcs_bss_cell *cell);
+
+/* Clear the connection towards BSS.
+ * Relations to the BSS and transaction is removed. */
+static void cell_clear(struct vgcs_bss_cell *cell, uint8_t cause)
+{
+ struct ran_msg ran_msg;
+
+ /* Must detach us from BSS. */
+ if (cell->bss) {
+ /* Remove pointer to talking channel. */
+ if (cell->bss->trans && cell->bss->trans->gcc.uplink_cell == cell)
+ cell->bss->trans->gcc.uplink_cell = NULL;
+ llist_del(&cell->list_bss);
+ cell->bss = NULL;
+ }
+
+ /* Change state. */
+ if (cell->fi->state != VGCS_CELL_ST_RELEASE)
+ osmo_fsm_inst_state_chg(cell->fi, VGCS_CELL_ST_RELEASE, 0, 0);
+
+ /* If there is no event to wait for, we can just destroy. */
+ if (!cell->conn && !cell->rtps) {
+ cell_destroy(cell);
+ return;
+ }
+
+ /* Send Clear Command to BSS. */
+ if (cell->conn) {
+ ran_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_CLEAR_COMMAND,
+ .clear_command = {
+ .gsm0808_cause = cause,
+ },
+ };
+ LOG_CELL(cell, LOGL_DEBUG, "Sending CLEAR COMMAND for call controling channel.\n");
+ ran_encode_and_send(cell->fi, &ran_msg, cell->conn, false);
+ }
+
+ /* Clear RTP stream. This may trigger VGCS_CELL_EV_RTP_STREAM_GONE within this release function. */
+ if (cell->rtps)
+ rtp_stream_release(cell->rtps);
+}
+
+/* When finally the BSS connection is released. (CLEAR COMPLETE response)
+ * Relations to the BSS and transaction is removed, if not already. */
+static void cell_destroy(struct vgcs_bss_cell *cell)
+{
+ struct vgcs_mgw_ep *mgw;
+
+ /* close RAN conn */
+ if (cell->conn) {
+ cell->conn->vgcs.cell = NULL;
+ ran_conn_close(cell->conn);
+ cell->conn = NULL;
+ }
+
+ /* Detach from BSS now. Check, to prevent race condition. */
+ if (cell->bss) {
+ /* Remove pointer to talking channel. */
+ if (cell->bss->trans && cell->bss->trans->gcc.uplink_cell == cell)
+ cell->bss->trans->gcc.uplink_cell = NULL;
+ llist_del(&cell->list_bss);
+ cell->bss = NULL;
+ }
+
+ /* Detach from MGW now. Check, to prevent race condition. */
+ if (cell->mgw) {
+ mgw = cell->mgw;
+ llist_del(&cell->list_mgw);
+ cell->mgw = NULL;
+ /* Destroy MGW endpoint, if list is empty. */
+ if (llist_empty(&mgw->cell_list))
+ osmo_fsm_inst_dispatch(mgw->fi, VGCS_MGW_EP_EV_CLEAR, NULL);
+ }
+
+ LOG_CELL(cell, LOGL_DEBUG, "Detroy connection to cell.\n");
+
+ /* Free FSM. (should be allocated) */
+ osmo_fsm_inst_state_chg(cell->fi, VGCS_CELL_ST_NULL, 0, 0);
+ osmo_fsm_inst_term(cell->fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+static void vgcs_cell_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct vgcs_bss_cell *cell = fi->priv;
+ const struct codec_mapping *cm;
+ int rc;
+
+ switch (event) {
+ case VGCS_CELL_EV_ASSIGN:
+ LOG_CELL(cell, LOGL_DEBUG, "Received assignment from BSS controling process.\n");
+ /* Allocate rtps stream. */
+ cell->rtps = rtp_stream_alloc(cell->fi, VGCS_CELL_EV_RTP_STREAM_GONE,
+ VGCS_CELL_EV_RTP_STREAM_ADDR_AVAILABLE,
+ VGCS_CELL_EV_RTP_STREAM_ESTABLISHED, RTP_TO_RAN, cell->call_id,
+ NULL);
+ if (!cell->rtps) {
+ LOG_CELL(cell, LOGL_DEBUG, "Failed to allocate RTP stream, cannot continue.\n");
+ cell_destroy(cell);
+ break;
+ }
+ /* Hard coded codec: GSM V1 */
+ cm = codec_mapping_by_gsm0808_speech_codec_type(GSM0808_SCT_FR1);
+ rtp_stream_set_one_codec(cell->rtps, &cm->sdp);
+ /* Set initial mode. */
+ rtp_stream_set_mode(cell->rtps, MGCP_CONN_RECV_ONLY);
+ /* Commit RTP stream. */
+ if (!cell->bss && !cell->bss->trans) {
+ LOG_CELL(cell, LOGL_DEBUG, "No transaction, cannot continue.\n");
+ cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC);
+ break;
+ }
+ if (!cell->mgw && !cell->mgw->mgw_ep) {
+ LOG_CELL(cell, LOGL_DEBUG, "No MGW endpoint, cannot continue.\n");
+ cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC);
+ break;
+ }
+ rc = rtp_stream_ensure_ci(cell->rtps, cell->mgw->mgw_ep);
+ if (rc < 0) {
+ LOG_CELL(cell, LOGL_DEBUG, "Failed to trigger RTP stream CI.\n");
+ cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC);
+ break;
+ }
+ /* Change state. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CELL_ST_ASSIGNMENT, 0, 0);
+ break;
+ case VGCS_CELL_EV_CLEAR:
+ /* The calling user process requested clearing of VGCS/VBS call. */
+ LOG_CELL(cell, LOGL_DEBUG, "Received clearing from BSS controling process.\n");
+ cell_clear(cell, GSM0808_CAUSE_CALL_CONTROL);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_cell_fsm_assignment(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct vgcs_bss_cell *cell = fi->priv;
+ struct ran_msg *rx_ran_msg = data;
+ struct ran_msg tx_ran_msg;
+ struct osmo_sockaddr_str ss;
+ const struct codec_mapping *cm;
+ struct vgcs_bss *bss;
+ int rc;
+
+ switch (event) {
+ case VGCS_CELL_EV_RTP_STREAM_GONE:
+ /* The RTP stream failed. */
+ LOG_CELL(cell, LOGL_ERROR, "RTP stream of MGW failed.\n");
+ cell->rtps = NULL;
+ goto channel_fail;
+ break;
+ case VGCS_CELL_EV_RTP_STREAM_ADDR_AVAILABLE:
+ /* The RTP stream sends its peer. */
+ if (!osmo_sockaddr_str_is_nonzero(&cell->rtps->local)) {
+ LOG_CELL(cell, LOGL_ERROR, "Invalid RTP address received from MGW: " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&cell->rtps->local));
+ goto channel_fail;
+ }
+ LOG_CELL(cell, LOGL_DEBUG,
+ "MGW endpoint's RTP address available for the CI %s: " OSMO_SOCKADDR_STR_FMT " (osmux=%s:%d)\n",
+ rtp_direction_name(cell->rtps->dir), OSMO_SOCKADDR_STR_FMT_ARGS(&cell->rtps->local),
+ cell->rtps->use_osmux ? "yes" : "no", cell->rtps->local_osmux_cid);
+ /* Send VGCS/VBS ASSIGNMENT REQUEST to BSS */
+ LOG_CELL(cell, LOGL_DEBUG, "Sending VGCS/VBS ASSIGNMENT REQUEST towards BSS.\n");
+ tx_ran_msg = (struct ran_msg) {
+ .msg_type = RAN_MSG_VGCS_VBS_ASSIGN_REQ,
+ .vgcs_vbs_assign_req = {
+ /* For now we support GSM/FR V1 only. This shall be supported by all MS. */
+ .channel_type = {
+ .ch_indctr = GSM0808_CHAN_SPEECH,
+ .ch_rate_type = GSM0808_SPEECH_FULL_BM,
+ .perm_spch_len = 1,
+ .perm_spch[0] = GSM0808_PERM_FR1,
+ },
+ /* For now we want a channel without any delay. */
+ .ass_req = GSM0808_ASRQ_IMMEDIATE,
+ .callref = {
+ .sf = (cell->trans_type == TRANS_GCC),
+ },
+ /* We need to identify the cell only. */
+ .cell_identifier = {
+ .id_discr = CELL_IDENT_CI,
+ .id.ci = cell->cell_id,
+ },
+ .aoip_transport_layer_present = true,
+ .call_id_present = true,
+ .call_id = cell->call_id,
+ .codec_list_present = true,
+ .codec_list_msc_preferred = {
+ .len = 1,
+ .codec[0] = {
+ .fi = 1,
+ .type = GSM0808_SCT_FR1,
+ .cfg = 0,
+ },
+ },
+ },
+ };
+ osmo_store32be_ext(cell->callref >> 3, &tx_ran_msg.vgcs_vbs_assign_req.callref.call_ref_hi, 3);
+ tx_ran_msg.vgcs_vbs_assign_req.callref.call_ref_lo = cell->callref & 0x7;
+ osmo_sockaddr_str_to_sockaddr(&cell->rtps->local, &tx_ran_msg.vgcs_vbs_assign_req.aoip_transport_layer);
+ /* First message, so we must set "initial" to "true". */
+ ran_encode_and_send(fi, &tx_ran_msg, cell->conn, true);
+ break;
+ case VGCS_CELL_EV_RTP_STREAM_ESTABLISHED:
+ /* The RTP stream established. */
+ LOG_CELL(cell, LOGL_DEBUG, "RTP stream is established.\n");
+ break;
+ case VGCS_CELL_EV_ASSIGN_RES:
+ /* Receive VGCS/VBS ASSIGNMENT RESULT from BSS. */
+ LOG_CELL(cell, LOGL_DEBUG, "Received VGCS/VBS ASSIGNMENT RESULT from BSS.\n");
+ cell->assigned = true;
+ if (!rx_ran_msg->vgcs_vbs_assign_res.aoip_transport_layer_present
+ && !rx_ran_msg->vgcs_vbs_assign_res.codec_present
+ && !rx_ran_msg->vgcs_vbs_assign_res.call_id_present) {
+ LOG_CELL(cell, LOGL_ERROR, "Mandatory IEs missing.\n");
+ goto channel_fail;
+ }
+ /* Send remote peer to RTP stream. */
+ if (osmo_sockaddr_str_from_sockaddr(&ss, &rx_ran_msg->vgcs_vbs_assign_res.aoip_transport_layer)) {
+ LOG_CELL(cell, LOGL_ERROR, "Cannot RTP-CONNECT, invalid RTP IP:port in incoming MNCC "
+ "message\n");
+ goto channel_fail;
+ }
+ rtp_stream_set_remote_addr(cell->rtps, &ss);
+ /* Send remote codec to RTP stream. */
+ cm = codec_mapping_by_gsm0808_speech_codec_type(rx_ran_msg->vgcs_vbs_assign_res.codec_msc_chosen.type);
+ if (!cm) {
+ LOG_CELL(cell, LOGL_ERROR, "Chosen codec by BSC is not supported by MSC.\n");
+ goto channel_fail;
+ }
+ rtp_stream_set_one_codec(cell->rtps, &cm->sdp);
+ /* Set listening mode. */
+ rtp_stream_set_mode(cell->rtps, MGCP_CONN_SEND_ONLY);
+ /* Commit RTP stream. */
+ rc = rtp_stream_commit(cell->rtps);
+ if (rc < 0) {
+ LOG_CELL(cell, LOGL_ERROR, "Failed to commit parameters to RTP stream.\n");
+ goto channel_fail;
+ }
+ /* Change state. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CELL_ST_ACTIVE, 0, 0);
+ /* Notify BSS FSM about channel activation. */
+ if (cell->bss)
+ osmo_fsm_inst_dispatch(cell->bss->fi, VGCS_BSS_EV_ACTIVE_OR_FAIL, NULL);
+ break;
+ case VGCS_CELL_EV_ASSIGN_FAIL:
+ /* Received VGCS/VBS ASSIGNMENT FAILURE from BSS. */
+ LOG_CELL(cell, LOGL_NOTICE, "Received VGCS/VBS ASSIGNMENT FAILURE from BSS.\n");
+channel_fail:
+ bss = cell->bss;
+ /* Remove cell. */
+ tx_ran_msg = (struct ran_msg){
+ .msg_type = RAN_MSG_CLEAR_COMMAND,
+ .clear_command = {
+ .gsm0808_cause = GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC,
+ },
+ };
+ ran_encode_and_send(bss->fi, &tx_ran_msg, cell->conn, false);
+ cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC);
+ /* Notify BSS FSM about channel failure. */
+ if (bss)
+ osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_ACTIVE_OR_FAIL, NULL);
+ break;
+ case VGCS_CELL_EV_CLEAR:
+ /* The calling user process requested clearing of VGCS/VBS call. */
+ LOG_CELL(cell, LOGL_DEBUG, "Received clearing from BSS controling process.\n");
+ cell_clear(cell, GSM0808_CAUSE_CALL_CONTROL);
+ break;
+ case VGCS_CELL_EV_CLOSE:
+ /* The SCCP connection from the MSC has been closed. */
+ LOG_CELL(cell, LOGL_NOTICE, "Received SCCP connecting closing from MSC.\n");
+ if (cell->conn) {
+ cell->conn->vgcs.bss = NULL;
+ cell->conn = NULL;
+ }
+ cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_cell_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct vgcs_bss_cell *cell = fi->priv;
+
+ switch (event) {
+ case VGCS_CELL_EV_RTP_STREAM_GONE:
+ /* The RTP stream failed. */
+ LOG_CELL(cell, LOGL_ERROR, "RTP stream of MGW failed.\n");
+ cell->rtps = NULL;
+ cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC);
+ break;
+ case VGCS_CELL_EV_RTP_STREAM_ESTABLISHED:
+ /* The RTP stream established. */
+ LOG_CELL(cell, LOGL_DEBUG, "RTP stream is established.\n");
+ break;
+ case VGCS_CELL_EV_CLEAR:
+ /* The calling user process requested clearing of VGCS/VBS call. */
+ LOG_CELL(cell, LOGL_DEBUG, "Received clearing from BSS controling process.\n");
+ cell_clear(cell, GSM0808_CAUSE_CALL_CONTROL);
+ break;
+ case VGCS_CELL_EV_CLOSE:
+ /* The SCCP connection from the MSC has been closed. */
+ LOG_CELL(cell, LOGL_NOTICE, "Received SCCP connecting closing from MSC.\n");
+ if (cell->conn) {
+ cell->conn->vgcs.bss = NULL;
+ cell->conn = NULL;
+ }
+ cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_cell_fsm_release(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct vgcs_bss_cell *cell = fi->priv;
+
+ switch (event) {
+ case VGCS_CELL_EV_RTP_STREAM_GONE:
+ /* The RTP stream gone. */
+ LOG_CELL(cell, LOGL_ERROR, "RTP stream gone.\n");
+ cell->rtps = NULL;
+ /* Wait for RAN conn. */
+ if (cell->conn)
+ break;
+ cell_destroy(cell);
+ break;
+ case VGCS_CELL_EV_CLEAR:
+ case VGCS_CELL_EV_RELEASED:
+ if (event == VGCS_CELL_EV_CLEAR) {
+ /* The SCCP connection from the MSC has been closed while waiting for CLEAR COMPLETE. */
+ LOG_CELL(cell, LOGL_NOTICE, "Received SCCP closing collision.\n");
+ } else
+ LOG_CELL(cell, LOGL_DEBUG, "Received CLEAR COMPLETE from BSS, we are done!\n");
+ /* Wait for RTP stream. */
+ if (cell->rtps) {
+ /* close RAN conn */
+ if (cell->conn) {
+ cell->conn->vgcs.cell = NULL;
+ ran_conn_close(cell->conn);
+ cell->conn = NULL;
+ }
+ break;
+ }
+ cell_destroy(cell);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static const struct osmo_fsm_state vgcs_cell_fsm_states[] = {
+ [VGCS_CELL_ST_NULL] = {
+ .name = "NULL",
+ .in_event_mask = S(VGCS_CELL_EV_ASSIGN) |
+ S(VGCS_CELL_EV_CLEAR),
+ .out_state_mask = S(VGCS_CELL_ST_ASSIGNMENT),
+ .action = vgcs_cell_fsm_null,
+ },
+ [VGCS_CELL_ST_ASSIGNMENT] = {
+ .name = "ASSIGNMENT Sent",
+ .in_event_mask = S(VGCS_CELL_EV_RTP_STREAM_GONE) |
+ S(VGCS_CELL_EV_RTP_STREAM_ADDR_AVAILABLE) |
+ S(VGCS_CELL_EV_RTP_STREAM_ESTABLISHED) |
+ S(VGCS_CELL_EV_ASSIGN_RES) |
+ S(VGCS_CELL_EV_ASSIGN_FAIL) |
+ S(VGCS_CELL_EV_CLEAR) |
+ S(VGCS_CELL_EV_CLOSE),
+ .out_state_mask = S(VGCS_CELL_ST_ACTIVE) |
+ S(VGCS_CELL_ST_RELEASE),
+ .action = vgcs_cell_fsm_assignment,
+ },
+ [VGCS_CELL_ST_ACTIVE] = {
+ .name = "VGCS/VBS channel active",
+ .in_event_mask = S(VGCS_CELL_EV_RTP_STREAM_GONE) |
+ S(VGCS_CELL_EV_RTP_STREAM_ESTABLISHED) |
+ S(VGCS_CELL_EV_CLEAR) |
+ S(VGCS_CELL_EV_CLOSE),
+ .out_state_mask = S(VGCS_CELL_ST_RELEASE),
+ .action = vgcs_cell_fsm_active,
+ },
+ [VGCS_CELL_ST_RELEASE] = {
+ .name = "Releasing VGCS/VBS channel",
+ .in_event_mask = S(VGCS_CELL_EV_RTP_STREAM_GONE) |
+ S(VGCS_CELL_EV_CLEAR) |
+ S(VGCS_CELL_EV_RELEASED),
+ .out_state_mask = S(VGCS_CELL_ST_NULL),
+ .action = vgcs_cell_fsm_release,
+ },
+};
+
+static struct osmo_fsm vgcs_cell_fsm = {
+ .name = "vgcs_cell",
+ .states = vgcs_cell_fsm_states,
+ .num_states = ARRAY_SIZE(vgcs_cell_fsm_states),
+ .log_subsys = DASCI,
+ .event_names = vgcs_cell_fsm_event_names,
+};
+
+/* The BSS accepts VGCS/VBS channel assignment. */
+void vgcs_vbs_assign_result(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg)
+{
+ osmo_fsm_inst_dispatch(cell->fi, VGCS_CELL_EV_ASSIGN_RES, (void *)ran_msg);
+}
+
+/* The BSS refuses VGCS/VBS channel assignment. */
+void vgcs_vbs_assign_fail(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg)
+{
+ osmo_fsm_inst_dispatch(cell->fi, VGCS_CELL_EV_ASSIGN_FAIL, (void *)ran_msg);
+}
+
+/* BSS indicated that the channel has been released. */
+void vgcs_vbs_clear_req_channel(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg)
+{
+ LOG_CELL(cell, LOGL_DEBUG, "Received CLEAR REQUEST for resource controling channel from BSS.\n");
+ osmo_fsm_inst_dispatch(cell->fi, VGCS_CELL_EV_CLOSE, (void *)ran_msg);
+}
+
+/* BSS confirms the release of channel. */
+void vgcs_vbs_clear_cpl_channel(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg)
+{
+ LOG_CELL(cell, LOGL_DEBUG, "Received CLEAR COMPLETE for resource controling channel from BSS.\n");
+ osmo_fsm_inst_dispatch(cell->fi, VGCS_CELL_EV_RELEASED, (void *)ran_msg);
+}
+
+/*
+ * MGW endpoint FSM
+ */
+
+static const struct value_string vgcs_mgw_ep_fsm_event_names[] = {
+ OSMO_VALUE_STRING(VGCS_MGW_EP_EV_FREE),
+ OSMO_VALUE_STRING(VGCS_MGW_EP_EV_CLEAR),
+ { }
+};
+
+static void vgcs_mgw_ep_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct vgcs_mgw_ep *mgw = fi->priv;
+ struct vgcs_bss_cell *cell, *cell2;
+ struct mgcp_client *mgcp_client;
+
+ switch (event) {
+ case VGCS_MGW_EP_EV_FREE:
+ LOGP(DASCI, LOGL_DEBUG, "MGW connection closed, removing all cell instances.\n");
+ llist_for_each_entry_safe(cell, cell2, &mgw->cell_list, list_mgw) {
+ if (cell->rtps)
+ cell->rtps->ci = NULL;
+ llist_del(&cell->list_mgw);
+ cell->mgw = NULL;
+ }
+ /* Put MGCP client back into MGW pool. */
+ mgcp_client = osmo_mgcpc_ep_client(mgw->mgw_ep);
+ mgcp_client_pool_put(mgcp_client);
+ /* Destroy this instance. */
+ osmo_fsm_inst_term_children(fi, OSMO_FSM_TERM_PARENT, NULL);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ break;
+ case VGCS_MGW_EP_EV_CLEAR:
+ if (!llist_empty(&mgw->cell_list))
+ break;
+ LOGP(DASCI, LOGL_DEBUG, "Cell list of MGW instance is now empty, dropping.\n");
+ /* Destroy this instance. */
+ osmo_fsm_inst_term_children(fi, OSMO_FSM_TERM_PARENT, NULL);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static const struct osmo_fsm_state vgcs_mgw_ep_fsm_states[] = {
+ [VGCS_MGW_EP_ST_NULL] = {
+ .name = "NULL",
+ .out_state_mask = S(VGCS_MGW_EP_ST_ACTIVE),
+ },
+ [VGCS_MGW_EP_ST_ACTIVE] = {
+ .name = "MGW endpoint allocated",
+ .in_event_mask = S(VGCS_MGW_EP_EV_FREE) |
+ S(VGCS_MGW_EP_EV_CLEAR),
+ .out_state_mask = S(VGCS_MGW_EP_ST_NULL),
+ .action = vgcs_mgw_ep_fsm_active,
+ },
+};
+
+static struct osmo_fsm vgcs_mgw_ep_fsm = {
+ .name = "vgcs_mgw_ep",
+ .states = vgcs_mgw_ep_fsm_states,
+ .num_states = ARRAY_SIZE(vgcs_mgw_ep_fsm_states),
+ .log_subsys = DASCI,
+ .event_names = vgcs_mgw_ep_fsm_event_names,
+};
diff --git a/src/libmsc/transaction.c b/src/libmsc/transaction.c
index a3a9ef1..89f77e5 100644
--- a/src/libmsc/transaction.c
+++ b/src/libmsc/transaction.c
@@ -29,6 +29,7 @@
#include <osmocom/msc/msub.h>
#include <osmocom/msc/paging.h>
#include <osmocom/msc/silent_call.h>
+#include <osmocom/msc/msc_vgcs.h>
void *tall_trans_ctx;
@@ -241,6 +242,14 @@
LOG_TRANS(trans, LOGL_DEBUG, "Freeing transaction\n");
switch (trans->type) {
+ case TRANS_GCC:
+ gsm44068_bcc_gcc_trans_free(trans);
+ usage_token = MSC_A_USE_GCC;
+ break;
+ case TRANS_BCC:
+ gsm44068_bcc_gcc_trans_free(trans);
+ usage_token = MSC_A_USE_BCC;
+ break;
case TRANS_CC:
_gsm48_cc_trans_free(trans);
usage_token = MSC_A_USE_CC;
@@ -392,6 +401,12 @@
trans_type_name(trans->type), gsm48_cc_state_name(trans->cc.state));
return namebuf;
+ case TRANS_GCC:
+ case TRANS_BCC:
+ snprintf(namebuf, sizeof(namebuf), "%s:%s",
+ trans_type_name(trans->type), gsm44068_group_id_string(trans->callref));
+ return namebuf;
+
default:
return trans_type_name(trans->type);
}
--
To view, visit https://gerrit.osmocom.org/c/osmo-msc/+/33511
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: osmo-msc
Gerrit-Branch: master
Gerrit-Change-Id: I9947403fde8212b66758104443c60aaacc8b1e7b
Gerrit-Change-Number: 33511
Gerrit-PatchSet: 18
Gerrit-Owner: jolly <andreas(a)eversberg.eu>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-CC: dexter <pmaier(a)sysmocom.de>
Gerrit-CC: neels <nhofmeyr(a)sysmocom.de>
Gerrit-MessageType: merged
1
0
17 Jul '23
Attention is currently required from: jolly, neels, dexter.
laforge has posted comments on this change. ( https://gerrit.osmocom.org/c/osmo-msc/+/33511 )
Change subject: ASCI: Add call control for VGCS/VBS
......................................................................
Patch Set 17: Code-Review+2
--
To view, visit https://gerrit.osmocom.org/c/osmo-msc/+/33511
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: osmo-msc
Gerrit-Branch: master
Gerrit-Change-Id: I9947403fde8212b66758104443c60aaacc8b1e7b
Gerrit-Change-Number: 33511
Gerrit-PatchSet: 17
Gerrit-Owner: jolly <andreas(a)eversberg.eu>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-CC: dexter <pmaier(a)sysmocom.de>
Gerrit-CC: neels <nhofmeyr(a)sysmocom.de>
Gerrit-Attention: jolly <andreas(a)eversberg.eu>
Gerrit-Attention: neels <nhofmeyr(a)sysmocom.de>
Gerrit-Attention: dexter <pmaier(a)sysmocom.de>
Gerrit-Comment-Date: Mon, 17 Jul 2023 12:29:11 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
Gerrit-MessageType: comment
1
0
[S] Change in osmo-remsim[master]: src/rspro: regenerate to fix misleading indents
by pespin 17 Jul '23
by pespin 17 Jul '23
17 Jul '23
pespin has posted comments on this change. ( https://gerrit.osmocom.org/c/osmo-remsim/+/33733 )
Change subject: src/rspro: regenerate to fix misleading indents
......................................................................
Patch Set 2:
(1 comment)
Patchset:
PS2:
> Please add to the commit description the as1nc version (repo + commit hash) you used to generate the […]
Too late, already merged. Still please leave the commit reference here in gerrit.
--
To view, visit https://gerrit.osmocom.org/c/osmo-remsim/+/33733
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: osmo-remsim
Gerrit-Branch: master
Gerrit-Change-Id: Icc2e8da7232a93b04ac8f23800380ca9317bd60f
Gerrit-Change-Number: 33733
Gerrit-PatchSet: 2
Gerrit-Owner: osmith <osmith(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-Comment-Date: Mon, 17 Jul 2023 12:28:57 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: pespin <pespin(a)sysmocom.de>
Gerrit-MessageType: comment
1
0
[S] Change in osmo-ci[master]: jobs/gerrit-verifications: disable lint for asn1c
by laforge 17 Jul '23
by laforge 17 Jul '23
17 Jul '23
laforge has submitted this change. ( https://gerrit.osmocom.org/c/osmo-ci/+/33764 )
Change subject: jobs/gerrit-verifications: disable lint for asn1c
......................................................................
jobs/gerrit-verifications: disable lint for asn1c
The asn1c code does not follow our coding guidelines, and in the
interest of keeping it closer to upstream it does not make sense to
reformat it.
Change-Id: Iae97d8997b576e43c9a73dfcc61a9260f875310f
---
M jobs/gerrit-verifications.yml
1 file changed, 14 insertions(+), 0 deletions(-)
Approvals:
Jenkins Builder: Verified
pespin: Looks good to me, but someone else must approve
laforge: Looks good to me, approved
diff --git a/jobs/gerrit-verifications.yml b/jobs/gerrit-verifications.yml
index c41d2a3..7b6c1f8 100644
--- a/jobs/gerrit-verifications.yml
+++ b/jobs/gerrit-verifications.yml
@@ -98,6 +98,7 @@
repos:
- asn1c:
pipeline_binpkgs: false
+ pipeline_lint: false
- cellmgr-ng
--
To view, visit https://gerrit.osmocom.org/c/osmo-ci/+/33764
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: osmo-ci
Gerrit-Branch: master
Gerrit-Change-Id: Iae97d8997b576e43c9a73dfcc61a9260f875310f
Gerrit-Change-Number: 33764
Gerrit-PatchSet: 1
Gerrit-Owner: osmith <osmith(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-MessageType: merged
1
0
[S] Change in osmo-ci[master]: jobs/gerrit-verifications: disable lint for asn1c
by laforge 17 Jul '23
by laforge 17 Jul '23
17 Jul '23
Attention is currently required from: osmith.
laforge has posted comments on this change. ( https://gerrit.osmocom.org/c/osmo-ci/+/33764 )
Change subject: jobs/gerrit-verifications: disable lint for asn1c
......................................................................
Patch Set 1: Code-Review+2
--
To view, visit https://gerrit.osmocom.org/c/osmo-ci/+/33764
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: osmo-ci
Gerrit-Branch: master
Gerrit-Change-Id: Iae97d8997b576e43c9a73dfcc61a9260f875310f
Gerrit-Change-Number: 33764
Gerrit-PatchSet: 1
Gerrit-Owner: osmith <osmith(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-Attention: osmith <osmith(a)sysmocom.de>
Gerrit-Comment-Date: Mon, 17 Jul 2023 12:28:44 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
Gerrit-MessageType: comment
1
0
[S] Change in osmo-remsim[master]: src/rspro: regenerate to fix misleading indents
by pespin 17 Jul '23
by pespin 17 Jul '23
17 Jul '23
pespin has posted comments on this change. ( https://gerrit.osmocom.org/c/osmo-remsim/+/33733 )
Change subject: src/rspro: regenerate to fix misleading indents
......................................................................
Patch Set 2:
(1 comment)
Patchset:
PS2:
Please add to the commit description the as1nc version (repo + commit hash) you used to generate the new commit.
--
To view, visit https://gerrit.osmocom.org/c/osmo-remsim/+/33733
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings
Gerrit-Project: osmo-remsim
Gerrit-Branch: master
Gerrit-Change-Id: Icc2e8da7232a93b04ac8f23800380ca9317bd60f
Gerrit-Change-Number: 33733
Gerrit-PatchSet: 2
Gerrit-Owner: osmith <osmith(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-Comment-Date: Mon, 17 Jul 2023 12:28:27 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Gerrit-MessageType: comment
1
0