/* osmo-cc-pstn-endpoint main * * (C) 2020 by Andreas Eversberg * 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 . */ #include #include #include #include #include #include #include #include #include #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 sweden = 0; 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 [--clip --clip-date] [--recall] []\n", app); } static void print_help() { /* - - */ printf(" -h --help\n"); printf(" This help\n"); printf(" --config [~/]\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 \n"); printf(" Abstract UNIX socket that provides layer 1 connection to a PSTN device\n"); printf(" -n --name \n"); printf(" Give name of this interface. It will be sent in each call towards\n"); printf(" -I --subscriber \n"); printf(" What caller ID to send on calls made from terminal. (default = '%s')\n", subscribers[0]); printf(" -I -R -I -R ...\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 \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 | \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 \n"); printf(" Cadenced ringing for incoming call. (default = %d)\n", ringing_types_incoming[0]); printf(" --ringing-type-hold \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(" --sweden\n"); printf(" Translate pulses from Swedish rotary phone into digits. One pulse is\n"); printf(" digit '0', two pulses are digit '1', and so on.\n"); printf(" -r --realtime \n"); printf(" Set prio: 0 to disable, 99 for maximum (default = %d)\n", rt_prio); printf(" -C --cc \"\" [--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 #define OPT_SWEDEN 267 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(OPT_SWEDEN, "sweden", 0); 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 OPT_SWEDEN: sweden = 1; 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, sweden); 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; }