1906 lines
58 KiB
C
1906 lines
58 KiB
C
/* call handling
|
|
*
|
|
* (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/>.
|
|
*/
|
|
|
|
/* Call forking process
|
|
*
|
|
* A call can be forwarded to one or multiple endpoints (call forking).
|
|
*
|
|
* In case of call forking, each terminating endpoint that disconnects or
|
|
* releases, is removed from the list of terminating endpoints. The cause is
|
|
* collected according to mulitpoint process at Q.931. If all endpoints have
|
|
* disconnected or released, the originating endpoint is released with the
|
|
* collected cause.
|
|
*
|
|
* If one terminating endpoint answers the call, all other endpoints are
|
|
* released with the cause "non-selected user clearing".
|
|
*
|
|
* If the originating endpoint disconnectes or releases prior answer, all
|
|
* terminating endpoints are released with the same cause.
|
|
*/
|
|
|
|
/* SDP negotiation process
|
|
*
|
|
* The originating endpoint sends a CC-SETUP-REQ with SDP included.
|
|
*
|
|
* If no RTP proxy is enabled, the SDP is forwarded towards terminating
|
|
* endpoint or multiple terminating endpoints (in case of call forking). The
|
|
* first reply with SDP from the terminating endpoint is stored. In case of
|
|
* single terminating endpoint, it is forwarded towards the originating
|
|
* endpoint with progress indicator set to 1 or 8. In case of multiple
|
|
* terminating endpoints (call forking) the SDP is forwarded as soon a
|
|
* CC-SETUP-RSP is received from the first terminating endpoint that answers.
|
|
* The SDP negotiation is complete.
|
|
*
|
|
* If RTP proxy is enabled, the SDP is negotiated with the supported codecs of
|
|
* this router. The first reply to the CC-SETUP-REQ message will include the
|
|
* SDP reply as well as progress indicator set to 8 (if not a CC-SETUP-CNF
|
|
* message) towards originating endpoint. The SDP negoatiation on the
|
|
* originating side is complete. If the call gets forwarded to a single or
|
|
* multiple terminating endpoints, an SDP is generated with teh supported
|
|
* codecs of this router. In case of single terminating endpont, the SDP of the
|
|
* first reply to the CC-SETUP-IND message is used to negotiate the codec. In
|
|
* case of multiple terminating endpoints (call forking) the SDP reply is
|
|
* stored and processed when a CC-SETUP-RSP is received from the first
|
|
* terminating endpoint that answers. The SDP negotiation on the terminating
|
|
* side is complete.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include "../libdebug/debug.h"
|
|
#include "../libg711/g711.h"
|
|
#include "call.h"
|
|
#include "audio.h"
|
|
#include "display.h"
|
|
|
|
call_t *call_list = NULL;
|
|
struct timer status_timer;
|
|
static osmo_cc_endpoint_t *cc_ep;
|
|
static const char *routing_script, *routing_shell;
|
|
|
|
static struct osmo_cc_helper_audio_codecs codecs[] = {
|
|
// { "L16", 8000, 1, encode_l16, decode_l16 }, FIXME: make codecs selectable, if more codecs are supported in the future
|
|
{ "PCMA", 8000, 1, g711_encode_alaw, g711_decode_alaw },
|
|
{ "PCMU", 8000, 1, g711_encode_ulaw, g711_decode_ulaw },
|
|
{ NULL, 0, 0, NULL, NULL},
|
|
};
|
|
|
|
static void refresh_status(void)
|
|
{
|
|
osmo_cc_call_t *cc_call;
|
|
int from_count, to_count;
|
|
call_t *call;
|
|
call_relation_t *relation;
|
|
|
|
display_status_start();
|
|
|
|
for (cc_call = cc_ep->call_list; cc_call; cc_call = cc_call->next) {
|
|
if (cc_call->state != OSMO_CC_STATE_ATTACH_IN)
|
|
continue;
|
|
if (!cc_call->attached_name)
|
|
continue;
|
|
from_count = 0;
|
|
for (call = call_list; call; call = call->next) {
|
|
if (!call->relation_list)
|
|
continue;
|
|
if (!!strcmp(cc_call->attached_name, call->relation_list->interface))
|
|
continue;
|
|
to_count = 0;
|
|
for (relation = call->relation_list->next; relation; relation = relation->next) {
|
|
display_status_line(cc_call->attached_name, from_count, call->relation_list->id, to_count, relation->interface, relation->id, relation->state);
|
|
to_count++;
|
|
}
|
|
if (!to_count)
|
|
display_status_line(cc_call->attached_name, from_count, call->relation_list->id, 0, NULL, NULL, 0);
|
|
from_count++;
|
|
}
|
|
if (!from_count)
|
|
display_status_line(cc_call->attached_name, 0, NULL, 0, NULL, NULL, 0);
|
|
}
|
|
|
|
display_status_end();
|
|
}
|
|
|
|
static int status_needs_update = 0;
|
|
|
|
static void status_timeout(struct timer *timer)
|
|
{
|
|
static int last_interfaces = -1;
|
|
osmo_cc_call_t *cc_call;
|
|
int interfaces = 0;
|
|
|
|
for (cc_call = cc_ep->call_list; cc_call; cc_call = cc_call->next) {
|
|
if (cc_call->state != OSMO_CC_STATE_ATTACH_IN)
|
|
continue;
|
|
interfaces++;
|
|
}
|
|
|
|
if (interfaces != last_interfaces) {
|
|
last_interfaces = interfaces;
|
|
status_needs_update = 1;
|
|
}
|
|
|
|
if (status_needs_update) {
|
|
refresh_status();
|
|
status_needs_update = 0;
|
|
}
|
|
|
|
timer_start(timer, 0.1);
|
|
}
|
|
|
|
static const char *state_names[] = {
|
|
"IDLE",
|
|
"SETUP",
|
|
"OVERLAP",
|
|
"PROCEEDING",
|
|
"ALERTING",
|
|
"CONNECT",
|
|
"DISC-FROM-ORIG",
|
|
"DISC-FROM-TERM",
|
|
};
|
|
|
|
static void new_state(call_t *call, enum call_state state)
|
|
{
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "(call #%d) Changing state %s -> %s.\n", call->num, state_names[call->state], state_names[state]);
|
|
call->state = state;
|
|
}
|
|
|
|
static const char *relation_name(call_relation_t *relation)
|
|
{
|
|
static char text[128];
|
|
|
|
if (relation->num == 0)
|
|
sprintf(text, "(call #%d, originator)", relation->call->num);
|
|
else
|
|
sprintf(text, "(call #%d, terminator #%d)", relation->call->num, relation->num);
|
|
|
|
return text;
|
|
}
|
|
|
|
static call_t *call_create(void)
|
|
{
|
|
call_t *call, **call_p;
|
|
static int call_num = 0;
|
|
|
|
call = calloc(1, sizeof(*call));
|
|
if (!call) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "No memory!\n");
|
|
abort();
|
|
}
|
|
|
|
call->num = ++call_num;
|
|
call->routing.call = call;
|
|
|
|
/* append to list */
|
|
call_p = &call_list;
|
|
while (*call_p)
|
|
call_p = &((*call_p)->next);
|
|
*call_p = call;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "(call #%d) Created new call instance.\n", call->num);
|
|
|
|
return call;
|
|
}
|
|
|
|
static void relation_destroy(call_relation_t *relation);
|
|
|
|
static void call_destroy(call_t *call)
|
|
{
|
|
call_t **call_p;
|
|
|
|
new_state(call, CALL_STATE_IDLE);
|
|
|
|
/* remove setup message */
|
|
free(call->setup_msg);
|
|
|
|
/* destroy all relations */
|
|
while (call->relation_list)
|
|
relation_destroy(call->relation_list);
|
|
|
|
/* destroy routing */
|
|
routing_stop(&call->routing);
|
|
routing_env_free(&call->routing);
|
|
|
|
/* detach */
|
|
call_p = &call_list;
|
|
while (*call_p) {
|
|
if (*call_p == call)
|
|
break;
|
|
call_p = &((*call_p)->next);
|
|
}
|
|
*call_p = call->next;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "(call #%d) Destroyed call instance.\n", call->num);
|
|
|
|
free(call);
|
|
}
|
|
|
|
static call_relation_t *relation_create(call_t *call)
|
|
{
|
|
call_relation_t *relation, **relation_p;
|
|
int rc;
|
|
|
|
relation = calloc(1, sizeof(*relation));
|
|
if (!relation) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "No memory!\n");
|
|
abort();
|
|
}
|
|
|
|
relation->call = call;
|
|
|
|
/* allocate jitter buffer */
|
|
rc = jitter_create(&relation->orig_dejitter, 8000 / 10); // FIXME: size
|
|
if (rc < 0)
|
|
abort();
|
|
rc = jitter_create(&relation->term_dejitter, 8000 / 10); // FIXME: size
|
|
if (rc < 0)
|
|
abort();
|
|
|
|
/* append to list, count number of relation */
|
|
relation_p = &call->relation_list;
|
|
while (*relation_p) {
|
|
relation->num++;
|
|
relation_p = &((*relation_p)->next);
|
|
}
|
|
*relation_p = relation;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s Created new endpoint relation instance.\n", relation_name(relation));
|
|
|
|
return relation;
|
|
}
|
|
|
|
static void relation_destroy(call_relation_t *relation)
|
|
{
|
|
call_relation_t **relation_p;
|
|
|
|
/* playback and record */
|
|
wave_destroy_playback(&relation->play);
|
|
wave_destroy_record(&relation->rec);
|
|
|
|
/* dtmf decoder */
|
|
dtmf_decode_exit(&relation->dtmf_dec);
|
|
|
|
/* SDP */
|
|
free((char *)relation->sdp);
|
|
|
|
/* session */
|
|
if (relation->cc_session) {
|
|
osmo_cc_free_session(relation->cc_session);
|
|
relation->cc_session = NULL;
|
|
}
|
|
|
|
/* destroy jitter buffer */
|
|
jitter_destroy(&relation->orig_dejitter);
|
|
jitter_destroy(&relation->term_dejitter);
|
|
|
|
/* detach */
|
|
relation_p = &relation->call->relation_list;
|
|
while (*relation_p) {
|
|
if (*relation_p == relation)
|
|
break;
|
|
relation_p = &((*relation_p)->next);
|
|
}
|
|
*relation_p = relation->next;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s Destroyed endpoint relation instance.\n", relation_name(relation));
|
|
|
|
free(relation);
|
|
|
|
/* refresh status: we lost a call (originating and/or terminating) */
|
|
status_needs_update = 1;
|
|
}
|
|
|
|
int call_init(osmo_cc_endpoint_t *ep, const char *_routing_script, const char *_routing_shell)
|
|
{
|
|
cc_ep = ep;
|
|
routing_script = _routing_script;
|
|
routing_shell = _routing_shell;
|
|
timer_init(&status_timer, status_timeout, NULL);
|
|
status_timeout(&status_timer);
|
|
return 0;
|
|
}
|
|
|
|
void call_exit(void)
|
|
{
|
|
timer_exit(&status_timer);
|
|
|
|
/* destroy all calls */
|
|
while (call_list)
|
|
call_destroy(call_list);
|
|
}
|
|
|
|
/* handle all calls, if it returns 1, work has been done, so it must be called again */
|
|
int call_handle(void)
|
|
{
|
|
int w;
|
|
call_t *call;
|
|
int status;
|
|
pid_t pid;
|
|
|
|
for (call = call_list; call; call = call->next) {
|
|
/* must return, call may be destroyed */
|
|
w = routing_handle(&call->routing);
|
|
if (w)
|
|
return 1;
|
|
}
|
|
|
|
/* eat zombies */
|
|
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "Script child %d terminated.\n", pid);
|
|
for (call = call_list; call; call = call->next) {
|
|
if (call->routing.script_pid == pid) {
|
|
/* tell routing that script has terminated */
|
|
call->routing.script_pid = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* RTP-Proxy
|
|
*/
|
|
|
|
/* send SDP answer to originator */
|
|
static void proxy_send_sdp_answer(call_relation_t *relation, osmo_cc_msg_t *msg)
|
|
{
|
|
const char *sdp;
|
|
|
|
/* no proxy */
|
|
if (!relation->rtp_proxy)
|
|
return;
|
|
|
|
/* NOTE: The SDP was removed at cc_message() */
|
|
|
|
/* add progreess if not connect message, but for first message or disconnect message */
|
|
if (msg->type != OSMO_CC_MSG_SETUP_CNF
|
|
&& (msg->type == OSMO_CC_MSG_DISC_IND || !relation->codec_negotiated)) {
|
|
/* process */
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "Sending progress indicator 8 to allow audio.\n");
|
|
osmo_cc_add_ie_progress(msg, OSMO_CC_CODING_ITU_T, OSMO_CC_LOCATION_BEYOND_INTERWORKING, OSMO_CC_PROGRESS_INBAND_INFO_AVAILABLE);
|
|
}
|
|
|
|
// if (!relation->codec_negotiated || msg->type == OSMO_CC_MSG_SETUP_CNF) { gibt einen crash, da codec vor der antwort schon gesetzt ist. warum sollten wir nach einer antwort denn nochmal den codec schicken?
|
|
if (!relation->codec_negotiated) {
|
|
sdp = osmo_cc_helper_audio_accept(relation, codecs, receive_originator, relation->call->setup_msg, &relation->cc_session, &relation->codec, 0);
|
|
if (sdp) {
|
|
relation->codec_negotiated = 1;
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "Sending SDP answer to originator with supported codecs.\n");
|
|
/* SDP */
|
|
osmo_cc_add_ie_sdp(msg, sdp);
|
|
} else {
|
|
relation->rtp_proxy = 0;
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "Originator's setup message does not contain a codec we support, cannot use RTP-Proxy!\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* process call from originator
|
|
*/
|
|
|
|
/* a new call is received */
|
|
static void orig_setup(uint32_t callref, osmo_cc_msg_t *old_msg)
|
|
{
|
|
call_t *call;
|
|
call_relation_t *relation;
|
|
char sdp[65536];
|
|
uint8_t type, plan, present, screen;
|
|
char number[256];
|
|
int rc;
|
|
|
|
/* creating call instance, transparent until setup with hdlc */
|
|
call = call_create();
|
|
if (!call) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "Cannot create calll instance.\n");
|
|
abort();
|
|
}
|
|
relation = relation_create(call);
|
|
/* link with cc */
|
|
relation->cc_callref = callref;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-SETUP-REQ from originator.\n", relation_name(relation));
|
|
|
|
/* store setup message in call structure */
|
|
call->setup_msg = osmo_cc_clone_msg(old_msg);
|
|
|
|
/* store SDP */
|
|
rc = osmo_cc_get_ie_sdp(old_msg, 0, sdp, sizeof(sdp));
|
|
if (rc >= 0) {
|
|
free((char *)relation->sdp);
|
|
relation->sdp = strdup(sdp);
|
|
}
|
|
|
|
/* store called number */
|
|
rc = osmo_cc_get_ie_called(old_msg, 0, &type, &plan, number, sizeof(number));
|
|
if (rc >= 0 && number[0]) {
|
|
/* add number to current dial string */
|
|
if (strlen(number) < sizeof(call->dialing_number))
|
|
strcpy(call->dialing_number, number);
|
|
}
|
|
|
|
/* store keypad */
|
|
rc = osmo_cc_get_ie_keypad(old_msg, 0, number, sizeof(number));
|
|
if (rc >= 0 && number[0]) {
|
|
/* add number to current dial string */
|
|
if (strlen(number) < sizeof(call->dialing_keypad))
|
|
strcpy(call->dialing_keypad, number);
|
|
}
|
|
|
|
/* store peer info for status */
|
|
rc = osmo_cc_get_ie_calling_interface(old_msg, 0, relation->interface, sizeof(relation->interface));
|
|
rc = osmo_cc_get_ie_calling(old_msg, 0, &type, &plan, &present, &screen, relation->id, sizeof(relation->id));
|
|
|
|
/* apply environment for routing */
|
|
routing_env_msg(&call->routing, old_msg);
|
|
|
|
/* start routing script */
|
|
routing_start(&call->routing, routing_script, routing_shell);
|
|
|
|
/* go into setup state and return */
|
|
new_state(call, CALL_STATE_SETUP);
|
|
|
|
/* refresh status: we have originating call */
|
|
status_needs_update = 1;
|
|
|
|
return;
|
|
}
|
|
|
|
/* information (dialing) is received from originating side */
|
|
static void orig_info(call_t *call, osmo_cc_msg_t *old_msg)
|
|
{
|
|
uint8_t type, plan;
|
|
char number[256];
|
|
char keypad[256];
|
|
uint8_t duration_ms, pause_ms, dtmf_mode;
|
|
char dtmf[256];
|
|
char command[512];
|
|
int rc;
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-INFO-REQ from originator.\n", relation_name(call->relation_list));
|
|
|
|
/* add called number to dial string */
|
|
rc = osmo_cc_get_ie_called(old_msg, 0, &type, &plan, number, sizeof(number));
|
|
if (rc < 0)
|
|
number[0] = '\0';
|
|
if (number[0]) {
|
|
/* add number to current dial string */
|
|
if (strlen(number) < sizeof(call->dialing_number) - strlen(call->dialing_number))
|
|
strcat(call->dialing_number, number);
|
|
}
|
|
|
|
/* add keypad to dial string */
|
|
rc = osmo_cc_get_ie_keypad(old_msg, 0, keypad, sizeof(keypad));
|
|
if (rc < 0)
|
|
keypad[0] = '\0';
|
|
if (keypad[0]) {
|
|
/* add number to current dial string */
|
|
if (strlen(keypad) < sizeof(call->dialing_keypad) - strlen(call->dialing_keypad))
|
|
strcat(call->dialing_keypad, keypad);
|
|
}
|
|
|
|
/* dtmf digits */
|
|
rc = osmo_cc_get_ie_dtmf(old_msg, 0, &duration_ms, &pause_ms, &dtmf_mode, dtmf, sizeof(dtmf));
|
|
if (rc < 0 || (dtmf_mode != OSMO_CC_DTMF_MODE_ON && dtmf_mode != OSMO_CC_DTMF_MODE_DIGITS))
|
|
dtmf[0] = '\0';
|
|
|
|
/* if there is only one call relation, forward that message */
|
|
if (call->relation_list->next && !call->relation_list->next->next) {
|
|
/* concat number to id of relation */
|
|
if (strlen(call->relation_list->next->id) + strlen(number) < sizeof(call->relation_list->next->id))
|
|
strcat(call->relation_list->next->id, number);
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_INFO_IND;
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->next->cc_callref, new_msg);
|
|
return;
|
|
}
|
|
|
|
/* if there is no call in overlap state, perform routing */
|
|
if (call->state == CALL_STATE_OVERLAP && !call->relation_list->next) {
|
|
if (!call->routing.routing) {
|
|
/* restart routing with new dial string */
|
|
routing_env_dialing(&call->routing, call->dialing_number, call->dialing_keypad);
|
|
routing_start(&call->routing, routing_script, routing_shell);
|
|
} else {
|
|
/* send digits to routing */
|
|
if (number[0]) {
|
|
snprintf(command, sizeof(command) - 1, "dialing %s", number);
|
|
command[sizeof(command) - 1] = '\0';
|
|
routing_send(&call->routing, command);
|
|
}
|
|
if (keypad[0]) {
|
|
snprintf(command, sizeof(command) - 1, "keypad %s", keypad);
|
|
command[sizeof(command) - 1] = '\0';
|
|
routing_send(&call->routing, command);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* send dtmf, to routing in all other states */
|
|
if (!call->relation_list->next) {
|
|
if (call->routing.routing) {
|
|
if (dtmf[0]) {
|
|
snprintf(command, sizeof(command) - 1, "dtmf %s", dtmf);
|
|
command[sizeof(command) - 1] = '\0';
|
|
routing_send(&call->routing, command);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* disconnect is received from originating side */
|
|
static void orig_disc(call_t *call, osmo_cc_msg_t *old_msg)
|
|
{
|
|
osmo_cc_msg_t *new_msg;
|
|
call_relation_t *relation;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-DISC-REQ from originator.\n", relation_name(call->relation_list));
|
|
|
|
/* stop routing, if originator hangs up */
|
|
if (call->routing.routing) {
|
|
routing_stop(&call->routing);
|
|
}
|
|
|
|
/* if there is no terminating call, release originator and destroy call */
|
|
if (!call->relation_list->next) {
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_REL_IND;
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
/* destroy call */
|
|
call_destroy(call);
|
|
return;
|
|
}
|
|
|
|
/* if there is one terminating call, forward the disc message */
|
|
if (!call->relation_list->next->next) {
|
|
/* update call state for status display */
|
|
call->relation_list->next->state = CALL_STATE_DISC_FROM_TERM;
|
|
status_needs_update = 1;
|
|
/* forward disc message */
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_DISC_IND;
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->next->cc_callref, new_msg);
|
|
new_state(call, CALL_STATE_DISC_FROM_ORIG);
|
|
return;
|
|
}
|
|
|
|
/* if there are multiple terminating calls, release them and originator and destroy call */
|
|
for (relation = call->relation_list->next; relation; relation = relation->next) {
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_REL_IND;
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
}
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_REL_IND;
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
/* destroy call */
|
|
call_destroy(call);
|
|
}
|
|
|
|
/* release is received from originating side */
|
|
static void orig_rel(call_t *call, osmo_cc_msg_t *old_msg)
|
|
{
|
|
osmo_cc_msg_t *new_msg;
|
|
call_relation_t *relation;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-REL-REQ from originator.\n", relation_name(call->relation_list));
|
|
|
|
/* stop routing, if originator hangs up */
|
|
if (call->routing.routing) {
|
|
routing_stop(&call->routing);
|
|
}
|
|
|
|
/* release all terminating calls, if any and confirm originator and destroy call */
|
|
for (relation = call->relation_list->next; relation; relation = relation->next) {
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_REL_IND;
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
}
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_REL_CNF;
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
/* destroy call */
|
|
call_destroy(call);
|
|
}
|
|
|
|
/* other message is received from originating side */
|
|
static void orig_other(call_t *call, osmo_cc_msg_t *old_msg)
|
|
{
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s Other CC message from originator.\n", relation_name(call->relation_list));
|
|
|
|
/* if there is one terminating call, forward the message */
|
|
if (call->relation_list->next && !call->forking) {
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = old_msg->type | 1; /* convert REQ->IND, RSP->CNF */
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->next->cc_callref, new_msg);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* process call from terminator
|
|
*/
|
|
|
|
/* overlap dialing is received from terminating side */
|
|
static void term_progress(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg)
|
|
{
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-PROGRESS-REQ from terminator.\n", relation_name(relation));
|
|
|
|
/* if single call exists, forward progress to originator */
|
|
if (!call->forking) {
|
|
/* forward message to originator */
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_PROGRESS_IND;
|
|
/* send SDP answer */
|
|
proxy_send_sdp_answer(call->relation_list, new_msg);
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
}
|
|
}
|
|
|
|
/* overlap dialing is received from terminating side */
|
|
static void term_overlap(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg)
|
|
{
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-SETUP-ACK-REQ from terminator.\n", relation_name(relation));
|
|
|
|
/* update call state for status display */
|
|
relation->state = CALL_STATE_OVERLAP;
|
|
status_needs_update = 1;
|
|
|
|
/* notify routing */
|
|
if (call->routing.routing)
|
|
routing_send(&call->routing, "call-overlap");
|
|
|
|
/* if we already reached/passed that state, we ignore it */
|
|
if (call->state != CALL_STATE_SETUP)
|
|
return;
|
|
|
|
/* change state */
|
|
new_state(call, CALL_STATE_OVERLAP);
|
|
|
|
if (!call->forking) {
|
|
/* forward message to originator */
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_SETUP_ACK_IND;
|
|
} else {
|
|
/* send message to originator */
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_ACK_IND);
|
|
}
|
|
/* send SDP answer */
|
|
proxy_send_sdp_answer(call->relation_list, new_msg);
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
}
|
|
|
|
/* proceeding is received from terminating side */
|
|
static void term_proc(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg)
|
|
{
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-PROC-REQ from terminator.\n", relation_name(relation));
|
|
|
|
/* update call state for status display */
|
|
relation->state = CALL_STATE_PROCEEDING;
|
|
status_needs_update = 1;
|
|
|
|
/* notify routing */
|
|
if (call->routing.routing)
|
|
routing_send(&call->routing, "call-proceeding");
|
|
|
|
/* if we already reached/passed that state, we ignore it */
|
|
if (call->state != CALL_STATE_SETUP
|
|
&& call->state != CALL_STATE_OVERLAP)
|
|
return;
|
|
|
|
/* change state */
|
|
new_state(call, CALL_STATE_PROCEEDING);
|
|
|
|
if (!call->forking) {
|
|
/* forward message to originator */
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_PROC_IND;
|
|
} else {
|
|
/* send message to originator */
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_PROC_IND);
|
|
}
|
|
/* send SDP answer */
|
|
proxy_send_sdp_answer(call->relation_list, new_msg);
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
}
|
|
|
|
/* alerting is received from terminating side */
|
|
static void term_alert(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg)
|
|
{
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-ALERT-REQ from terminator.\n", relation_name(relation));
|
|
|
|
/* update call state for status display */
|
|
relation->state = CALL_STATE_ALERTING;
|
|
status_needs_update = 1;
|
|
|
|
/* notify routing */
|
|
if (call->routing.routing)
|
|
routing_send(&call->routing, "call-alerting");
|
|
|
|
/* if we already reached/passed that state, we ignore it */
|
|
if (call->state != CALL_STATE_SETUP
|
|
&& call->state != CALL_STATE_OVERLAP
|
|
&& call->state != CALL_STATE_PROCEEDING)
|
|
return;
|
|
|
|
/* change state */
|
|
new_state(call, CALL_STATE_ALERTING);
|
|
|
|
if (!call->forking) {
|
|
/* forward message to originator */
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_ALERT_IND;
|
|
} else {
|
|
/* send message to originator */
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_ALERT_IND);
|
|
}
|
|
/* send SDP answer */
|
|
proxy_send_sdp_answer(call->relation_list, new_msg);
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
}
|
|
|
|
/* connect is received from terminating side */
|
|
static void term_connect(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg)
|
|
{
|
|
osmo_cc_msg_t *new_msg;
|
|
char sdp[65536];
|
|
int rc;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-SETUP-RSP (connect) from terminator.\n", relation_name(relation));
|
|
|
|
/* update call state for status display */
|
|
relation->state = CALL_STATE_CONNECT;
|
|
status_needs_update = 1;
|
|
|
|
/* notify routing */
|
|
if (call->routing.routing)
|
|
routing_send(&call->routing, "call-answer");
|
|
|
|
/* if we already reached/passed that state, we ignore it */
|
|
if (call->state != CALL_STATE_SETUP
|
|
&& call->state != CALL_STATE_OVERLAP
|
|
&& call->state != CALL_STATE_PROCEEDING
|
|
&& call->state != CALL_STATE_ALERTING) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "Connect message from terminating call now allowed in state '%s'.\n", state_names[call->state]);
|
|
return;
|
|
}
|
|
|
|
/* change state */
|
|
new_state(call, CALL_STATE_CONNECT);
|
|
|
|
/* release all other relations with "non-selected user clearing" */
|
|
while (call->relation_list->next->next) {
|
|
call_relation_t *other;
|
|
/* select other terminating call (not the one that answered) */
|
|
if (call->relation_list->next == relation)
|
|
other = call->relation_list->next->next;
|
|
else
|
|
other = call->relation_list->next;
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "Sending 'non-selected user clearing' to other terminator.\n");
|
|
/* send release message */
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
|
|
osmo_cc_add_ie_cause(new_msg, cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NONSE_USER_CLR, 0, 0);
|
|
osmo_cc_ll_msg(cc_ep, other->cc_callref, new_msg);
|
|
/* destroy terminating relation */
|
|
relation_destroy(other);
|
|
}
|
|
|
|
/* call is not forking anymore */
|
|
call->forking = 0;
|
|
|
|
rc = osmo_cc_get_ie_sdp(old_msg, 0, sdp, sizeof(sdp));
|
|
if (rc < 0)
|
|
sdp[0] = '\0';
|
|
|
|
/* forward message to originator */
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_SETUP_CNF;
|
|
/* only if RTP-Proxy is used */
|
|
if (call->relation_list->rtp_proxy) {
|
|
/* send SDP answer */
|
|
proxy_send_sdp_answer(call->relation_list, new_msg);
|
|
} else
|
|
/* use earlier SDP if not included */
|
|
if (!sdp[0] && relation->sdp)
|
|
osmo_cc_add_ie_sdp(new_msg, relation->sdp);
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
}
|
|
|
|
/* disconnect is received from terminating side */
|
|
static void term_disc(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg)
|
|
{
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-DISC-REQ from terminator.\n", relation_name(relation));
|
|
|
|
/* update call state for status display */
|
|
relation->state = CALL_STATE_DISC_FROM_TERM;
|
|
status_needs_update = 1;
|
|
|
|
/* if we have not yet connected a call */
|
|
if (call->state == CALL_STATE_SETUP
|
|
|| call->state == CALL_STATE_OVERLAP
|
|
|| call->state == CALL_STATE_PROCEEDING
|
|
|| call->state == CALL_STATE_ALERTING) {
|
|
uint8_t location, isdn_cause, socket_cause;
|
|
uint16_t sip_cause;
|
|
int rc;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "Got a disconnect before connect.\n");
|
|
/* if there is only one terminating call, forward that disconnect */
|
|
if (!call->forking) {
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "Call is not forking, so we directly forward this disconnect.\n");
|
|
/* notify routing */
|
|
if (call->routing.routing)
|
|
routing_send(&call->routing, "call-disconnect");
|
|
/* change state */
|
|
new_state(call, CALL_STATE_DISC_FROM_TERM);
|
|
/* forward message to originator */
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_DISC_IND;
|
|
/* send SDP answer */
|
|
proxy_send_sdp_answer(call->relation_list, new_msg);
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
return;
|
|
}
|
|
|
|
/* collect cause */
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "Call is forking, so we collect ISDN cause and destroy relation.\n");
|
|
rc = osmo_cc_get_ie_cause(old_msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause);
|
|
if (rc >= 0)
|
|
call->collect_cause = osmo_cc_collect_cause(call->collect_cause, isdn_cause);
|
|
|
|
/* release the terminator */
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_REL_IND;
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
|
|
/* remove relation */
|
|
relation_destroy(relation);
|
|
|
|
/* if all terminating calls have been released, release the originator and destroy call */
|
|
if (!call->relation_list->next) {
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "All terminators have disconnected, so we forward a release with the collected cause.\n");
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
|
|
osmo_cc_add_ie_cause(new_msg, cc_ep->serving_location, call->collect_cause, 0, 0);
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
/* destroy call */
|
|
call_destroy(call);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* this is connect or disconnect collision. the state implies that there is only one terminating call */
|
|
if (call->state == CALL_STATE_CONNECT
|
|
|| call->state == CALL_STATE_DISC_FROM_ORIG) {
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "Got a disconnect after connect, so we directly forward this disconnect.\n");
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_REL_IND;
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_REL_IND;
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
/* destroy call */
|
|
call_destroy(call);
|
|
return;
|
|
}
|
|
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "Disconnect message from terminating call now allowed in state '%s'.\n", state_names[call->state]);
|
|
}
|
|
|
|
/* rel is received from terminating side */
|
|
static void term_rel(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg)
|
|
{
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-REL-REQ from terminator.\n", relation_name(relation));
|
|
|
|
/* if we have not yet connected a call */
|
|
if (call->state == CALL_STATE_SETUP
|
|
|| call->state == CALL_STATE_OVERLAP
|
|
|| call->state == CALL_STATE_PROCEEDING
|
|
|| call->state == CALL_STATE_ALERTING) {
|
|
uint8_t location, isdn_cause, socket_cause;
|
|
uint16_t sip_cause;
|
|
int rc;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "Got a release before connect.\n");
|
|
/* if there is only one terminating call, forward that disconnect */
|
|
if (!call->forking) {
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "Call is not forking, so we directly forward this release.\n");
|
|
/* forward message to originator */
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_REL_IND;
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
/* confirm to terminator */
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_REL_CNF;
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
/* destroy call */
|
|
call_destroy(call);
|
|
return;
|
|
}
|
|
|
|
/* collect cause */
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "Call is forking, so we collect ISDN cause and destroy relation.\n");
|
|
rc = osmo_cc_get_ie_cause(old_msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause);
|
|
if (rc >= 0)
|
|
call->collect_cause = osmo_cc_collect_cause(call->collect_cause, isdn_cause);
|
|
|
|
/* confirm the terminator */
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_REL_CNF;
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
|
|
/* remove relation */
|
|
relation_destroy(relation);
|
|
|
|
/* if all terminating calls have been released, release the originator and destroy call */
|
|
if (!call->relation_list->next) {
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "All terminators have released, so we forward it with the collected cause.\n");
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
|
|
osmo_cc_add_ie_cause(new_msg, cc_ep->serving_location, call->collect_cause, 0, 0);
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
/* destroy call */
|
|
call_destroy(call);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* forward release to originator and confirm to terminator. the state implies that there is only one terminating call */
|
|
if (call->state == CALL_STATE_CONNECT
|
|
|| call->state == CALL_STATE_DISC_FROM_ORIG) {
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "Got a release after connect, so we directly forward this release.\n");
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_REL_IND;
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = OSMO_CC_MSG_REL_CNF;
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
/* destroy call */
|
|
call_destroy(call);
|
|
return;
|
|
}
|
|
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "Release message from terminating call now allowed in state '%s'.\n", state_names[call->state]);
|
|
}
|
|
|
|
/* other message is received from terminating side */
|
|
static void term_other(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg)
|
|
{
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s Other CC message from terminator.\n", relation_name(relation));
|
|
|
|
/* if there is one terminating call, forward the message */
|
|
if (!call->relation_list->next->next) {
|
|
new_msg = osmo_cc_clone_msg(old_msg);
|
|
new_msg->type = old_msg->type | 1; /* convert REQ->IND, RSP->CNF */
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* handle message from upper layer */
|
|
void cc_message(osmo_cc_endpoint_t __attribute__((unused)) *ep, uint32_t callref, osmo_cc_msg_t *msg)
|
|
{
|
|
call_t *call;
|
|
call_relation_t *relation;
|
|
char sdp[65536];
|
|
int rc_sdp;
|
|
|
|
/* hunt for callref */
|
|
call = call_list;
|
|
while (call) {
|
|
relation = call->relation_list;
|
|
while (relation) {
|
|
if (relation->cc_callref == callref)
|
|
break;
|
|
relation = relation->next;
|
|
}
|
|
if (relation)
|
|
break;
|
|
call = call->next;
|
|
}
|
|
|
|
/* process SETUP (new call) */
|
|
if (!call) {
|
|
if (msg->type != OSMO_CC_MSG_SETUP_REQ) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "Received message without call instance, please fix!\n");
|
|
return;
|
|
}
|
|
orig_setup(callref, msg);
|
|
osmo_cc_free_msg(msg);
|
|
return;
|
|
}
|
|
|
|
if (call->relation_list == relation) {
|
|
/* handle messages from caller */
|
|
switch (msg->type) {
|
|
case OSMO_CC_MSG_INFO_REQ:
|
|
orig_info(call, msg);
|
|
break;
|
|
case OSMO_CC_MSG_DISC_REQ:
|
|
orig_disc(call, msg);
|
|
break;
|
|
case OSMO_CC_MSG_REL_REQ:
|
|
orig_rel(call, msg);
|
|
break;
|
|
default:
|
|
orig_other(call, msg);
|
|
}
|
|
} else {
|
|
/* store sdp, if present */
|
|
rc_sdp = osmo_cc_get_ie_sdp(msg, 0, sdp, sizeof(sdp));
|
|
if (rc_sdp >= 0) {
|
|
free((char *)relation->sdp);
|
|
relation->sdp = strdup(sdp);
|
|
}
|
|
/* negotiate codec if RTP-Proxy is used and not already negotiated */
|
|
if (call->relation_list->rtp_proxy && relation->cc_session && !relation->codec_negotiated) {
|
|
/* remove progress, since it will be added with the SDP answer */
|
|
osmo_cc_remove_ie(msg, OSMO_CC_IE_PROGRESS, 0);
|
|
/* negotiate codec */
|
|
osmo_cc_helper_audio_negotiate(msg, &relation->cc_session, &relation->codec);
|
|
if (relation->codec)
|
|
relation->codec_negotiated = 1;
|
|
}
|
|
/* remove SDP, if we do RTP-Proxy */
|
|
if (rc_sdp >= 0 && call->relation_list->rtp_proxy)
|
|
osmo_cc_remove_ie(msg, OSMO_CC_IE_SDP, 0);
|
|
/* handle messages from called */
|
|
switch (msg->type) {
|
|
case OSMO_CC_MSG_PROGRESS_IND:
|
|
term_progress(call, relation, msg);
|
|
break;
|
|
case OSMO_CC_MSG_SETUP_ACK_REQ:
|
|
term_overlap(call, relation, msg);
|
|
break;
|
|
case OSMO_CC_MSG_PROC_REQ:
|
|
term_proc(call, relation, msg);
|
|
break;
|
|
case OSMO_CC_MSG_ALERT_REQ:
|
|
term_alert(call, relation, msg);
|
|
break;
|
|
case OSMO_CC_MSG_SETUP_RSP:
|
|
term_connect(call, relation, msg);
|
|
break;
|
|
case OSMO_CC_MSG_DISC_REQ:
|
|
term_disc(call, relation, msg);
|
|
break;
|
|
case OSMO_CC_MSG_REL_REQ:
|
|
case OSMO_CC_MSG_REJ_REQ:
|
|
term_rel(call, relation, msg);
|
|
break;
|
|
default:
|
|
term_other(call, relation, msg);
|
|
}
|
|
}
|
|
|
|
osmo_cc_free_msg(msg);
|
|
}
|
|
|
|
/*
|
|
* process message from routing
|
|
*/
|
|
|
|
static const char *value_of_param(const char *arg, const char *param, const char **value_p)
|
|
{
|
|
if (!!strncmp(arg, param, strlen(param)))
|
|
return NULL;
|
|
arg += strlen(param);
|
|
|
|
if (*arg == '\0') {
|
|
if (value_p)
|
|
*value_p = "";
|
|
return "";
|
|
}
|
|
|
|
if (*arg != '=')
|
|
return NULL;
|
|
arg++;
|
|
|
|
if (value_p)
|
|
*value_p = arg;
|
|
return arg;
|
|
}
|
|
|
|
/* routing orders us to activate rtp proxy */
|
|
static void routing_rtp_proxy(call_t *call)
|
|
{
|
|
call_relation_t *relation = call->relation_list;
|
|
|
|
/* ignore, if already enabled */
|
|
if (relation->rtp_proxy) {
|
|
PDEBUG(DROUTER, DEBUG_NOTICE, "RTP proxy is already enabled!.\n");
|
|
return;
|
|
}
|
|
|
|
/* error, if we already negotiated */
|
|
if (call->sdp_forwarded) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "RTP-Proxy cannot be enabled now, because we already forwarded a call.\n");
|
|
return;
|
|
}
|
|
|
|
/* no SDP */
|
|
if (!relation->sdp) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "Originator's setup message does not contain SDP, please fix!.\n");
|
|
return;
|
|
}
|
|
|
|
relation->rtp_proxy = 1;
|
|
}
|
|
|
|
/* routing orders us to play a wave file */
|
|
static void routing_play(call_t *call, int argc, const char *argv[])
|
|
{
|
|
call_relation_t *relation = call->relation_list;
|
|
const char *filename = NULL, *volume = "1.0", *loop = NULL;
|
|
int i;
|
|
int samplerate = 8000, channels = 0;
|
|
double deviation;
|
|
int rc;
|
|
|
|
wave_destroy_playback(&relation->play);
|
|
|
|
if (!relation->rtp_proxy) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "RTP-Proxy must be enabled to play a file!.\n");
|
|
return;
|
|
}
|
|
|
|
/* loop through all arguments and stop if there is a ':' */
|
|
for (i = 0; i < argc ; i++) {
|
|
if (value_of_param(argv[i], "volume", &volume));
|
|
else if (value_of_param(argv[i], "loop", &loop));
|
|
else {
|
|
if (filename) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "'play' command reqires only one file name, you specified '%s' and '%s'.\n", filename, argv[i]);
|
|
return;
|
|
}
|
|
filename = argv[i];
|
|
}
|
|
}
|
|
|
|
if (!filename) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "'play' command reqires a file name as parameter.\n");
|
|
return;
|
|
}
|
|
|
|
deviation = 1.0 / SPEECH_LEVEL * atof(volume);
|
|
rc = wave_create_playback(&relation->play, filename, &samplerate, &channels, deviation);
|
|
if (rc < 0)
|
|
return;
|
|
strncpy(relation->play_filename, filename, sizeof(relation->play_filename) - 1);
|
|
relation->play_deviation = deviation;
|
|
|
|
|
|
if (channels != 1 && channels != 2) {
|
|
wave_destroy_playback(&relation->play);
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "'play' command reqires a wave file that has 1 or 2 channels only.\n");
|
|
return;
|
|
}
|
|
|
|
if (loop)
|
|
relation->play_loop = 1;
|
|
}
|
|
|
|
/* routing orders us stop playing a wave file */
|
|
static void routing_play_stop(call_t *call)
|
|
{
|
|
call_relation_t *relation = call->relation_list;
|
|
|
|
wave_destroy_playback(&relation->play);
|
|
}
|
|
|
|
/* routing orders us to record a wave file */
|
|
static void routing_record(call_t *call, int argc, const char *argv[])
|
|
{
|
|
call_relation_t *relation = call->relation_list;
|
|
const char *filename = NULL, *volume = "1.0";
|
|
int i;
|
|
int samplerate = 8000, channels = 2;
|
|
int rc;
|
|
|
|
wave_destroy_record(&relation->rec);
|
|
|
|
if (!relation->rtp_proxy) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "RTP-Proxy must be enabled to record a file!.\n");
|
|
return;
|
|
}
|
|
|
|
/* loop through all arguments and stop if there is a ':' */
|
|
for (i = 0; i < argc ; i++) {
|
|
if (value_of_param(argv[i], "volume", &volume));
|
|
else {
|
|
if (filename) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "'record' command reqires only one file name, you specified '%s' and '%s'.\n", filename, argv[i]);
|
|
return;
|
|
}
|
|
filename = argv[i];
|
|
}
|
|
}
|
|
|
|
if (!filename) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "'record' command reqires a file name as parameter.\n");
|
|
return;
|
|
}
|
|
|
|
rc = wave_create_record(&relation->rec, filename, samplerate, channels, 1.0 / SPEECH_LEVEL / atof(volume));
|
|
if (rc < 0)
|
|
return;
|
|
}
|
|
|
|
/* routing orders us stop recording a wave file */
|
|
static void routing_record_stop(call_t *call)
|
|
{
|
|
call_relation_t *relation = call->relation_list;
|
|
|
|
wave_destroy_record(&relation->rec);
|
|
}
|
|
|
|
/* routing orders us to set local gain */
|
|
static void routing_gain(call_t *call, int argc, const char *argv[], int tx)
|
|
{
|
|
int i;
|
|
const char *gain = NULL;
|
|
|
|
if (!call->relation_list->rtp_proxy) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "RTP-Proxy must be enabled to record a file!\n");
|
|
return;
|
|
}
|
|
|
|
/* loop through all arguments and stop if there is a ':' */
|
|
for (i = 0; i < argc ; i++) {
|
|
if (gain) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "'%s-gain' command reqires only parameter, you specified '%s' and '%s'.\n", (tx) ? "tx" : "rx", gain, argv[i]);
|
|
return;
|
|
}
|
|
gain = argv[i];
|
|
}
|
|
|
|
if (!gain) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "'%s-gain' command reqires a gain value as parameter.\n", (tx) ? "tx" : "rx");
|
|
return;
|
|
}
|
|
|
|
if (tx)
|
|
call->tx_gain = atof(gain);
|
|
else
|
|
call->rx_gain = atof(gain);
|
|
|
|
}
|
|
|
|
/* routing orders us to call remote end */
|
|
static void routing_call(call_t *call, int argc, const char *argv[])
|
|
{
|
|
const char *interface;
|
|
const char *bearer_coding, *bearer_capability, *bearer_mode;
|
|
const char *calling, *calling_type, *calling_plan, *calling_present, *calling_screen, *no_calling;
|
|
const char *calling2, *calling2_type, *calling2_plan, *calling2_present, *calling2_screen, *no_calling2;
|
|
const char *redirecting, *redirecting_type, *redirecting_plan, *redirecting_present, *redirecting_screen, *redirecting_reason, *no_redirecting;
|
|
const char *dialing, *dialing_type, *dialing_plan;
|
|
const char *keypad;
|
|
uint8_t coding, capability, mode;
|
|
uint8_t type, plan, present, screen, reason;
|
|
char number[256];
|
|
int i, rc, calls = 0;
|
|
osmo_cc_msg_t *new_msg, *setup_msg;
|
|
|
|
/* if we have a call, we don't add more terminators */
|
|
if (call->relation_list->next) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "Multiple call commands from routing are not allowed.\n");
|
|
return;
|
|
}
|
|
|
|
setup_msg = call->setup_msg;
|
|
|
|
next_call:
|
|
interface = NULL;
|
|
bearer_coding = bearer_capability = bearer_mode = NULL;
|
|
calling = calling_type = calling_plan = calling_present = calling_screen = no_calling = NULL;
|
|
calling2 = calling2_type = calling2_plan = calling2_present = calling2_screen = no_calling2 = NULL;
|
|
redirecting = redirecting_type = redirecting_plan = redirecting_present = redirecting_screen = redirecting_reason = no_redirecting = NULL;
|
|
dialing = dialing_type = dialing_plan = NULL;
|
|
keypad = NULL;
|
|
/* loop through all arguments and stop if there is a ':' */
|
|
for (i = 0; i < argc && argv[i][0] != ':'; i++) {
|
|
if (value_of_param(argv[i], "interface", &interface));
|
|
else if (value_of_param(argv[i], "bearer-coding", &bearer_coding));
|
|
else if (value_of_param(argv[i], "bearer-capability", &bearer_capability));
|
|
else if (value_of_param(argv[i], "bearer-mode", &bearer_mode));
|
|
else if (value_of_param(argv[i], "calling", &calling));
|
|
else if (value_of_param(argv[i], "calling-type", &calling_type));
|
|
else if (value_of_param(argv[i], "calling-plan", &calling_plan));
|
|
else if (value_of_param(argv[i], "calling-present", &calling_present));
|
|
else if (value_of_param(argv[i], "calling-screen", &calling_screen));
|
|
else if (value_of_param(argv[i], "no-calling", &no_calling));
|
|
else if (value_of_param(argv[i], "calling2", &calling2));
|
|
else if (value_of_param(argv[i], "calling2-type", &calling2_type));
|
|
else if (value_of_param(argv[i], "calling2-plan", &calling2_plan));
|
|
else if (value_of_param(argv[i], "calling2-present", &calling2_present));
|
|
else if (value_of_param(argv[i], "calling2-screen", &calling2_screen));
|
|
else if (value_of_param(argv[i], "no-calling2", &no_calling2));
|
|
else if (value_of_param(argv[i], "redirecting", &redirecting));
|
|
else if (value_of_param(argv[i], "redirecting-type", &redirecting_type));
|
|
else if (value_of_param(argv[i], "redirecting-plan", &redirecting_plan));
|
|
else if (value_of_param(argv[i], "redirecting-present", &redirecting_present));
|
|
else if (value_of_param(argv[i], "redirecting-screen", &redirecting_screen));
|
|
else if (value_of_param(argv[i], "redirecting-reason", &redirecting_reason));
|
|
else if (value_of_param(argv[i], "no-redirecting", &no_redirecting));
|
|
else if (value_of_param(argv[i], "dialing", &dialing));
|
|
else if (value_of_param(argv[i], "dialing-type", &dialing_type));
|
|
else if (value_of_param(argv[i], "dialing-plan", &dialing_plan));
|
|
else if (value_of_param(argv[i], "keypad", &keypad));
|
|
else
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "Unknown 'call' parameter '%s' from routing.\n", argv[i]);
|
|
}
|
|
/* if more calls, then forward arguments behind colon, otherwise set argc to 0 */
|
|
if (i < argc) {
|
|
argv += i + 1;
|
|
argc -= i + 1;
|
|
} else
|
|
argc = 0;
|
|
|
|
if (!interface) {
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "'call' command without 'interface' parameter.\n");
|
|
goto try_next;
|
|
}
|
|
|
|
calls++;
|
|
|
|
/* set call forking, if we have more than one terminating call */
|
|
if (calls > 1)
|
|
call->forking = 1;
|
|
|
|
/* create endpoint */
|
|
osmo_cc_call_t *cc_call = osmo_cc_call_new(cc_ep);
|
|
call_relation_t *relation = relation_create(call);
|
|
|
|
/* link with cc */
|
|
relation->cc_callref = cc_call->callref;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-SETUP-IND to terminator.\n", relation_name(relation));
|
|
|
|
/* create setup message */
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_IND);
|
|
|
|
/* interface name */
|
|
osmo_cc_add_ie_called_interface(new_msg, interface);
|
|
|
|
/* bearer capability */
|
|
rc = osmo_cc_get_ie_bearer(setup_msg, 0, &coding, &capability, &mode);
|
|
if (rc >= 0 || bearer_coding || bearer_capability || bearer_mode) {
|
|
/* if not preset, use default values */
|
|
if (rc < 0) {
|
|
coding = OSMO_CC_CODING_ITU_T;
|
|
capability = OSMO_CC_CAPABILITY_AUDIO;
|
|
mode = OSMO_CC_MODE_CIRCUIT;
|
|
}
|
|
/* alter values */
|
|
if (bearer_coding)
|
|
coding = atoi(bearer_coding);
|
|
if (bearer_capability)
|
|
capability = atoi(bearer_capability);
|
|
if (bearer_mode)
|
|
mode = atoi(bearer_coding);
|
|
osmo_cc_add_ie_bearer(new_msg, coding, capability, mode);
|
|
}
|
|
|
|
/* calling */
|
|
if (!no_calling) {
|
|
rc = osmo_cc_get_ie_calling(setup_msg, 0, &type, &plan, &present, &screen, number, sizeof(number));
|
|
if (rc >= 0 || calling_type || calling_plan || calling_present || calling_screen || calling) {
|
|
/* if not preset, use default values */
|
|
if (rc < 0) {
|
|
type = OSMO_CC_TYPE_UNKNOWN;
|
|
plan = OSMO_CC_PLAN_TELEPHONY;
|
|
present = 0;
|
|
screen = 0;
|
|
number[0] = '\0';
|
|
}
|
|
/* alter values */
|
|
if (calling_type)
|
|
type = atoi(calling_type);
|
|
if (calling_plan)
|
|
plan = atoi(calling_plan);
|
|
if (calling_present)
|
|
present = atoi(calling_present);
|
|
if (calling_screen)
|
|
screen = atoi(calling_screen);
|
|
if (!calling)
|
|
calling = number;
|
|
osmo_cc_add_ie_calling(new_msg, type, plan, present, screen, calling);
|
|
}
|
|
}
|
|
if (!no_calling && !no_calling2) {
|
|
rc = osmo_cc_get_ie_calling(setup_msg, 1, &type, &plan, &present, &screen, number, sizeof(number));
|
|
if (rc >= 0 || calling2_type || calling2_plan || calling2_present || calling2_screen || calling2) {
|
|
/* if not preset, use default values */
|
|
if (rc < 0) {
|
|
type = OSMO_CC_TYPE_UNKNOWN;
|
|
plan = OSMO_CC_PLAN_TELEPHONY;
|
|
present = 0;
|
|
screen = 0;
|
|
number[0] = '\0';
|
|
}
|
|
/* alter values */
|
|
if (calling2_type)
|
|
type = atoi(calling2_type);
|
|
if (calling2_plan)
|
|
plan = atoi(calling2_plan);
|
|
if (calling2_present)
|
|
present = atoi(calling2_present);
|
|
if (calling2_screen)
|
|
screen = atoi(calling2_screen);
|
|
if (!calling2)
|
|
calling2 = number;
|
|
osmo_cc_add_ie_calling(new_msg, type, plan, present, screen, calling2);
|
|
}
|
|
}
|
|
|
|
/* redirecting */
|
|
if (!no_redirecting) {
|
|
rc = osmo_cc_get_ie_redir(setup_msg, 0, &type, &plan, &present, &screen, &reason, number, sizeof(number));
|
|
if (rc >= 0 || redirecting_type || redirecting_plan || redirecting_present || redirecting_screen || redirecting) {
|
|
/* if not preset, use default values */
|
|
if (rc < 0) {
|
|
type = OSMO_CC_TYPE_UNKNOWN;
|
|
plan = OSMO_CC_PLAN_TELEPHONY;
|
|
present = 0;
|
|
screen = 0;
|
|
reason = OSMO_CC_REDIR_REASON_UNKNOWN;
|
|
number[0] = '\0';
|
|
}
|
|
/* alter values */
|
|
if (redirecting_type)
|
|
type = atoi(redirecting_type);
|
|
if (redirecting_plan)
|
|
plan = atoi(redirecting_plan);
|
|
if (redirecting_present)
|
|
present = atoi(redirecting_present);
|
|
if (redirecting_screen)
|
|
screen = atoi(redirecting_screen);
|
|
if (!redirecting)
|
|
redirecting = number;
|
|
osmo_cc_add_ie_redir(new_msg, type, plan, present, screen, reason, redirecting);
|
|
}
|
|
}
|
|
|
|
/* dialing */
|
|
rc = osmo_cc_get_ie_called(setup_msg, 0, &type, &plan, number, sizeof(number));
|
|
if (rc >= 0 || dialing_type || dialing_plan || dialing) {
|
|
/* if not preset, use default values */
|
|
if (rc < 0) {
|
|
type = OSMO_CC_TYPE_UNKNOWN;
|
|
plan = OSMO_CC_PLAN_TELEPHONY;
|
|
}
|
|
/* alter values */
|
|
if (dialing_type)
|
|
type = atoi(dialing_type);
|
|
if (dialing_plan)
|
|
plan = atoi(dialing_plan);
|
|
if (!dialing)
|
|
dialing = "";
|
|
osmo_cc_add_ie_called(new_msg, type, plan, dialing);
|
|
}
|
|
|
|
/* keypad */
|
|
if (keypad) {
|
|
if (keypad[0])
|
|
osmo_cc_add_ie_keypad(new_msg, keypad);
|
|
}
|
|
|
|
/* only if RTP-Proxy is used */
|
|
if (call->relation_list->rtp_proxy) {
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "Sending our codecs to the terminator.\n");
|
|
relation->cc_session = osmo_cc_helper_audio_offer(relation, codecs, receive_terminator, new_msg, 1);
|
|
} else
|
|
/* sdp from originator's setup message */
|
|
if (call->relation_list->sdp)
|
|
osmo_cc_add_ie_sdp(new_msg, call->relation_list->sdp);
|
|
|
|
/* send message to osmo-cc */
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
|
|
/* remember this, since we cannot do RTP-Proxy after */
|
|
call->sdp_forwarded = 1;
|
|
|
|
/* store peer info for status */
|
|
strncpy(relation->interface, interface, sizeof(relation->interface) - 1);
|
|
strncpy(relation->id, dialing, sizeof(relation->id) - 1);
|
|
|
|
/* update call state for status display */
|
|
relation->state = CALL_STATE_SETUP;
|
|
status_needs_update = 1;
|
|
|
|
try_next:
|
|
/* there is another call */
|
|
if (argc)
|
|
goto next_call;
|
|
}
|
|
|
|
/* routing orders us to hangup all terminating calls */
|
|
static void routing_call_stop(call_t *call)
|
|
{
|
|
call_relation_t *relation;
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
/* send message to all terminators, if any */
|
|
while (call->relation_list->next) {
|
|
relation = call->relation_list->next;
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-REL-IND to terminator.\n", relation_name(relation));
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
|
|
osmo_cc_add_ie_cause(new_msg, cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR, 0, 0);
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
relation_destroy(relation);
|
|
}
|
|
}
|
|
|
|
/* routing orders us to set call into overlap state */
|
|
static void routing_overlap(call_t *call)
|
|
{
|
|
call_relation_t *relation = call->relation_list;
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
if (call->state != CALL_STATE_SETUP)
|
|
return;
|
|
|
|
new_state(call, CALL_STATE_OVERLAP);
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-SETUP-ACK-IND to originator.\n", relation_name(relation));
|
|
|
|
/* send message to originator */
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_ACK_IND);
|
|
|
|
/* send SDP answer */
|
|
proxy_send_sdp_answer(relation, new_msg);
|
|
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
}
|
|
|
|
/* routing orders us to set call into proceeding state */
|
|
static void routing_proceeding(call_t *call)
|
|
{
|
|
call_relation_t *relation = call->relation_list;
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
if (call->state != CALL_STATE_SETUP
|
|
&& call->state != CALL_STATE_OVERLAP)
|
|
return;
|
|
|
|
new_state(call, CALL_STATE_PROCEEDING);
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-PROC-IND to originator.\n", relation_name(relation));
|
|
|
|
/* send message to originator */
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_PROC_IND);
|
|
|
|
/* send SDP answer */
|
|
proxy_send_sdp_answer(relation, new_msg);
|
|
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
}
|
|
|
|
/* routing orders us to set call into alerting state */
|
|
static void routing_alerting(call_t *call)
|
|
{
|
|
call_relation_t *relation = call->relation_list;
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
if (call->state != CALL_STATE_SETUP
|
|
&& call->state != CALL_STATE_OVERLAP
|
|
&& call->state != CALL_STATE_PROCEEDING)
|
|
return;
|
|
|
|
new_state(call, CALL_STATE_ALERTING);
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-ALERT-IND to originator.\n", relation_name(relation));
|
|
|
|
/* send message to originator */
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_ALERT_IND);
|
|
|
|
/* send SDP answer */
|
|
proxy_send_sdp_answer(relation, new_msg);
|
|
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
}
|
|
|
|
/* routing orders us to set call into answer state */
|
|
static void routing_answer(call_t *call)
|
|
{
|
|
call_relation_t *relation = call->relation_list;
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
if (call->state != CALL_STATE_SETUP
|
|
&& call->state != CALL_STATE_OVERLAP
|
|
&& call->state != CALL_STATE_PROCEEDING
|
|
&& call->state != CALL_STATE_ALERTING)
|
|
return;
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-SETUP-CNF to originator.\n", relation_name(relation));
|
|
|
|
new_state(call, CALL_STATE_CONNECT);
|
|
|
|
/* send message to originator */
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_CNF);
|
|
|
|
/* send SDP answer */
|
|
proxy_send_sdp_answer(relation, new_msg);
|
|
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
}
|
|
|
|
/* routing orders us to dsiconnect/release the call */
|
|
static void routing_disc_rel(call_t *call, int argc, const char *argv[], int disconnect)
|
|
{
|
|
call_relation_t *relation = call->relation_list;
|
|
uint8_t cause = 0;
|
|
osmo_cc_msg_t *new_msg;
|
|
int i;
|
|
|
|
/* get cause, if any */
|
|
for (i = 0; i < argc; i++) {
|
|
if (!strncmp(argv[i], "cause=", 6))
|
|
cause = atoi(argv[i] + 6);
|
|
else
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "Unknown 'disconnect' / 'release' parameter '%s' from routing.\n", argv[i]);
|
|
}
|
|
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-%s-IND to originator.\n", relation_name(relation), (disconnect) ? "DISC" : "REL");
|
|
|
|
/* send message to originator */
|
|
new_msg = osmo_cc_new_msg((disconnect) ? OSMO_CC_MSG_DISC_IND : OSMO_CC_MSG_REL_IND);
|
|
if (disconnect) {
|
|
/* send SDP answer */
|
|
proxy_send_sdp_answer(relation, new_msg);
|
|
}
|
|
/* add cause */
|
|
osmo_cc_add_ie_cause(new_msg, cc_ep->serving_location, cause, 0, 0);
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
|
|
/* send message to all terminators, if any */
|
|
for (relation = relation->next; relation; relation = relation->next) {
|
|
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-REL-IND to terminator.\n", relation_name(relation));
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
|
|
/* add cause */
|
|
osmo_cc_add_ie_cause(new_msg, cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR, 0, 0);
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
}
|
|
|
|
if (!disconnect) {
|
|
/* destroy call */
|
|
call_destroy(call);
|
|
}
|
|
}
|
|
|
|
#define db2level(db) pow(10, (double)(db) / 20.0)
|
|
|
|
void recv_dtmf(void *priv, char digit, dtmf_meas_t __attribute__((unused)) *meas)
|
|
{
|
|
call_relation_t *relation = (call_relation_t *)priv;
|
|
char digit_string[7] = "dtmf x";
|
|
|
|
if (!relation->call->routing.routing)
|
|
return;
|
|
|
|
digit_string[5] = digit;
|
|
routing_send(&relation->call->routing, digit_string);
|
|
}
|
|
|
|
/* routing orders us to enable DTMF decoding */
|
|
static void routing_dtmf(call_t *call)
|
|
{
|
|
call_relation_t *relation = call->relation_list;
|
|
|
|
dtmf_decode_exit(&relation->dtmf_dec);
|
|
|
|
/* we add 13, because we working at speech level and not at 0dBm, wich is -13 dBm */
|
|
dtmf_decode_init(&relation->dtmf_dec, relation, recv_dtmf, 8000, db2level(6.0 + 13.0), db2level(-30.0 + 13.0));
|
|
|
|
relation->dtmf_dec_enable = 1;
|
|
}
|
|
|
|
/* routing orders us to disable DTMF decoding */
|
|
static void routing_dtmf_stop(call_t *call)
|
|
{
|
|
call_relation_t *relation = call->relation_list;
|
|
|
|
dtmf_decode_exit(&relation->dtmf_dec);
|
|
|
|
relation->dtmf_dec_enable = 0;
|
|
}
|
|
|
|
/* routing failed, release the call */
|
|
static void routing_error(call_t *call, const char *error)
|
|
{
|
|
osmo_cc_msg_t *new_msg;
|
|
call_relation_t *relation;
|
|
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "Routing script error: '%s'\n", error);
|
|
|
|
/* send message to originator */
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
|
|
osmo_cc_add_ie_cause(new_msg, cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NETWORK_OOO, 0, 0);
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
|
|
/* send message to all terminators, if any */
|
|
for (relation = call->relation_list->next; relation; relation = relation->next) {
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
|
|
osmo_cc_add_ie_cause(new_msg, cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NETWORK_OOO, 0, 0);
|
|
osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
/* routing script says something */
|
|
static void routing_say(call_t *call, const char *error)
|
|
{
|
|
char text[1024] = "";
|
|
fuer alle argv
|
|
fuege text hinzu
|
|
PDEBUG(DROUTER, DEBUG_NOTICE, "Routing script says: %s\n", text);
|
|
das script anpassen
|
|
}
|
|
#endif
|
|
|
|
void routing_receive_stdout(routing_t *routing, const char *string)
|
|
{
|
|
call_t *call = routing->call;
|
|
int argc = 0;
|
|
const char *argv[256], *token;
|
|
|
|
/* convert string into tokens */
|
|
while ((token = osmo_cc_strtok_quotes(&string)))
|
|
argv[argc++] = strdup(token);
|
|
if (!argc)
|
|
return;
|
|
|
|
if (!strcasecmp(argv[0], "rtp-proxy"))
|
|
routing_rtp_proxy(call);
|
|
else
|
|
if (!strcasecmp(argv[0], "play"))
|
|
routing_play(call, argc - 1, argv + 1);
|
|
else
|
|
if (!strcasecmp(argv[0], "play-stop"))
|
|
routing_play_stop(call);
|
|
else
|
|
if (!strcasecmp(argv[0], "record"))
|
|
routing_record(call, argc - 1, argv + 1);
|
|
else
|
|
if (!strcasecmp(argv[0], "record-stop"))
|
|
routing_record_stop(call);
|
|
else
|
|
if (!strcasecmp(argv[0], "tx-gain"))
|
|
routing_gain(call, argc - 1, argv + 1, 1);
|
|
else
|
|
if (!strcasecmp(argv[0], "rx-gain"))
|
|
routing_gain(call, argc - 1, argv + 1, 0);
|
|
else
|
|
if (!strcasecmp(argv[0], "call"))
|
|
routing_call(call, argc - 1, argv + 1);
|
|
else
|
|
if (!strcasecmp(argv[0], "call-stop"))
|
|
routing_call_stop(call);
|
|
else
|
|
if (!strcasecmp(argv[0], "overlap"))
|
|
routing_overlap(call);
|
|
else
|
|
if (!strcasecmp(argv[0], "proceeding"))
|
|
routing_proceeding(call);
|
|
else
|
|
if (!strcasecmp(argv[0], "alerting"))
|
|
routing_alerting(call);
|
|
else
|
|
if (!strcasecmp(argv[0], "answer"))
|
|
routing_answer(call);
|
|
else
|
|
if (!strcasecmp(argv[0], "disconnect"))
|
|
routing_disc_rel(call, argc - 1, argv + 1, 1);
|
|
else
|
|
if (!strcasecmp(argv[0], "release"))
|
|
routing_disc_rel(call, argc - 1, argv + 1, 0);
|
|
else
|
|
if (!strcasecmp(argv[0], "dtmf"))
|
|
routing_dtmf(call);
|
|
else
|
|
if (!strcasecmp(argv[0], "dtmf-stop"))
|
|
routing_dtmf_stop(call);
|
|
else
|
|
if (!strcasecmp(argv[0], "error"))
|
|
routing_error(call, (argc > 1) ? argv[1] : "<unknown>");
|
|
else
|
|
#if 0
|
|
if (!strcasecmp(argv[0], "say"))
|
|
routing_say(call, argc - 1, argv + 1, 0);
|
|
else
|
|
#endif
|
|
PDEBUG(DROUTER, DEBUG_ERROR, "Unknown command '%s' from routing.\n", argv[0]);
|
|
|
|
while (argc)
|
|
free((char *)argv[--argc]);
|
|
}
|
|
|
|
void routing_receive_stderr(routing_t *routing, const char *string)
|
|
{
|
|
if (routing->call->relation_list)
|
|
PDEBUG(DSTDERR, DEBUG_NOTICE, "(call #%d) Routing STDERR: %s\n", routing->call->num, string);
|
|
else
|
|
PDEBUG(DSTDERR, DEBUG_NOTICE, "Routing STDERR: %s\n", string);
|
|
}
|
|
|
|
void routing_close(routing_t *routing)
|
|
{
|
|
call_t *call = routing->call;
|
|
|
|
osmo_cc_msg_t *new_msg;
|
|
|
|
PDEBUG(DROUTER, DEBUG_INFO, "(call #%d) Routing script exitted.\n", call->num);
|
|
|
|
/* if we have a terminating call, it is fine to continue without routing process */
|
|
if (call->relation_list->next)
|
|
return;
|
|
|
|
/* in setup state we change to overlap state, so that routing can be restartet with dialing information added */
|
|
if (call->state == CALL_STATE_SETUP) {
|
|
/* change state */
|
|
new_state(call, CALL_STATE_OVERLAP);
|
|
/* forward message to originator */
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_ACK_IND);
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
return;
|
|
}
|
|
|
|
/* in overlap state we wait for more information to be added */
|
|
if (call->state == CALL_STATE_OVERLAP) {
|
|
return;
|
|
}
|
|
|
|
/* there is no way to dial more digits, so we release the call */
|
|
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
|
|
osmo_cc_add_ie_cause(new_msg, cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NO_ROUTE, 0, 0);
|
|
osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
|
|
|
|
/* destroy call */
|
|
call_destroy(call);
|
|
}
|
|
|
|
#warning add progress, if terminating call sends sdp but call state already reached
|
|
|
|
#warning beim disc muss progress geprueft werden und damit entschieden ob wir audio mitsenden sollen
|
|
|