452 lines
14 KiB
C
452 lines
14 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(" -D --dial-hint xxxx | xxxx-yyyy\n");
|
|
printf(" The given one or multpiple hits for numbers with a fixed length. This\n");
|
|
printf(" can be a single number or a range of number. If a range is given, xxxx\n");
|
|
printf(" and yyyy must have equal length. If one of the given numbers are dialed,\n");
|
|
printf(" the en-block dialing is complete and there is no need to wait for the\n");
|
|
printf(" timeout.\n");
|
|
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('D', "dial-hint", 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 'D':
|
|
rc = add_dial_hint(argv[argi]);
|
|
if (rc < 0) {
|
|
fprintf(stderr, "Given dial hint '%s' is not valid, please use -h for help.\n", argv[argi]);
|
|
return rc;
|
|
}
|
|
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 work;
|
|
double timeout;
|
|
/* handle all handlers until done */
|
|
do {
|
|
work = 0;
|
|
work |= osmo_cc_handle();
|
|
} while (work);
|
|
/* handle all timers
|
|
* timeout is 0, if there was an event
|
|
* -> handle FDs without waiting, continue this loop
|
|
* timeout is not 0, if there was no event
|
|
* -> wait until FD or timeout
|
|
*/
|
|
timeout = process_timer();
|
|
/* wait for FD event until given timeout */
|
|
work = osmo_fd_select(timeout);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
purge_dial_hints();
|
|
|
|
options_free();
|
|
|
|
/* exit fm */
|
|
fm_exit();
|
|
|
|
return 0;
|
|
}
|
|
|