osmo-cc-pstn-endpoint/src/pstn/main.c

429 lines
13 KiB
C

/* osmo-cc-pstn-endpoint main
*
* (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <termios.h>
#include <sched.h>
#include "../libdebug/debug.h"
#include "../liboptions/options.h"
#include "../libg711/g711.h"
#include "pstn.h"
pstn_t *pstn_ep = NULL;
int num_kanal = 1;
#define SUBSCRIBER_MAX 16
static char law = 'a';
static int serving_location = 1; /* private network serving local user */
static const char *socketname = NULL;
static const char *name = "pstn";
static const char *subscribers[SUBSCRIBER_MAX] = { "" };
static int subscriber_num = 0;
static int tx_delay = 0;
static enum pstn_cid_method clip = CID_METHOD_NONE;
static int cid_bell = 0;
static int cid_dtmf = 0;
static int clip_date = 0;
static int enblock = 4;
static int recall = 0;
static int ringing_types_incoming[SUBSCRIBER_MAX] = { 0 };
static int ringing_type_incoming_num = 0;
static int ringing_type_hold = 0;
static enum tones_type tones_type = TONES_TYPE_GERMAN;
static int rt_prio = 1;
#define MAX_CC_ARGS 1024
static int cc_argc = 0;
static const char *cc_argv[MAX_CC_ARGS];
static void print_usage(const char *app)
{
printf("Usage: %s -s <socketname> [--clip --clip-date] [--recall] [<options>]\n", app);
}
static void print_help()
{
/* - - */
printf(" -h --help\n");
printf(" This help\n");
printf(" --config [~/]<path to config file>\n");
printf(" Give a config file to use. If it starts with '~/', path is at home dir.\n");
printf(" Each line in config file is one option, '-' or '--' must not be given!\n");
debug_print_help();
printf(" -s --socket <name>\n");
printf(" Abstract UNIX socket that provides layer 1 connection to a PSTN device\n");
printf(" -n --name <interface name>\n");
printf(" Give name of this interface. It will be sent in each call towards\n");
printf(" -I --subscriber <subscriber id>\n");
printf(" What caller ID to send on calls made from terminal. (default = '%s')\n", subscribers[0]);
printf(" -I <subscriber id 1> -R <ringing type 1> -I <subscriber id 2> -R <ringing type 2> ...\n");
printf(" You may specify multiple subscriber IDs and as much ringing types.\n");
printf(" Calls to a subscriber's ID will then ring as specified. Calls to an\n");
printf(" unspecified subscriber ID will ring as specified for first subscriber.\n");
printf(" --tx-delay <ms>\n");
printf(" Give a delay in milliseconds. This is required for modem/fax. Audio\n");
printf(" toward ISDN interface is buffered with the given delay.\n");
printf(" This feature alters dejittering strategy.\n");
printf(" --clip pulse | stop | dtas | dtas-lr [--clip-date]\n");
printf(" Enable caller ID, optionally with date info. (disabled by default)\n");
printf(" Use 'pulse' to send first ringing via pulse signal and CLIP afterwards.\n");
printf(" Use 'stop' to stop after first ring, send CLIP and continue ringing.\n");
printf(" Use 'dtas' to send CLIP with DT-AS before first ring.\n");
printf(" Use 'dtas-lr' to send CLIP as above, but with reversed polarity.\n");
printf(" --clip-date\n");
printf(" Send date+time with caller ID.\n");
printf(" --cid-bell\n");
printf(" Use Bell 202 to modulate caller ID, rather than V.23 (default).\n");
printf(" Note that both systems are so similar (and have same center frequency),\n");
printf(" so that it does not matter which one is used. (my oppinion!)\n");
printf(" --cid-dtmf D\n");
printf(" Use DTMF with given start digit 'D' for default, rather than FSK.\n");
printf(" 'D' is used in Taiwan, 'A' in Brazil...\n");
printf(" --enblock off | <seconds>\n");
printf(" Enable en-block dialing, to collect number before setup. The value\n");
printf(" given is the number of seconds to wait for more digits to be dialed.\n");
printf(" (default = %d)\n", enblock);
printf(" --recall\n");
printf(" Enable recall / call waiting. (disabled by default)\n");
printf(" -R --ringing-type-incoming <type>\n");
printf(" Cadenced ringing for incoming call. (default = %d)\n", ringing_types_incoming[0]);
printf(" --ringing-type-hold <type>\n");
printf(" Cadenced ringing for call on hold. (default = %d)\n", ringing_type_hold);
printf(" -T --local-tones german | oldgerman | american\n");
printf(" Send locally generated tones, if not provided by remote interface.\n");
printf(" DTMF may not work with old German tones, because they might interfer!\n");
printf(" --ulaw\n");
printf(" Use U-LAW for b-channel coding instead of alaw.\n");
printf(" --serving-location (see Q.931)\n");
printf(" 0 = user, 1 = private network serving local user (default=%d)\n", serving_location);
printf(" -r --realtime <prio>\n");
printf(" Set prio: 0 to disable, 99 for maximum (default = %d)\n", rt_prio);
printf(" -C --cc \"<osmo-cc arg>\" [--cc ...]\n");
printf(" Pass arguments to Osmo-CC endpoint. Use '-cc help' for description.\n");
}
static void print_recall()
{
/* - - */
printf("\n");
printf("Recall:\n");
printf("To enable recall, use --recall option. Then you can place or receive a second\n");
printf("call and switch between them.\n");
printf("If you have an active call, press hook-flash to hold the active call and make\n");
printf("a second call.\n");
printf("If you have two connected calls, press hook-flash to switch between these\n");
printf("calls.\n");
printf("If an incoming call is waiting, you will hear CW singal. Press hook-flash to\n");
printf("switch between these calls.\n");
printf("If you want to release the active call and retrieve the call on hold, hang up\n");
printf("and the phone will ring. Answer it.\n");
printf("\n");
}
#define OPT_TX_DELAY 256
#define OPT_TX_CLIP 257
#define OPT_TX_CLIP_DATE 258
#define OPT_TX_CID_BELL 259
#define OPT_TX_CID_DTMF 260
#define OPT_TX_ENBLOCK 261
#define OPT_TX_RECALL 262
#define OPT_TX_RING_I 'R'
#define OPT_TX_RING_H 264
#define OPT_ULAW 265
#define OPT_SERVING 266
static void add_options(void)
{
option_add('h', "help", 0);
option_add('v', "verbose", 1);
option_add('s', "socket", 1);
option_add('n', "name", 1);
option_add('I', "subscriber", 1);
option_add(OPT_TX_DELAY, "tx-delay", 1);
option_add(OPT_TX_CLIP, "clip", 1);
option_add(OPT_TX_CLIP_DATE, "clip-date", 0);
option_add(OPT_TX_CID_BELL, "cid-bell", 0);
option_add(OPT_TX_CID_DTMF, "cid-dtmf", 1);
option_add(OPT_TX_ENBLOCK, "enblock", 1);
option_add(OPT_TX_RECALL, "recall", 0);
option_add(OPT_TX_RING_I, "ringing-type-incoming", 1);
option_add(OPT_TX_RING_H, "ringing-type-hold", 1);
option_add('T', "local-tones", 0);
option_add(OPT_ULAW, "ulaw", 0);
option_add(OPT_SERVING, "serving-location", 1);
option_add('r', "realtime", 1);
option_add('C', "cc", 1);
}
static int handle_options(int short_option, int argi, char **argv)
{
int rc;
switch (short_option) {
case 'h':
print_usage(argv[0]);
print_help();
return 0;
case 'v':
if (!strcasecmp(argv[argi], "list")) {
debug_list_cat();
return 0;
}
rc = parse_debug_opt(argv[argi]);
if (rc < 0) {
fprintf(stderr, "Failed to parse debug option, please use -h for help.\n");
return rc;
}
break;
case 's':
socketname = options_strdup(argv[argi]);
break;
case 'n':
name = options_strdup(argv[argi]);
break;
case 'I':
if (subscriber_num == SUBSCRIBER_MAX) {
fprintf(stderr, "Cannot define more than %d subscriber Ids.\n", SUBSCRIBER_MAX);
return -EINVAL;
}
subscribers[subscriber_num++] = options_strdup(argv[argi]);
break;
case OPT_TX_DELAY:
tx_delay = atoi(argv[argi]);
break;
case OPT_TX_CLIP:
if (!strcasecmp(argv[argi], "pulse"))
clip = CID_METHOD_PULSE;
else if (!strcasecmp(argv[argi], "stop"))
clip = CID_METHOD_STOP;
else if (!strcasecmp(argv[argi], "dtas"))
clip = CID_METHOD_DTAS;
else if (!strcasecmp(argv[argi], "dtas-lr"))
clip = CID_METHOD_DTAS_LR;
else {
fprintf(stderr, "Invalid clip method!\n");
return -EINVAL;
}
break;
case OPT_TX_CLIP_DATE:
clip_date = 1;
break;
case OPT_TX_CID_BELL:
cid_bell = 1;
break;
case OPT_TX_CID_DTMF:
if (strlen(argv[argi]) != 1 || argv[argi][0] < 'A' || argv[argi][0] > 'D') {
fprintf(stderr, "Only DTMF start digits 'A', 'B', 'C', 'D' are allowed.\n");
return -EINVAL;
}
cid_dtmf = argv[argi][0];
break;
case OPT_TX_ENBLOCK:
enblock = atoi(argv[argi]);
break;
case OPT_TX_RECALL:
recall = 1;
break;
case OPT_TX_RING_I:
if (ringing_type_incoming_num == SUBSCRIBER_MAX) {
fprintf(stderr, "Cannot define more than %d ringing types.\n", SUBSCRIBER_MAX);
return -EINVAL;
}
ringing_types_incoming[ringing_type_incoming_num++] = atoi(argv[argi]);
break;
case OPT_TX_RING_H:
ringing_type_hold = atoi(argv[argi]);
break;
case 'T':
if (!strcasecmp(argv[argi], "american"))
tones_type = TONES_TYPE_AMERICAN;
else if (!strcasecmp(argv[argi], "german"))
tones_type = TONES_TYPE_GERMAN;
else if (!strcasecmp(argv[argi], "oldgerman"))
tones_type = TONES_TYPE_OLDGERMAN;
else {
fprintf(stderr, "Invalid tones type given!\n");
return -EINVAL;
}
break;
case OPT_ULAW:
law = 'u';
break;
case OPT_SERVING:
serving_location = atoi(argv[argi]);;
break;
case 'r':
rt_prio = atoi(argv[argi]);
break;
case 'C':
if (!strcasecmp(argv[argi], "help")) {
osmo_cc_help();
return 0;
}
if (cc_argc == MAX_CC_ARGS) {
fprintf(stderr, "Too many osmo-cc args!\n");
break;
}
cc_argv[cc_argc++] = options_strdup(argv[argi]);
break;
default:
return -EINVAL;
}
return 1;
}
static int quit = 0;
void sighandler(int sigset)
{
if (sigset == SIGHUP || sigset == SIGPIPE)
return;
fprintf(stderr, "\nSignal %d received.\n", sigset);
quit = 1;
}
int main(int argc, char *argv[])
{
int argi, rc;
/* init codecs */
g711_init();
cc_argv[cc_argc++] = options_strdup("remote auto");
/* handle options / config file */
add_options();
rc = options_config_file(argc, argv, "~/.osmocom/pstn/pstn.conf", handle_options);
if (rc < 0)
return 0;
argi = options_command_line(argc, argv, handle_options);
if (argi <= 0)
return argi;
/* init fm */
fm_init(0);
if (!socketname) {
fprintf(stderr, "No socket name given, use '-h' for help.\n");
goto error;
}
/* change tones to ulaw */
if (law == 'u')
isdn_tone_generate_ulaw_samples();
/* check subscribers and their ringing types */
if (subscriber_num == 0)
subscriber_num = 1;
if (ringing_type_incoming_num == 0)
ringing_type_incoming_num = 1;
if (subscriber_num != ringing_type_incoming_num) {
fprintf(stderr, "You need to specify as many ringing types as you specified subscriber IDs, use '-h' for help.\n");
goto error;
}
pstn_ep = pstn_create();
if (!pstn_ep)
goto error;
rc = pstn_init(pstn_ep, name, socketname, subscribers, subscriber_num, serving_location, tx_delay, clip, cid_bell, cid_dtmf, clip_date, enblock, recall, ringing_types_incoming, ringing_type_hold, tones_type, law);
if (rc) {
PDEBUG(DTEL, DEBUG_ERROR, "Endpoint initializing failed!\n");
goto error;
}
rc = osmo_cc_new(&pstn_ep->cc_ep, OSMO_CC_VERSION, name, OSMO_CC_LOCATION_USER, cc_message, NULL, pstn_ep, cc_argc, cc_argv);
if (rc < 0)
goto error;
/* real time priority */
if (rt_prio > 0) {
struct sched_param schedp;
int rc;
memset(&schedp, 0, sizeof(schedp));
schedp.sched_priority = rt_prio;
rc = sched_setscheduler(0, SCHED_RR, &schedp);
if (rc)
fprintf(stderr, "Error setting SCHED_RR with prio %d\n", rt_prio);
}
printf("PSTN endpoint ready, waiting for V5 application to connect...\n");
print_recall();
if (tones_type == TONES_TYPE_OLDGERMAN) {
printf("********************\n");
printf("DTMF may not work with old German dial tone, because it disturbs the DTMF tones!\n");
printf("********************\n");
}
signal(SIGINT, sighandler);
signal(SIGHUP, sighandler);
signal(SIGTERM, sighandler);
signal(SIGPIPE, sighandler);
while (!quit) {
int w;
process_timer();
pstn_work(pstn_ep);
rtp_work(pstn_ep);
do {
w = 0;
w |= osmo_cc_handle();
} while (w);
usleep(1000);
}
signal(SIGINT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
signal(SIGHUP, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
/* reset real time prio */
if (rt_prio > 0) {
struct sched_param schedp;
memset(&schedp, 0, sizeof(schedp));
schedp.sched_priority = 0;
sched_setscheduler(0, SCHED_OTHER, &schedp);
}
error:
if (pstn_ep) {
osmo_cc_delete(&pstn_ep->cc_ep);
pstn_destroy(pstn_ep);
}
options_free();
/* exit fm */
fm_exit();
return 0;
}