blob: a138eda5120ea238090c1b7d236a7d458c1df633 [file] [log] [blame]
Andreas Eversbergf8ac7342023-04-23 12:22:25 +02001/* GCR interface to VTY */
2/*
3 * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
4 * All Rights Reserved
5 *
6 * SPDX-License-Identifier: AGPL-3.0+
7 *
8 * Author: Andreas Eversberg
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Affero General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Affero General Public License for more details.
19 *
20 * You should have received a copy of the GNU Affero General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24#include <osmocom/msc/vty.h>
25#include <osmocom/msc/transaction.h>
26#include <osmocom/msc/msc_vgcs.h>
27#include <osmocom/msc/asci_vty.h>
28#include <osmocom/msc/asci_gcr.h>
29
30static struct gsm_network *gsmnet;
31
32/***********************************************************************
33 * ASCI Node
34 ***********************************************************************/
35
36#define ASCI_STR "Advanced Speech Call Items\n"
37
38static void asci_disabled(struct vty *vty)
39{
40 vty_out(vty, "%%Advanced Speech Call Items are disabled.%s", VTY_NEWLINE);
41}
42
43DEFUN(asci_call, asci_call_cmd,
44 "asci (initiate|terminate) (vgc|vbc) CALLREF",
45 ASCI_STR "Initiate a call\nTerminate a call\nVoice Group Call\nVoice Broadcast Call\nCall reference")
46{
47 struct gcr *gcr;
48 const char *error;
49
50 if (!gsmnet->asci.enable) {
51 asci_disabled(vty);
52 return CMD_WARNING;
53 }
54
55 gcr = gcr_by_group_id(gsmnet, (argv[1][1] == 'g') ? TRANS_GCC : TRANS_BCC, argv[2]);
56 if (!gcr) {
57 vty_out(vty, "%%Given call ref does not exist in GCR.%s", VTY_NEWLINE);
58 return CMD_WARNING;
59 }
60 if (argv[0][0] == 'i')
61 error = vgcs_vty_initiate(gsmnet, gcr);
62 else
63 error = vgcs_vty_terminate(gsmnet, gcr);
64 if (error) {
65 vty_out(vty, "%%%s%s", error, VTY_NEWLINE);
66 return CMD_WARNING;
67 }
68 return CMD_SUCCESS;
69}
70
71DEFUN(asci_show, asci_show_cmd,
72 "show asci calls",
73 SHOW_STR ASCI_STR "Show all Voice Group/Broadcast Calls")
74{
75 struct gsm_trans *trans;
76 const char *typestr;
77 struct vgcs_bss *bss;
78 struct vgcs_bss_cell *cell;
79
80 if (!gsmnet->asci.enable) {
81 asci_disabled(vty);
82 return CMD_WARNING;
83 }
84
85 llist_for_each_entry(trans, &gsmnet->trans_list, entry) {
86 if (trans->type == TRANS_GCC)
87 typestr = "Group";
88 else if (trans->type == TRANS_BCC)
89 typestr = "Broadcast";
90 else
91 continue;
92 vty_out(vty, "Call Reference %s (Voice %s Call).%s", gsm44068_group_id_string(trans->callref),
93 typestr, VTY_NEWLINE);
94 vty_out(vty, " Call state : %s%s", vgcs_bcc_gcc_state_name(trans->gcc.fi), VTY_NEWLINE);
95 vty_out(vty, " Uplink state: %s%s", (trans->gcc.uplink_busy) ? "busy" : "free", VTY_NEWLINE);
96 if (trans->gcc.uplink_busy)
97 vty_out(vty, " Talker : %s subscriber%s",
98 (trans->gcc.uplink_originator) ? "calling" : "other", VTY_NEWLINE);
99 llist_for_each_entry(bss, &trans->gcc.bss_list, list) {
100 vty_out(vty, " BSS %8s: listening%s%s", osmo_ss7_pointcode_print(NULL, bss->pc),
101 (trans->gcc.uplink_busy && bss == trans->gcc.uplink_bss) ? "+talking" : "",
102 VTY_NEWLINE);
103 llist_for_each_entry(cell, &bss->cell_list, list_bss) {
104 vty_out(vty, " Cell %6d: listening%s%s", cell->cell_id,
105 (trans->gcc.uplink_busy && cell == trans->gcc.uplink_cell) ? "+talking" : "",
106 VTY_NEWLINE);
107 }
108 }
109 }
110
111 return CMD_SUCCESS;
112}
113
114/***********************************************************************
115 * GCR Config Node
116 ***********************************************************************/
117
118static struct cmd_node asci_node = {
119 ASCI_NODE,
120 "%s(config-asci)# ",
121 1,
122};
123
124static struct cmd_node gcr_node = {
125 GCR_NODE,
126 "%s(config-gcr)# ",
127 1,
128};
129
130char conf_prompt[64];
131
132static struct cmd_node vgc_node = {
133 VGC_NODE,
134 conf_prompt,
135 1,
136};
137
138static struct cmd_node vbc_node = {
139 VBC_NODE,
140 conf_prompt,
141 1,
142};
143
144DEFUN(cfg_asci, cfg_asci_cmd,
145 "asci", "Enable and configure " ASCI_STR)
146{
147 vty->node = ASCI_NODE;
148 return CMD_SUCCESS;
149}
150
151DEFUN(cfg_enable_disable, cfg_enable_disable_cmd,
152 "(enable|disable)", "Enable " ASCI_STR "Disable " ASCI_STR)
153{
154 gsmnet->asci.enable = (argv[0][0] == 'e');
155 return CMD_SUCCESS;
156}
157
158DEFUN(cfg_gcr, cfg_gcr_cmd,
159 "gcr", "Configure Group Call Register")
160{
161 vty->node = GCR_NODE;
162 return CMD_SUCCESS;
163}
164
165static bool valid_group_id(const char *id, struct vty *vty)
166{
167 int i;
168
169 if (strlen(id) < 1 || strlen(id) > 8) {
170 vty_out(vty, "%%Given group ID is not valid. Use up to 8 numeric digits!%s", VTY_NEWLINE);
171 return false;
172 }
173 for (i = 0; i < strlen(id); i++) {
174 if (id[i] < '0' || id[i] > '9') {
175 vty_out(vty, "%%Given group ID is not valid. Use numeric digits only!%s", VTY_NEWLINE);
176 return false;
177 }
178 }
179
180 return true;
181}
182
183DEFUN(cfg_vgc, cfg_vgc_cmd,
184 "vgc ID", "Configure Voice Group Call\n" "Group ID")
185{
186 struct gcr *gcr;
187
188 if (!valid_group_id(argv[0], vty))
189 return CMD_WARNING;
190
191 gcr = gcr_by_group_id(gsmnet, TRANS_GCC, argv[0]);
192 if (!gcr)
193 gcr = gcr_create(gsmnet, TRANS_GCC, argv[0]);
194 if (!gcr)
195 return CMD_WARNING;
196
197 sprintf(conf_prompt, "%%s(vgc-%s)# ", gcr->group_id);
198 vty->node = VGC_NODE;
199 vty->index = gcr;
200 return CMD_SUCCESS;
201}
202
203DEFUN(cfg_no_vgc, cfg_no_vgc_cmd,
204 "no vgc ID", NO_STR "Configure Voice Group Call\n" "Group ID")
205{
206 struct gcr *gcr;
207
208 if (!valid_group_id(argv[0], vty))
209 return CMD_WARNING;
210
211 gcr = gcr_by_group_id(gsmnet, TRANS_GCC, argv[0]);
212 if (!gcr) {
213 vty_out(vty, "%%Voice group call with given group ID does not exit!%s", VTY_NEWLINE);
214 return CMD_WARNING;
215 }
216 gcr_destroy(gcr);
217
218 return CMD_SUCCESS;
219}
220
221DEFUN(cfg_vbc, cfg_vbc_cmd,
222 "vbc ID", "Configure Voice Broadcast Call\n" "Group ID")
223{
224 struct gcr *gcr;
225
226 if (!valid_group_id(argv[0], vty))
227 return CMD_WARNING;
228
229 gcr = gcr_by_group_id(gsmnet, TRANS_BCC, argv[0]);
230 if (!gcr)
231 gcr = gcr_create(gsmnet, TRANS_BCC, argv[0]);
232 if (!gcr)
233 return CMD_WARNING;
234
235 sprintf(conf_prompt, "%%s(vbc-%s)# ", gcr->group_id);
236 vty->node = VBC_NODE;
237 vty->index = gcr;
238 return CMD_SUCCESS;
239}
240
241DEFUN(cfg_no_vbc, cfg_no_vbc_cmd,
242 "no vbc ID", NO_STR "Configure Voice Broadcast Call\n" "Group ID")
243{
244 struct gcr *gcr;
245
246 if (!valid_group_id(argv[0], vty))
247 return CMD_WARNING;
248
249 gcr = gcr_by_group_id(gsmnet, TRANS_BCC, argv[0]);
250 if (!gcr) {
251 vty_out(vty, "%%Voice broadcast call with given group ID does not exit!%s", VTY_NEWLINE);
252 return CMD_WARNING;
253 }
254 gcr_destroy(gcr);
255
256 return CMD_SUCCESS;
257}
258
259DEFUN(cfg_mute, cfg_mute_cmd,
260 "mute-talker", "Mute talker's downlink")
261{
262 struct gcr *gcr = vty->index;
263
264 gcr->mute_talker = true;
265
266 return CMD_SUCCESS;
267}
268
269DEFUN(cfg_unmute, cfg_unmute_cmd,
270 "unmute-talker", "Unmute talker's downlink")
271{
272 struct gcr *gcr = vty->index;
273
274 gcr->mute_talker = false;
275
276 return CMD_SUCCESS;
277}
278
279DEFUN(cfg_timeout, cfg_timeout_cmd,
280 "timeout <1-65535>", "Set inactivity timer\n" "Timeout in seconds")
281{
282 struct gcr *gcr = vty->index;
283
284 gcr->timeout = atoi(argv[0]);
285
286 return CMD_SUCCESS;
287}
288
289DEFUN(cfg_no_timeout, cfg_no_timeout_cmd,
290 "no timeout", NO_STR "Unset inactivity timer")
291{
292 struct gcr *gcr = vty->index;
293
294 gcr->timeout = 0;
295
296 return CMD_SUCCESS;
297}
298
299#define PC_ID_STR "Point code of MSC\nCell ID of BTS"
300
301DEFUN(cfg_no_cell, cfg_no_cell_cmd,
302 "no cell POINT_CODE [<0-65535>]", NO_STR "Remove BSS/cell from current group\n" PC_ID_STR)
303{
304 struct osmo_ss7_instance *ss7 = osmo_ss7_instance_find(0);
305 struct gcr *gcr = vty->index;
306 struct gcr_bss *bss;
307 int pc = osmo_ss7_pointcode_parse(ss7, argv[0]);
308 uint16_t cell_id;
309
310 if (pc < 0 || !osmo_ss7_pc_is_valid((uint32_t)pc)) {
311 vty_out(vty, "Invalid point code (%s)%s", argv[0], VTY_NEWLINE);
312 return CMD_WARNING;
313 }
314
315 bss = gcr_find_bss(gcr, pc);
316 if (!bss) {
317 vty_out(vty, "%%Given BSS point code does not exit in list!%s", VTY_NEWLINE);
318 return CMD_WARNING;
319 }
320
321 if (argc > 1) {
322 cell_id = atoi(argv[1]);
323 if (!gcr_find_cell(bss, cell_id)) {
324 vty_out(vty, "%%Given cell does not exit in list!%s", VTY_NEWLINE);
325 return CMD_WARNING;
326 }
327
328 /* Remove cell only. Exit if there are still cells for this BSS. */
329 gcr_rm_cell(bss, cell_id);
330 if (!llist_empty(&bss->cell_list))
331 return CMD_SUCCESS;
332 }
333
334 gcr_rm_bss(gcr, pc);
335
336 return CMD_SUCCESS;
337}
338
339DEFUN(cfg_cell, cfg_cell_cmd,
340 "cell POINT_CODE <0-65535>", "Add cell to current group\n" PC_ID_STR)
341{
342 struct osmo_ss7_instance *ss7 = osmo_ss7_instance_find(0);
343 struct gcr *gcr = vty->index;
344 struct gcr_bss *bss;
345 int pc = osmo_ss7_pointcode_parse(ss7, argv[0]);
346 uint16_t cell_id = atoi(argv[1]);
347
348 if (pc < 0 || !osmo_ss7_pc_is_valid((uint32_t)pc)) {
349 vty_out(vty, "Invalid point code (%s)%s", argv[0], VTY_NEWLINE);
350 return CMD_WARNING;
351 }
352
353 bss = gcr_find_bss(gcr, pc);
354 if (!bss)
355 bss = gcr_add_bss(gcr, pc);
356 if (!bss)
357 return CMD_WARNING;
358
359 if (gcr_find_cell(bss, cell_id))
360 return CMD_SUCCESS;
361
362 gcr_add_cell(bss, cell_id);
363
364 return CMD_SUCCESS;
365}
366
367static int config_write_asci(struct vty *vty)
368{
369 struct osmo_ss7_instance *ss7 = osmo_ss7_instance_find(0);
370 struct gcr *gcr;
371 struct gcr_bss *b;
372 struct gcr_cell *c;
373
374 vty_out(vty, "asci%s", VTY_NEWLINE);
375
376 vty_out(vty, " %s%s", (gsmnet->asci.enable) ? "enable" : "disable", VTY_NEWLINE);
377
378 vty_out(vty, " gcr%s", VTY_NEWLINE);
379
380 llist_for_each_entry(gcr, &gsmnet->asci.gcr_lists, list) {
381 vty_out(vty, " %s %s%s", (gcr->trans_type == TRANS_GCC) ? "vgc" : "vbc", gcr->group_id, VTY_NEWLINE);
382 if (gcr->trans_type == TRANS_GCC) {
383 if (gcr->timeout)
384 vty_out(vty, " timeout %d%s", gcr->timeout, VTY_NEWLINE);
385 else
386 vty_out(vty, " no timeout%s", VTY_NEWLINE);
387 }
388 if (gcr->mute_talker)
389 vty_out(vty, " mute-talker%s", VTY_NEWLINE);
390 else
391 vty_out(vty, " unmute-talker%s", VTY_NEWLINE);
392 if (llist_empty(&gcr->bss_list))
393 vty_out(vty, " ! Please add cell(s) here!%s", VTY_NEWLINE);
394 llist_for_each_entry(b, &gcr->bss_list, list) {
395 llist_for_each_entry(c, &b->cell_list, list)
396 vty_out(vty, " cell %s %d%s", osmo_ss7_pointcode_print(ss7, b->pc), c->cell_id, VTY_NEWLINE);
397 }
398 }
399
400 return CMD_SUCCESS;
401}
402
403void asci_vty_init(struct gsm_network *msc_network)
404{
405 OSMO_ASSERT(gsmnet == NULL);
406 gsmnet = msc_network;
407
408 install_element_ve(&asci_show_cmd);
409 /* enable node */
410 install_element(ENABLE_NODE, &asci_call_cmd);
411 /* Config node */
412 install_element(CONFIG_NODE, &cfg_asci_cmd);
413 install_node(&asci_node, config_write_asci);
414 install_element(ASCI_NODE, &cfg_enable_disable_cmd);
415 install_element(ASCI_NODE, &cfg_gcr_cmd);
416 install_node(&gcr_node, NULL);
417 install_element(GCR_NODE, &cfg_vgc_cmd);
418 install_element(GCR_NODE, &cfg_no_vgc_cmd);
419 install_node(&vgc_node, NULL);
420 install_element(GCR_NODE, &cfg_vbc_cmd);
421 install_element(GCR_NODE, &cfg_no_vbc_cmd);
422 install_node(&vbc_node, NULL);
423 install_element(VGC_NODE, &cfg_mute_cmd);
424 install_element(VGC_NODE, &cfg_unmute_cmd);
425 install_element(VGC_NODE, &cfg_timeout_cmd);
426 install_element(VGC_NODE, &cfg_no_timeout_cmd);
427 install_element(VGC_NODE, &cfg_cell_cmd);
428 install_element(VGC_NODE, &cfg_no_cell_cmd);
429 /* Add all VGC_NODEs again for VBC_NODEs. */
430 install_element(VBC_NODE, &cfg_mute_cmd);
431 install_element(VBC_NODE, &cfg_unmute_cmd);
432 install_element(VBC_NODE, &cfg_cell_cmd);
433 install_element(VBC_NODE, &cfg_no_cell_cmd);
434}