1136 lines
34 KiB
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);
|
|
}
|
|
|