osmo-cc-ss5-endpoint/src/ss5/ss5.c

1136 lines
34 KiB
C

/* SS5 process
*
* (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/>.
*/
#define CHAN ss5->name
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <errno.h>
#include <sys/types.h>
#include "../libdebug/debug.h"
#include "../libg711/g711.h"
#include "ss5.h"
#include "display.h"
/* names of all SS5 states */
const char *ss5_state_names[] = {
"NULL",
/* idle line */
"IDLE",
/* outgoing call */
"SEND SEIZE",
"RECV PROCEED-TO-SEND",
"SEND DIGITS",
"OUT INACTIVE",
"SEND ACKNOWLEDGE (to answer)",
"SEND ACKNOWLEDGE (to busy-flash)",
"SEND ACKNOWLEDGE (to clear-back)",
"OUT ACTIVE",
"SEND CLEAR-FORWARD",
"RECV RELEASE-GUARD",
"SEND FORWARD-TRANSFER",
/* incoming call */
"SEND PROCEED-TO-SEND",
"RECV DIGIT",
"RECV SPACE",
"IN INACTIVE",
"SEND ANSWER",
"IN ACTIVE",
"SEND BUSY-FLASH",
"SEND CLEAR-BACK",
"SEND RELEASE-GUARD",
"SEND RELEASE-GUARD (waiting)",
/* seize collision */
"DOUBLE-SEIZURE",
};
/* timers and durations */
#define SIGN_RECOGNITION_FAST 0.040 /* 40 ms for seize and proceed-to-send */
#define SIGN_RECOGNITION_NORMAL 0.125 /* 125 ms for all other signals */
#define MIN_RELEASE_GUARD 0.200 /* minimum 200 ms, in case we prevent blueboxing */
#define MAX_SEIZE 10.0
#define MAX_PROCEED_TO_SEND 4.0
#define MAX_ANSWER 10.0
#define MAX_BUSY_FLASH 10.0
#define MAX_CLEAR_BACK 10.0
#define MAX_ACKNOWLEDGE 4.0
#define MAX_CLEAR_FORWARD 10.0
#define MAX_RELEASE_GUARD 4.0
#define DUR_DOUBLE_SEIZURE 0.850 /* 850 ms to be sure the other end recognizes the double seizure */
#define DUR_FORWARD_TRANSFER 0.850 /* 850 ms forward-transfer */
#define PAUSE_BEFORE_DIALING 0.080 /* pause before dialing after cease of tone */
#define TO_DIALING 10.0 /* as defined in clause about releasing the incoming register when number is incomplete */
static struct osmo_cc_helper_audio_codecs codecs[] = {
{ "L16", 8000, 1, encode_l16, decode_l16 },
{ "PCMA", 8000, 1, g711_encode_alaw, g711_decode_alaw },
{ "PCMU", 8000, 1, g711_encode_ulaw, g711_decode_ulaw },
{ NULL, 0, 0, NULL, NULL},
};
void refresh_status(void)
{
osmo_cc_endpoint_t *ep;
ss5_endpoint_t *ss5_ep;
ss5_t *ss5;
int i;
display_status_start();
for (ep = osmo_cc_endpoint_list; ep; ep = ep->next) {
ss5_ep = ep->priv;
if (!ss5_ep->link_list)
display_status_line(ep->local_name, 0, NULL, NULL, 0);
for (i = 0, ss5 = ss5_ep->link_list; ss5; i++, ss5 = ss5->next)
display_status_line(ep->local_name, i, ss5->callerid, ss5->dialing, ss5->state);
}
display_status_end();
}
void ss5_new_state(ss5_t *ss5, enum ss5_state state)
{
PDEBUG_CHAN(DSS5, DEBUG_DEBUG, "Changing state '%s' -> '%s'\n", ss5_state_names[ss5->state], ss5_state_names[state]);
ss5->state = state;
/* must update (new state) */
refresh_status();
}
/*
* endpoints & links
*/
/* reset ss5 link, but keep states, so audio generation/processing can continue */
static void link_reset(ss5_t *ss5)
{
/* unlink callref */
ss5->cc_callref = 0;
/* stop timer */
timer_stop(&ss5->timer);
/* free session description */
if (ss5->cc_session) {
osmo_cc_free_session(ss5->cc_session);
ss5->cc_session = NULL;
ss5->codec = NULL;
}
/* reset jitter buffer */
jitter_reset(&ss5->tx_dejitter);
/* set recognition time */
set_sig_detect_duration(&ss5->dsp, SIGN_RECOGNITION_FAST, SIGN_RECOGNITION_NORMAL);
/* reset all other states */
ss5->callerid[0] = '\0';
ss5->dialing[0] = '\0';
/* must update (e.g. caller and dialing) */
refresh_status();
}
static void ss5_timeout(struct timer *timer);
static ss5_t *link_create(ss5_endpoint_t *ss5_ep, const char *ep_name, int linkid)
{
ss5_t *ss5, **ss5_p;
int rc;
ss5 = calloc(1, sizeof(*ss5));
if (!ss5) {
PDEBUG(DSS5, DEBUG_ERROR, "No memory!\n");
abort();
}
ss5->ss5_ep = ss5_ep;
ss5_p = &ss5_ep->link_list;
while (*ss5_p)
ss5_p = &((*ss5_p)->next);
*ss5_p = ss5;
/* debug name */
snprintf(ss5->name, sizeof(ss5->name) - 1, "%s/%d", ep_name, linkid);
/* init dsp instance */
dsp_init_inst(&ss5->dsp, ss5, ss5_ep->samplerate, ss5_ep->sense_db);
/* init timer */
timer_init(&ss5->timer, ss5_timeout, ss5);
/* allocate jitter buffer */
rc = jitter_create(&ss5->tx_dejitter, "tx", 8000, sizeof(sample_t), JITTER_DATA);
if (rc < 0)
abort();
/* alloc delay buffer */
if (ss5_ep->delay_ms) {
ss5->delay_length = (int)(ss5_ep->samplerate * (double)ss5_ep->delay_ms / 1000.0);
ss5->delay_buffer = calloc(ss5->delay_length, sizeof(*ss5->delay_buffer));
}
/* reset instance */
link_reset(ss5);
/* state idle */
ss5_new_state(ss5, SS5_STATE_IDLE);
PDEBUG_CHAN(DSS5, DEBUG_DEBUG, "created ss5 instance\n");
return ss5;
}
static void link_destroy(ss5_t *ss5)
{
ss5_t **ss5_p;
/* state idle */
ss5_new_state(ss5, SS5_STATE_IDLE);
/* reset instance */
link_reset(ss5);
/* exit timer */
timer_exit(&ss5->timer);
/* free jitter buffer */
jitter_destroy(&ss5->tx_dejitter);
/* free delay buffer */
if (ss5->delay_buffer) {
free(ss5->delay_buffer);
ss5->delay_buffer = NULL;
}
/* cleanup dsp instance */
dsp_cleanup_inst(&ss5->dsp);
/* detach */
ss5_p = &ss5->ss5_ep->link_list;
while (*ss5_p) {
if (*ss5_p == ss5)
break;
ss5_p = &((*ss5_p)->next);
}
*ss5_p = ss5->next;
PDEBUG_CHAN(DSS5, DEBUG_DEBUG, "destroyed ss5 instance\n");
free(ss5);
}
ss5_endpoint_t *ss5_ep_create(const char *ep_name, int links, int prevent_blueboxing, int crosstalk, int delay_ms, int comfort_noise, int suppress_disconnect, double sense_db)
{
ss5_endpoint_t *ss5_ep;
int i;
ss5_ep = calloc(1, sizeof(*ss5_ep));
if (!ss5_ep) {
PDEBUG(DSS5, DEBUG_ERROR, "No memory!\n");
abort();
}
ss5_ep->samplerate = 8000;
ss5_ep->prevent_blueboxing = prevent_blueboxing;
ss5_ep->crosstalk = crosstalk;
ss5_ep->delay_ms = delay_ms;
ss5_ep->comfort_noise = comfort_noise;
ss5_ep->suppress_disconnect = suppress_disconnect;
ss5_ep->sense_db = sense_db;
for (i = 0; i < links; i++)
link_create(ss5_ep, ep_name, i + 1);
PDEBUG(DSS5, DEBUG_DEBUG, "SS5 endpoint instance created\n");
return ss5_ep;
}
void ss5_ep_destroy(ss5_endpoint_t *ss5_ep)
{
/* destroy all calls */
while (ss5_ep->link_list)
link_destroy(ss5_ep->link_list);
free(ss5_ep);
PDEBUG(DSS5, DEBUG_DEBUG, "SS5 endpoint instance destroyed\n");
}
/*
* several messages towards CC
*/
static void reject_call(ss5_endpoint_t *ss5_ep, uint32_t callref, uint8_t isdn_cause)
{
osmo_cc_msg_t *new_msg;
/* create osmo-cc message */
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_IND);
/* cause */
osmo_cc_add_ie_cause(new_msg, OSMO_CC_LOCATION_BEYOND_INTERWORKING, isdn_cause, 0, 0);
/* send message to osmo-cc */
osmo_cc_ll_msg(&ss5_ep->cc_ep, callref, new_msg);
}
static void release_call(ss5_t *ss5, uint8_t isdn_cause)
{
osmo_cc_msg_t *new_msg;
/* create osmo-cc message */
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
/* cause */
osmo_cc_add_ie_cause(new_msg, OSMO_CC_LOCATION_BEYOND_INTERWORKING, isdn_cause, 0, 0);
/* send message to osmo-cc */
osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
}
static void disconnect_call(ss5_t *ss5, uint8_t isdn_cause)
{
osmo_cc_msg_t *new_msg;
if (ss5->ss5_ep->suppress_disconnect)
return;
/* create osmo-cc message */
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_DISC_IND);
/* progress */
osmo_cc_add_ie_progress(new_msg, OSMO_CC_CODING_ITU_T, OSMO_CC_LOCATION_BEYOND_INTERWORKING, OSMO_CC_PROGRESS_INBAND_INFO_AVAILABLE);
/* cause */
osmo_cc_add_ie_cause(new_msg, OSMO_CC_LOCATION_BEYOND_INTERWORKING, isdn_cause, 0, 0);
/* send message to osmo-cc */
osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
}
static void proceed_call(ss5_t *ss5, const char *sdp)
{
osmo_cc_msg_t *new_msg;
/* create osmo-cc message */
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_PROC_IND);
/* progress */
osmo_cc_add_ie_progress(new_msg, OSMO_CC_CODING_ITU_T, OSMO_CC_LOCATION_BEYOND_INTERWORKING, OSMO_CC_PROGRESS_INBAND_INFO_AVAILABLE);
/* sdp */
osmo_cc_add_ie_sdp(new_msg, sdp);
/* send message to osmo-cc */
osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
}
static void alert_call(ss5_t *ss5)
{
osmo_cc_msg_t *new_msg;
/* create osmo-cc message */
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_ALERT_IND);
/* send message to osmo-cc */
osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
}
static void answer_call(ss5_t *ss5)
{
osmo_cc_msg_t *new_msg;
/* create osmo-cc message */
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_CNF);
/* send message to osmo-cc */
osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
}
static void setup_comp_call(ss5_t *ss5)
{
osmo_cc_msg_t *new_msg;
/* create osmo-cc message */
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_COMP_IND);
/* send message to osmo-cc */
osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
}
/*
* dial string generation and parsing
*/
static char *prefix_1_digit[] = { "1", "7", NULL };
static char *prefix_2_digit[] = {
"20", "27", "28", "30", "31", "32", "33", "34", "36", "39", "40", "41",
"43", "44", "45", "46", "47", "48", "49", "51", "52", "53", "54", "55",
"56", "57", "58", "60", "61", "62", "63", "64", "65", "66", "81", "82",
"83", "84", "86", "89", "90", "91", "92", "93", "94", "95", "98",
NULL };
/* use number and number type to generate an SS5 dial string
* the digits are checked if they can be dialed
* if the number is already in SS5 format, only digits are checked
*/
static int generate_dial_string(uint8_t type, const char *dialing, char *string, int string_size)
{
int full_string_given = 0;
int i, ii;
if ((int)strlen(dialing) + 4 > string_size) {
PDEBUG(DSS5, DEBUG_NOTICE, "Dial string is too long for our digit register, call is rejected!\n");
return -EINVAL;
}
/* check for correct digits */
for (i = 0, ii = strlen(dialing); i < ii; i++) {
/* string may start with 'a' or 'b', but then 'c' must be the last digit */
if (dialing[i] == 'a' || dialing[i] == 'b') {
full_string_given = 1;
if (dialing[ii - 1] != 'c') {
PDEBUG(DSS5, DEBUG_NOTICE, "Number starts with 'a' (KP1) or 'b' (KP2) but missing 'c' (ST) at the end, call is rejected!\n");
return -EINVAL;
}
/* remove check of last digit 'c' */
--ii;
continue;
}
/* string must only consist of numerical digits and '*' (code 11) and '#' (code 12) */
if (!strchr("0123456789*#", dialing[i])) {
PDEBUG(DSS5, DEBUG_NOTICE, "Number has invalid digits, call is rejected!\n");
return -EINVAL;
}
}
/* if full string with 'a'/'b' and 'c' is given, we have complete dial string */
if (full_string_given) {
strcpy(string, dialing);
return 0;
}
/* if number is not of international type, create national dial string */
if (type != OSMO_CC_TYPE_INTERNATIONAL) {
// make GCC happy
strcpy(string, "a0");
strcat(string, dialing);
strcat(string, "c");
return 0;
}
/* check international prefix with length of 1 digit */
if ((int)strlen(dialing) < 1) {
PDEBUG(DSS5, DEBUG_NOTICE, "International number too short to get country code from, call is rejected!\n");
return -EINVAL;
}
for (i = 0; prefix_1_digit[i]; i++) {
if (prefix_1_digit[i][0] == dialing[0])
break;
}
/* if number is of international type, create international dial string */
if (prefix_1_digit[i]) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-overflow"
sprintf(string, "b%c0%sc", dialing[0], dialing + 1);
#pragma GCC diagnostic pop
return 0;
}
/* check international prefix with length of 2 digits */
if ((int)strlen(dialing) < 2) {
PDEBUG(DSS5, DEBUG_NOTICE, "International number too short to get country code from, call is rejected!\n");
return -EINVAL;
}
for (i = 0; prefix_2_digit[i]; i++) {
if (prefix_2_digit[i][0] == dialing[0]
&& prefix_2_digit[i][1] == dialing[1])
break;
}
/* if number is of international type, create international dial string */
if (prefix_2_digit[i]) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-overflow"
sprintf(string, "b%c%c0%sc", dialing[0], dialing[1], dialing + 2);
#pragma GCC diagnostic pop
return 0;
}
/* check international prefix with length of 3 digits */
if ((int)strlen(dialing) < 3) {
PDEBUG(DSS5, DEBUG_NOTICE, "International number too short to get country code from, call is rejected!\n");
return -EINVAL;
}
/* if number is of international type, create international dial string */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-overflow"
sprintf(string, "b%c%c%c0%sc", dialing[0], dialing[1], dialing[2], dialing + 3);
#pragma GCC diagnostic pop
return 0;
}
/* parse received SS5 dial string and convert it into a national or international number */
static int parse_dial_string(uint8_t *type, char *dialing, int dialing_size, const char *string)
{
char kp_digit;
const char *prefix;
int length;
int i;
/* remove start and stop digits, set string after start digit and set length to digits between start and stop */
if (string[0] != 'a' && string[0] != 'b') {
PDEBUG(DSS5, DEBUG_NOTICE, "Received digits do not start with 'a' (KP1) nor 'b' (KP2), call is rejected!\n");
return -EINVAL;
}
kp_digit = *string++;
length = strlen(string) - 1;
if (string[length] != 'c') {
PDEBUG(DSS5, DEBUG_NOTICE, "Received digits do end with 'c' (ST), call is rejected!\n");
return -EINVAL;
}
if (length > dialing_size - 1) {
PDEBUG(DSS5, DEBUG_NOTICE, "Received dial string is too long, call is rejected!\n");
return -EINVAL;
}
/* received national call */
if (kp_digit == 'a') {
/* remove discriminaing digit */
string++;
--length;
*type = OSMO_CC_TYPE_NATIONAL;
strncpy(dialing, string, length);
dialing[length] = '\0';
return 0;
}
/* check international prefix with length of 1 digit */
if (length < 2) {
PDEBUG(DSS5, DEBUG_NOTICE, "International number too short to get country code from, call is rejected!\n");
return -EINVAL;
}
for (i = 0; prefix_1_digit[i]; i++) {
if (prefix_1_digit[i][0] == string[0])
break;
}
/* if number is of international type, create international dial string */
if (prefix_1_digit[i]) {
prefix = string;
string += 1;
length -= 1;
/* remove discriminaing digit */
string++;
--length;
*type = OSMO_CC_TYPE_INTERNATIONAL;
dialing[0] = prefix[0];
strncpy(dialing + 1, string, length);
dialing[1 + length] = '\0';
return 0;
}
/* check international prefix with length of 2 digits */
if (length < 3) {
PDEBUG(DSS5, DEBUG_NOTICE, "International number too short to get country code from, call is rejected!\n");
return -EINVAL;
}
for (i = 0; prefix_2_digit[i]; i++) {
if (prefix_2_digit[i][0] == string[0]
&& prefix_2_digit[i][1] == string[1])
break;
}
/* if number is of international type, create international dial string */
if (prefix_2_digit[i]) {
prefix = string;
string += 2;
length -= 2;
/* remove discriminaing digit */
string++;
--length;
*type = OSMO_CC_TYPE_INTERNATIONAL;
dialing[0] = prefix[0];
dialing[1] = prefix[1];
strncpy(dialing + 2, string, length);
dialing[2 + length] = '\0';
return 0;
}
/* check international prefix with length of 3 digits */
if (length < 4) {
PDEBUG(DSS5, DEBUG_NOTICE, "International number too short to get country code from, call is rejected!\n");
return -EINVAL;
}
/* if number is of international type, create international dial string */
prefix = string;
string += 3;
length -= 3;
/* remove discriminaing digit */
string++;
--length;
*type = OSMO_CC_TYPE_INTERNATIONAL;
dialing[0] = prefix[0];
dialing[1] = prefix[1];
dialing[2] = prefix[2];
strncpy(dialing + 3, string, length);
dialing[3 + length] = '\0';
return 0;
}
/*
* event handling
*/
/* function that receives the digit or the cease of it (' ' or different digit) */
void receive_digit(void *priv, char digit, double dbm)
{
ss5_t *ss5 = priv;
int i;
int rc;
if (digit > ' ')
PDEBUG_CHAN(DSS5, DEBUG_DEBUG, "Received digit '%c' in '%s' state. (%.1f dBm)\n", digit, ss5_state_names[ss5->state], dbm);
else
PDEBUG_CHAN(DSS5, DEBUG_DEBUG, "Received cease of digit in '%s' state.\n", ss5_state_names[ss5->state]);
/* a clear forward (not release guard) at any state (including idle state) */
if (ss5->state != SS5_STATE_SEND_CLR_FWD && digit == 'C') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'clear-forward' signal in '%s' state, sending 'release-guard' and clearing call.\n", ss5_state_names[ss5->state]);
/* release outgoing call */
if (ss5->cc_callref) {
/* send release indication towards CC */
release_call(ss5, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR);
/* remove ref */
ss5->cc_callref = 0;
}
/* stop timer */
timer_stop(&ss5->timer);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_RELEASE);
/* unset dial string, if set */
set_dial_string(&ss5->dsp, "");
/* send release-guard */
set_tone(&ss5->dsp, 'C', 0);
/* to prevent blueboxing */
if (ss5->ss5_ep->prevent_blueboxing) {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Starting release-guard timer to prevent blueboxing.\n");
/* start timer */
timer_start(&ss5->timer, MIN_RELEASE_GUARD);
}
return;
}
switch (ss5->state) {
/* release guard */
case SS5_STATE_SEND_RELEASE:
if (digit != 'C') {
/* wait at least the minimum release-guard time, to prevent blueboxing */
if (timer_running(&ss5->timer)) {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "'clear-forward' is ceased in '%s' state, must wait to prevent blueboxing.\n", ss5_state_names[ss5->state]);
/* state idle */
ss5_new_state(ss5, SS5_STATE_SEND_REL_WAIT);
break;
}
PDEBUG_CHAN(DSS5, DEBUG_INFO, "'clear-forward' is ceased in '%s' state, going idle.\n", ss5_state_names[ss5->state]);
/* cease */
set_tone(&ss5->dsp, 0, 0);
/* state idle */
ss5_new_state(ss5, SS5_STATE_IDLE);
/* reset instance */
link_reset(ss5);
}
break;
/* outgoing call sends seize */
case SS5_STATE_SEND_SEIZE:
if (digit == 'A') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'seize' signal in '%s' state, this is double seizure.\n", ss5_state_names[ss5->state]);
/* set timeout */
timer_start(&ss5->timer, DUR_DOUBLE_SEIZURE);
/* change state */
ss5_new_state(ss5, SS5_STATE_DOUBLE_SEIZE);
}
if (digit == 'B') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'proceed-to-send' signal in '%s' state, ceasing 'seize' signal.\n", ss5_state_names[ss5->state]);
/* set recognition time to normal */
set_sig_detect_duration(&ss5->dsp, SIGN_RECOGNITION_NORMAL, SIGN_RECOGNITION_NORMAL);
/* cease */
set_tone(&ss5->dsp, 0, 0);
/* stop timer */
timer_stop(&ss5->timer);
/* change state */
ss5_new_state(ss5, SS5_STATE_RECV_PROCEED);
}
break;
/* both ends send a seize, waiting for timeout */
case SS5_STATE_DOUBLE_SEIZE:
break;
/* outgoing call receives proceed-to-send */
case SS5_STATE_RECV_PROCEED:
if (digit != 'B') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "proceed-to-send' is ceased in '%s' state, sendig digits.\n", ss5_state_names[ss5->state]);
/* cease */
set_tone(&ss5->dsp, 0, 0);
/* dial */
set_tone(&ss5->dsp, ' ', PAUSE_BEFORE_DIALING);
set_dial_string(&ss5->dsp, ss5->dialing);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_DIGITS);
}
break;
/* outgoing call receives answer or busy-flash */
case SS5_STATE_OUT_INACTIVE:
if (digit == 'A') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'answer' signal in '%s' state, sending 'acknowledge'.\n", ss5_state_names[ss5->state]);
/* send acknowledge */
set_tone(&ss5->dsp, 'A', MAX_ACKNOWLEDGE);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_ACK_ANS);
/* indicate answer to upper layer */
answer_call(ss5);
}
if (digit == 'B') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'busy-flash' signal in '%s' state, sending 'acknowledge'.\n", ss5_state_names[ss5->state]);
/* send acknowledge */
set_tone(&ss5->dsp, 'A', MAX_ACKNOWLEDGE);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_ACK_BUS);
/* indicate disconnect w/tones to upper layer */
disconnect_call(ss5, OSMO_CC_ISDN_CAUSE_USER_BUSY);
}
break;
/* outgoing call receives clear-back */
case SS5_STATE_OUT_ACTIVE:
if (digit == 'A') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'answer' signal in '%s' state, sending 'acknowledge'.\n", ss5_state_names[ss5->state]);
/* send acknowledge */
set_tone(&ss5->dsp, 'A', MAX_ACKNOWLEDGE);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_ACK_ANS);
/* indicate answer to upper layer */
answer_call(ss5);
}
if (digit == 'B') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'clear-back' signal in '%s' state, sending 'acknowledge'.\n", ss5_state_names[ss5->state]);
/* send acknowledge */
set_tone(&ss5->dsp, 'A', MAX_ACKNOWLEDGE);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_ACK_CLR);
/* indicate disconnect w/tones to upper layer */
disconnect_call(ss5, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR);
}
break;
/* outgoing call receives answer */
case SS5_STATE_SEND_ACK_ANS:
if (digit != 'A') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "'answer' is ceased in '%s' state, call is established.\n", ss5_state_names[ss5->state]);
/* stop timer */
timer_stop(&ss5->timer);
/* cease */
set_tone(&ss5->dsp, 0, 0);
/* change state */
ss5_new_state(ss5, SS5_STATE_OUT_ACTIVE);
}
break;
/* outgoing call receives busy-flash */
case SS5_STATE_SEND_ACK_BUS:
case SS5_STATE_SEND_DIGITS:
if (digit != 'B') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "'busy-flash' is ceased in '%s' state, call is disconnected.\n", ss5_state_names[ss5->state]);
/* stop timer */
timer_stop(&ss5->timer);
/* unset dial string, if set */
set_dial_string(&ss5->dsp, "");
/* cease */
set_tone(&ss5->dsp, 0, 0);
/* change state */
ss5_new_state(ss5, SS5_STATE_OUT_INACTIVE);
}
break;
/* outgoing call receives clear-back */
case SS5_STATE_SEND_ACK_CLR:
if (digit != 'B') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "'clear-back' is ceased in '%s' state, call is disconnected.\n", ss5_state_names[ss5->state]);
/* stop timer */
timer_stop(&ss5->timer);
/* cease */
set_tone(&ss5->dsp, 0, 0);
/* change state */
ss5_new_state(ss5, SS5_STATE_OUT_INACTIVE);
}
break;
/* outgoing call sends clear forward */
case SS5_STATE_SEND_CLR_FWD:
if (digit == 'C') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'release-guard' signal in '%s' state, ceasing 'clear-forward' signal.\n", ss5_state_names[ss5->state]);
/* cease */
set_tone(&ss5->dsp, 0, 0);
/* stop timer */
timer_stop(&ss5->timer);
/* change state */
ss5_new_state(ss5, SS5_STATE_RECV_RELEASE);
}
break;
/* outgoing call receives release guard */
case SS5_STATE_RECV_RELEASE:
if (digit != 'C') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "'release-guard' is ceased in '%s' state, going idle.\n", ss5_state_names[ss5->state]);
/* state idle */
ss5_new_state(ss5, SS5_STATE_IDLE);
/* reset instance */
link_reset(ss5);
}
break;
/* incoming call receives seize */
case SS5_STATE_IDLE:
if (digit == 'A') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'seize' signal in '%s' state, sending 'proceed-to-send'.\n", ss5_state_names[ss5->state]);
/* set recognition time to normal */
set_sig_detect_duration(&ss5->dsp, SIGN_RECOGNITION_NORMAL, SIGN_RECOGNITION_NORMAL);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_PROCEED);
/* send proceed-to-send */
set_tone(&ss5->dsp, 'B', MAX_PROCEED_TO_SEND);
}
break;
/* incoming call sends proceed-to-send */
case SS5_STATE_SEND_PROCEED:
if (digit != 'A') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "'seize' is ceased in '%s' state, receiving digits.\n", ss5_state_names[ss5->state]);
/* cease */
set_tone(&ss5->dsp, 0, 0);
/* change state */
ss5_new_state(ss5, SS5_STATE_RECV_DIGIT);
/* start timer */
timer_start(&ss5->timer, TO_DIALING);
}
break;
/* incoming call receives digits */
case SS5_STATE_RECV_DIGIT:
if (!(digit >= '0' && digit <= '9') && !(digit >= 'a' && digit <= 'c')) {
break;
}
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Digit '%c' is ceased in '%s' state.\n", digit, ss5_state_names[ss5->state]);
/* add digit */
i = strlen(ss5->dialing);
if (i + 1 == sizeof(ss5->dialing))
break;
ss5->dialing[i++] = digit;
ss5->dialing[i] = '\0';
/* change state */
ss5_new_state(ss5, SS5_STATE_RECV_SPACE);
/* restart timer */
timer_start(&ss5->timer, TO_DIALING);
break;
case SS5_STATE_RECV_SPACE:
if (digit != ' ')
break;
/* check for end of dialing */
i = strlen(ss5->dialing) - 1;
if (ss5->dialing[i] == 'c') {
osmo_cc_msg_t *msg;
uint8_t type;
char dialing[sizeof(ss5->dialing)];
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Dialing '%s' is complete, sending setup message towards call control.\n", ss5->dialing);
/* stop timer */
timer_stop(&ss5->timer);
/* check dial string */
rc = parse_dial_string(&type, dialing, sizeof(dialing), ss5->dialing);
if (rc < 0) {
/* send clear-back */
set_tone(&ss5->dsp, 'B', MAX_CLEAR_BACK);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_CLR_BAK);
break;
}
/* setup message */
msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_IND);
/* network type */
osmo_cc_add_ie_calling_network(msg, OSMO_CC_NETWORK_SS5_NONE, "");
/* called number */
osmo_cc_add_ie_called(msg, type, OSMO_CC_PLAN_TELEPHONY, dialing);
/* sdp offer */
ss5->cc_session = osmo_cc_helper_audio_offer(&ss5->ss5_ep->cc_ep.session_config, ss5, codecs, down_audio, msg, 1);
if (!ss5->cc_session) {
osmo_cc_free_msg(msg);
PDEBUG_CHAN(DSS5, DEBUG_NOTICE, "Failed to offer audio, sending 'clear-back'.\n");
/* send clear-back */
set_tone(&ss5->dsp, 'B', MAX_CLEAR_BACK);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_CLR_BAK);
break;
}
/* create new call */
osmo_cc_call_t *cc_call = osmo_cc_call_new(&ss5->ss5_ep->cc_ep);
ss5->cc_callref = cc_call->callref;
/* send message to CC */
osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, msg);
/* change state */
ss5_new_state(ss5, SS5_STATE_IN_INACTIVE);
break;
}
/* change state */
ss5_new_state(ss5, SS5_STATE_RECV_DIGIT);
break;
/* incoming call sends answer */
case SS5_STATE_SEND_ANSWER:
if (digit == 'A') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'acknowledge' to 'answer' in '%s' state, call is now active.\n", ss5_state_names[ss5->state]);
/* stop timer */
timer_stop(&ss5->timer);
/* cease */
set_tone(&ss5->dsp, 0, 0);
/* change state */
ss5_new_state(ss5, SS5_STATE_IN_ACTIVE);
}
break;
/* incoming call sends busy-flash */
case SS5_STATE_SEND_BUSY:
if (digit == 'A') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'acknowledge' to 'busy-flash' in '%s' state, call is now inactive.\n", ss5_state_names[ss5->state]);
/* stop timer */
timer_stop(&ss5->timer);
/* cease */
set_tone(&ss5->dsp, 0, 0);
/* change state */
ss5_new_state(ss5, SS5_STATE_IN_INACTIVE);
}
break;
/* incoming call sends clear-back */
case SS5_STATE_SEND_CLR_BAK:
if (digit == 'A') {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received 'acknowledge' to 'clear-back' in '%s' state, call is now inactive.\n", ss5_state_names[ss5->state]);
/* stop timer */
timer_stop(&ss5->timer);
/* cease */
set_tone(&ss5->dsp, 0, 0);
/* change state */
ss5_new_state(ss5, SS5_STATE_IN_INACTIVE);
}
break;
default:
PDEBUG_CHAN(DSS5, DEBUG_ERROR, "Received digit '%c' in '%s' state is not handled, please fix!\n", digit, ss5_state_names[ss5->state]);
}
}
/* dialing was completed */
void dialing_complete(void *priv)
{
ss5_t *ss5 = priv;
if (ss5->state == SS5_STATE_SEND_DIGITS) {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Dialing is complete in '%s' state, waiting for remote party to answer.\n", ss5_state_names[ss5->state]);
/* change state */
ss5_new_state(ss5, SS5_STATE_OUT_INACTIVE);
}
/* indicate alerting */
alert_call(ss5);
}
/* timeouts */
static void ss5_timeout(struct timer *timer)
{
ss5_t *ss5 = timer->priv;
switch (ss5->state) {
case SS5_STATE_RECV_DIGIT:
case SS5_STATE_RECV_SPACE:
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Received timeout in '%s' state, sending 'clear-back'.\n", ss5_state_names[ss5->state]);
/* send clear-back */
set_tone(&ss5->dsp, 'B', MAX_CLEAR_BACK);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_CLR_BAK);
break;
case SS5_STATE_DOUBLE_SEIZE:
/* cease */
set_tone(&ss5->dsp, 0, 0);
/* state idle */
ss5_new_state(ss5, SS5_STATE_IDLE);
/* send release indication towards CC */
release_call(ss5, OSMO_CC_ISDN_CAUSE_NO_CIRCUIT_CHAN);
/* reset inst */
link_reset(ss5);
break;
case SS5_STATE_SEND_REL_WAIT:
PDEBUG_CHAN(DSS5, DEBUG_INFO, "'release-guard' timer expired, going idle.\n");
/* cease */
set_tone(&ss5->dsp, 0, 0);
/* state idle */
ss5_new_state(ss5, SS5_STATE_IDLE);
/* reset instance */
link_reset(ss5);
break;
default:
;
}
}
/* message from call control */
void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg)
{
ss5_endpoint_t *ss5_ep = ep->priv;
ss5_t *ss5;
osmo_cc_msg_t *new_msg;
uint8_t type, plan, present, screen;
char dialing[64];
const char *sdp;
int rc;
/* hunt for callref */
ss5 = ss5_ep->link_list;
while (ss5) {
if (ss5->cc_callref == callref)
break;
ss5 = ss5->next;
}
/* process SETUP */
if (!ss5) {
if (msg->type != OSMO_CC_MSG_SETUP_REQ) {
PDEBUG(DSS5, DEBUG_ERROR, "received message without ss5 instance, please fix!\n");
goto done;
}
/* hunt free ss5 instance */
ss5 = ss5_ep->link_list;
while (ss5) {
if (ss5->state == SS5_STATE_IDLE)
break;
ss5 = ss5->next;
}
if (!ss5) {
PDEBUG(DSS5, DEBUG_NOTICE, "No free ss5 instance, rejecting.\n");
reject_call(ss5_ep, callref, OSMO_CC_ISDN_CAUSE_NO_CIRCUIT_CHAN);
goto done;
}
/* link with cc */
ss5->cc_callref = callref;
}
switch (msg->type) {
case OSMO_CC_MSG_SETUP_REQ: /* dial-out command received from epoint */
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Outgoing call in '%s' state, sending 'seize'.\n", ss5_state_names[ss5->state]);
/* caller id */
rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, ss5->callerid, sizeof(ss5->callerid));
/* called number */
rc = osmo_cc_get_ie_called(msg, 0, &type, &plan, dialing, sizeof(dialing));
if (rc < 0 || !dialing[0]) {
PDEBUG_CHAN(DSS5, DEBUG_NOTICE, "No number given, call is rejected!\n");
inv_nr:
reject_call(ss5->ss5_ep, ss5->cc_callref, OSMO_CC_ISDN_CAUSE_INV_NR_FORMAT);
link_reset(ss5);
goto done;
}
rc = generate_dial_string(type, dialing, ss5->dialing, sizeof(ss5->dialing));
if (rc < 0)
goto inv_nr;
/* sdp accept */
sdp = osmo_cc_helper_audio_accept(&ss5->ss5_ep->cc_ep.session_config, ss5, codecs, down_audio, msg, &ss5->cc_session, &ss5->codec, 0);
if (!sdp) {
reject_call(ss5->ss5_ep, ss5->cc_callref, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL);
link_reset(ss5);
goto done;
}
/* proceed */
proceed_call(ss5, sdp);
/* send seize */
set_tone(&ss5->dsp, 'A', MAX_SEIZE);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_SEIZE);
break;
case OSMO_CC_MSG_SETUP_ACK_REQ: /* more information is needed */
case OSMO_CC_MSG_PROC_REQ: /* call of endpoint is proceeding */
case OSMO_CC_MSG_ALERT_REQ: /* call of endpoint is ringing */
case OSMO_CC_MSG_PROGRESS_REQ: /* progress */
rc = osmo_cc_helper_audio_negotiate(msg, &ss5->cc_session, &ss5->codec);
if (rc < 0) {
codec_failed:
PDEBUG_CHAN(DSS5, DEBUG_NOTICE, "Releasing, because codec negotiation failed.\n");
/* send busy-flash */
set_tone(&ss5->dsp, 'B', MAX_BUSY_FLASH);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_BUSY);
/* release call */
release_call(ss5, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL);
/* reset inst */
link_reset(ss5);
goto done;
}
break;
case OSMO_CC_MSG_SETUP_RSP: /* call of endpoint is connected */
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Incoming call has answered in '%s' state, sending 'answer'.\n", ss5_state_names[ss5->state]);
rc = osmo_cc_helper_audio_negotiate(msg, &ss5->cc_session, &ss5->codec);
if (rc < 0)
goto codec_failed;
/* connect acknowledge */
setup_comp_call(ss5);
/* not in right state, which should never happen anyway */
if (ss5->state != SS5_STATE_IN_INACTIVE)
break;
/* send answer */
set_tone(&ss5->dsp, 'A', MAX_ANSWER);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_ANSWER);
break;
case OSMO_CC_MSG_REJ_REQ: /* call has been rejected */
case OSMO_CC_MSG_REL_REQ: /* call has been released */
case OSMO_CC_MSG_DISC_REQ: /* call has been disconnected */
rc = osmo_cc_helper_audio_negotiate(msg, &ss5->cc_session, &ss5->codec);
if (rc < 0)
goto codec_failed;
/* right state */
if (ss5->state == SS5_STATE_IN_INACTIVE) {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Incoming call has disconnected in '%s' state, sending 'busy-flash'.\n", ss5_state_names[ss5->state]);
/* send busy-flash */
set_tone(&ss5->dsp, 'B', MAX_BUSY_FLASH);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_BUSY);
} else
if (ss5->state == SS5_STATE_IN_ACTIVE) {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Incoming call has disconnected in '%s' state, sending 'clear-back'.\n", ss5_state_names[ss5->state]);
/* send clear-back */
set_tone(&ss5->dsp, 'B', MAX_CLEAR_BACK);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_CLR_BAK);
} else {
PDEBUG_CHAN(DSS5, DEBUG_INFO, "Outgoing call has disconnected in '%s' state, sending 'clear-forward'.\n", ss5_state_names[ss5->state]);
/* send clear-forward */
set_tone(&ss5->dsp, 'C', MAX_CLEAR_FORWARD);
/* change state */
ss5_new_state(ss5, SS5_STATE_SEND_CLR_FWD);
if (msg->type == OSMO_CC_MSG_DISC_REQ) {
/* clone osmo-cc message to preserve cause */
new_msg = osmo_cc_clone_msg(msg);
new_msg->type = OSMO_CC_MSG_REL_IND;
/* send message to osmo-cc */
osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
/* reset */
link_reset(ss5);
break;
}
}
/* on release, we confirm */
if (msg->type == OSMO_CC_MSG_REL_REQ) {
/* clone osmo-cc message to preserve cause */
new_msg = osmo_cc_clone_msg(msg);
new_msg->type = OSMO_CC_MSG_REL_CNF;
/* send message to osmo-cc */
osmo_cc_ll_msg(&ss5->ss5_ep->cc_ep, ss5->cc_callref, new_msg);
}
/* on reject/release we reset/unlink call */
if (msg->type != OSMO_CC_MSG_DISC_REQ)
link_reset(ss5);
break;
}
done:
osmo_cc_free_msg(msg);
}