osmo-cc-router/src/router/call.c

2132 lines
71 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 negotiation on the
* originating side is complete. If the call gets forwarded to a single or
* multiple terminating endpoints, an SDP is generated with the supported
* codecs of this router. In case of single terminating endpoint, 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_list[3];
static const char *routing_script, *routing_shell;
static struct osmo_cc_helper_audio_codecs codecs_def[] = {
{ "PCMA", 8000, 1, g711_encode_alaw, g711_decode_alaw },
{ "PCMU", 8000, 1, g711_encode_ulaw, g711_decode_ulaw },
{ "telephone-event", 8000, 1, encode_te, decode_te },
{ "L16", 8000, 1, encode_l16, decode_l16 },
{ 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;
int e;
display_status_start();
for (e = 0; cc_ep_list[e]; e++) {
for (cc_call = cc_ep_list[e]->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;
int e;
for (e = 0; cc_ep_list[e]; e++) {
for (cc_call = cc_ep_list[e]->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;
int rc;
call = calloc(1, sizeof(*call));
if (!call) {
PDEBUG(DROUTER, DEBUG_ERROR, "No memory!\n");
abort();
}
call->num = ++call_num;
call->routing.call = call;
/* allocate jitter buffer */
rc = jitter_create(&call->orig_dejitter, "tx", 8000, sizeof(sample_t), JITTER_AUDIO);
if (rc < 0)
abort();
rc = jitter_create(&call->term_dejitter, "tx", 8000, sizeof(sample_t), JITTER_AUDIO);
if (rc < 0)
abort();
/* 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);
/* playback and record */
wave_destroy_playback(&call->play);
wave_destroy_record(&call->rec);
/* 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);
/* destroy jitter buffer */
jitter_destroy(&call->orig_dejitter);
jitter_destroy(&call->term_dejitter);
/* 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;
relation = calloc(1, sizeof(*relation));
if (!relation) {
PDEBUG(DROUTER, DEBUG_ERROR, "No memory!\n");
abort();
}
relation->call = call;
/* 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;
/* 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;
}
/* 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 *cc_ep1, osmo_cc_endpoint_t *cc_ep2, const char *_routing_script, const char *_routing_shell)
{
cc_ep_list[0] = cc_ep1;
cc_ep_list[1] = cc_ep2;
cc_ep_list[2] = NULL;
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->call->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->cc_ep->session_config, relation, relation->orig_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->call->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(osmo_cc_endpoint_t *cc_ep, 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_ep = cc_ep;
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];
int complete = 0;
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);
}
/* indicate complete number to environment */
rc = osmo_cc_get_ie_complete(old_msg, 0);
if (rc)
complete = 1;
/* 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(call->relation_list->next->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, complete);
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(call->relation_list->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(call->relation_list->next->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(relation->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(call->relation_list->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(relation->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(call->relation_list->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(call->relation_list->next->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(call->relation_list->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(call->relation_list->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(call->relation_list->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(call->relation_list->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, other->cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NONSE_USER_CLR, 0, 0);
osmo_cc_ll_msg(other->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->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(call->relation_list->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(call->relation_list->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(relation->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, call->relation_list->cc_ep->serving_location, call->collect_cause, 0, 0);
osmo_cc_ll_msg(call->relation_list->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(call->relation_list->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(relation->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(call->relation_list->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(relation->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(relation->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, call->relation_list->cc_ep->serving_location, call->collect_cause, 0, 0);
osmo_cc_ll_msg(call->relation_list->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(call->relation_list->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(relation->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(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg);
return;
}
}
/* handle message from upper layer */
void cc_message(osmo_cc_endpoint_t *cc_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(cc_ep, 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->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->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 struct param {
/* rtp-proxy */
struct osmo_cc_helper_audio_codecs orig_codecs[MAX_CODECS + 1];
struct osmo_cc_helper_audio_codecs term_codecs[MAX_CODECS + 1];
int orig_given, term_given;
/* play */
char *filename, *volume, *loop;
/* gain */
char *gain;
/* call */
char *interface;
int bearer_coding, bearer_capability, bearer_mode;
char *calling;
int calling_type, calling_plan, calling_present, calling_screen, no_calling;
char *calling2;
int calling2_type, calling2_plan, calling2_present, calling2_screen, no_calling2;
char *redirecting;
int redirecting_type, redirecting_plan, redirecting_present, redirecting_screen, redirecting_reason, no_redirecting;
char *dialing;
int dialing_type, dialing_plan;
char *keypad;
/* disc/rel */
int isdn_cause, sip_cause;
/* error */
char *error;
} param;
struct param_def {
const char *n;
char **s;
int *i;
struct osmo_cc_helper_audio_codecs *o, *t;
const char *d;
int mandatory;
int no_value;
const char *(*value2name)(int value);
int (*name2value)(const char *name);
int num;
};
struct command_def {
const char *name;
void (*func)(call_t *call, int argc, char *argv[], struct command_def *command_def);
const char *description;
struct param_def *param_list;
} command_def[];
static void add_codecs_by_names(struct osmo_cc_helper_audio_codecs *codecs, char *names)
{
int c = 0, i;
const char *name;
while ((name = strsep(&names, ","))) {
for (i = 0; codecs_def[i].payload_name; i++) {
if (!strcasecmp(name, codecs_def[i].payload_name)) {
if (c == MAX_CODECS) {
PDEBUG(DROUTER, DEBUG_ERROR, "Maximum of %d codecs are reached. Ignoring codec '%s'.\n", c, name);
break;
}
memcpy(&codecs[c], &codecs_def[i], sizeof(*codecs));
PDEBUG(DROUTER, DEBUG_INFO, "Adding given codec '%s'.\n", codecs[c].payload_name);
c++;
break;
}
}
if (!codecs_def[i].payload_name) {
PDEBUG(DROUTER, DEBUG_ERROR, "Given codec '%s' not supported!\n", name);
}
}
memset(&codecs[c], 0, sizeof(*codecs));
}
static int parse_params(int argc, char *argv[], struct command_def *command_def)
{
struct param_def *param_def = command_def->param_list;
int i, j;
char *arg;
/* clear parameter set */
memset(&param, 0, sizeof(param));
/* no parameter list given */
if (param_def == NULL)
return -EINVAL;
/* loop through all arguments and stop if there is a ':' */
for (i = 0; i < argc && argv[i][0] != ':'; i++) {
arg = argv[i];
/* loop through all possible parameter definitions */
for (j = 0; param_def[j].n; j++) {
/* stop if parameter has been found */
if (!strncasecmp(arg, param_def[j].n, strlen(param_def[j].n))) {
arg += strlen(param_def[j].n);
/* no value */
if (*arg == '\0') {
arg = "";
break;
}
/* has value */
if (*arg == '=') {
arg++;
break;
}
/* continue, if more digits given */
}
}
if (!param_def[j].n) {
PDEBUG(DROUTER, DEBUG_ERROR, "Unknown '%s' parameter '%s' from routing.\n", command_def->name, argv[i]);
continue;
}
if (arg[0] && param_def[j].no_value) {
PDEBUG(DROUTER, DEBUG_ERROR, "'%s' parameter '%s' has no value, ignoring.\n", command_def->name, argv[i]);
continue;
}
if (param_def[j].no_value)
arg = "1";
if (param_def[j].i) {
if (param_def[j].name2value) {
*param_def[j].i = param_def[j].name2value(arg);
if (*param_def[j].i < 0)
*param_def[j].i = atoi(arg);
} else
*param_def[j].i = atoi(arg);
}
if (param_def[j].s)
*param_def[j].s = arg;
if (param_def[j].o) {
PDEBUG(DROUTER, DEBUG_INFO, "Originating codecs given: '%s'.\n", arg);
add_codecs_by_names(param_def[j].o, arg);
param.orig_given = 1;
}
if (param_def[j].t) {
PDEBUG(DROUTER, DEBUG_INFO, "Terminating codecs given: '%s'.\n", arg);
add_codecs_by_names(param_def[j].t, arg);
param.term_given = 1;
}
}
return i;
}
/* routing orders us to activate rtp proxy */
struct param_def param_rtp_proxy[] = {
{ .n = "orig-codecs", .o = param.orig_codecs, .d = "Define allowed codecs to be accepted by originating endpoint." },
{ .n = "term-codecs", .t = param.term_codecs, .d = "Define allowed codecs on terminating endpoint in order of preference." },
{ .n = NULL }
};
static void routing_rtp_proxy(call_t *call, int argc, char *argv[], struct command_def *command_def)
{
call_relation_t *relation = call->relation_list;
/* ignore, if already enabled */
if (call->rtp_proxy) {
PDEBUG(DROUTER, DEBUG_NOTICE, "RTP proxy is already enabled!.\n");
return;
}
parse_params(argc, argv, command_def);
if (!param.orig_given) {
memcpy(&relation->orig_codecs[0], &codecs_def[0], sizeof(relation->orig_codecs[0]));
memcpy(&relation->orig_codecs[1], &codecs_def[1], sizeof(relation->orig_codecs[1]));
memcpy(&relation->orig_codecs[2], &codecs_def[2], sizeof(relation->orig_codecs[2]));
memset(&relation->orig_codecs[3], 0, sizeof(relation->orig_codecs[3]));
PDEBUG(DROUTER, DEBUG_INFO, "No originating codeds given, using default '%s' and '%s' and '%s'.\n", relation->orig_codecs[0].payload_name, relation->orig_codecs[1].payload_name, relation->orig_codecs[2].payload_name);
} else
memcpy(relation->orig_codecs, param.orig_codecs, sizeof(param.orig_codecs));
if (!param.term_given) {
memcpy(&relation->term_codecs[0], &codecs_def[0], sizeof(relation->term_codecs[0]));
memcpy(&relation->term_codecs[1], &codecs_def[1], sizeof(relation->term_codecs[1]));
memset(&relation->term_codecs[2], 0, sizeof(relation->term_codecs[2]));
PDEBUG(DROUTER, DEBUG_INFO, "No terminating codeds given, using default '%s' and '%s'.\n", relation->term_codecs[0].payload_name, relation->term_codecs[1].payload_name);
} else
memcpy(relation->term_codecs, param.term_codecs, sizeof(param.term_codecs));
/* 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;
}
call->rtp_proxy = 1;
}
/* routing orders us to play a wave file */
struct param_def param_play[] = {
{ .n = "filename", .s = &param.filename, .d = "file name of audio file", .mandatory = 1 },
{ .n = "volume", .s = &param.volume, .d = "speech level, which is '1.0'" },
{ .n = "loop", .s = &param.loop, .d = "play in an endless loop", .no_value = 1 },
{ .n = NULL }
};
static void routing_play(call_t *call, int argc, char *argv[], struct command_def *command_def)
{
int samplerate = 8000, channels = 0;
double deviation;
int rc;
wave_destroy_playback(&call->play);
if (!call->rtp_proxy) {
PDEBUG(DROUTER, DEBUG_ERROR, "RTP-Proxy must be enabled to play a file!.\n");
return;
}
parse_params(argc, argv, command_def);
if (!param.filename) {
PDEBUG(DROUTER, DEBUG_ERROR, "'play' command reqires a file name as parameter.\n");
return;
}
if (!param.volume)
param.volume = "1.0";
deviation = 1.0 / SPEECH_LEVEL * atof(param.volume);
rc = wave_create_playback(&call->play, param.filename, &samplerate, &channels, deviation);
if (rc < 0)
return;
strncpy(call->play_filename, param.filename, sizeof(call->play_filename) - 1);
call->play_deviation = deviation;
if (channels != 1 && channels != 2) {
wave_destroy_playback(&call->play);
PDEBUG(DROUTER, DEBUG_ERROR, "'play' command reqires a wave file that has 1 or 2 channels only.\n");
return;
}
if (param.loop)
call->play_loop = 1;
}
/* routing orders us stop playing a wave file */
static void routing_play_stop(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def)
{
wave_destroy_playback(&call->play);
}
/* routing orders us to record a wave file */
struct param_def param_record[] = {
{ .n = "filename", .s = &param.filename, .d = "file name of audio file", .mandatory = 1 },
{ .n = "volume", .s = &param.volume, .d = "speech level, which is '1.0'" },
{ .n = NULL }
};
static void routing_record(call_t *call, int argc, char *argv[], struct command_def *command_def)
{
int samplerate = 8000, channels = 2;
int rc;
wave_destroy_record(&call->rec);
if (!call->rtp_proxy) {
PDEBUG(DROUTER, DEBUG_ERROR, "RTP-Proxy must be enabled to record a file!.\n");
return;
}
parse_params(argc, argv, command_def);
if (!param.filename) {
PDEBUG(DROUTER, DEBUG_ERROR, "'record' command reqires a file name as parameter.\n");
return;
}
if (!param.volume)
param.volume = "1.0";
rc = wave_create_record(&call->rec, param.filename, samplerate, channels, 1.0 / SPEECH_LEVEL / atof(param.volume));
if (rc < 0)
return;
}
/* routing orders us stop recording a wave file */
static void routing_record_stop(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def)
{
wave_destroy_record(&call->rec);
}
/* routing orders us to set local gain */
struct param_def param_gain[] = {
{ .n = "gain", .s = &param.gain, .d = "gain in dB" },
{ .n = NULL }
};
static void routing_tx_gain(call_t *call, int argc, char *argv[], struct command_def *command_def)
{
if (!call->rtp_proxy) {
PDEBUG(DROUTER, DEBUG_ERROR, "RTP-Proxy must be enabled to record a file!\n");
return;
}
parse_params(argc, argv, command_def);
if (!param.gain) {
PDEBUG(DROUTER, DEBUG_ERROR, "'tx-gain' command reqires a gain value as parameter.\n");
return;
}
call->tx_gain = atof(param.gain);
}
static void routing_rx_gain(call_t *call, int argc, char *argv[], struct command_def *command_def)
{
if (!call->rtp_proxy) {
PDEBUG(DROUTER, DEBUG_ERROR, "RTP-Proxy must be enabled to record a file!\n");
return;
}
parse_params(argc, argv, command_def);
if (!param.gain) {
PDEBUG(DROUTER, DEBUG_ERROR, "'rx-gain' command reqires a gain value as parameter.\n");
return;
}
call->rx_gain = atof(param.gain);
}
/* routing orders us to call remote end */
struct param_def param_call[] = {
{ .n = "interface", .s = &param.interface, .d = "name of interface to route the call to", .mandatory = 1},
{ .n = "bearer-coding", .i = &param.bearer_coding, .d = "coding of bearer capability", .name2value = osmo_cc_coding_name2value, .value2name = osmo_cc_coding_value2name, .num = OSMO_CC_CODING_NUM },
{ .n = "bearer-capability", .i = &param.bearer_capability, .d = "bearer capability value", .name2value = osmo_cc_capability_name2value, .value2name = osmo_cc_capability_value2name, .num = OSMO_CC_CAPABILITY_NUM },
{ .n = "bearer-mode", .i = &param.bearer_mode, .d = "bearer mode", .name2value = osmo_cc_mode_name2value, .value2name = osmo_cc_mode_value2name, .num = OSMO_CC_MODE_NUM },
{ .n = "calling", .s = &param.calling, .d = "calling party number" },
{ .n = "calling-type", .i = &param.calling_type, .d = "type of calling party number", .name2value = osmo_cc_type_name2value, .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM},
{ .n = "calling-plan", .i = &param.calling_plan, .d = "numbering plan of calling party number", .name2value = osmo_cc_plan_name2value, .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM },
{ .n = "calling-present", .i = &param.calling_present, .d = "presentation of calling party number", .name2value = osmo_cc_present_name2value, .value2name = osmo_cc_present_value2name, .num = OSMO_CC_PRESENT_NUM },
{ .n = "calling-screen", .i = &param.calling_screen, .d = "screening indicator of calling party number", .name2value = osmo_cc_screen_name2value, .value2name = osmo_cc_screen_value2name, .num = OSMO_CC_SCREEN_NUM },
{ .n = "no-calling", .i = &param.no_calling, .d = "disable calling party number", .no_value = 1},
{ .n = "calling2", .s = &param.calling2, .d = "second calling party number" },
{ .n = "calling2-type", .i = &param.calling2_type, .d = "type of second calling party number", .name2value = osmo_cc_type_name2value, .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM},
{ .n = "calling2-plan", .i = &param.calling2_plan, .d = "numbering plan of second calling party number", .name2value = osmo_cc_plan_name2value, .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM },
{ .n = "calling2-present", .i = &param.calling2_present, .d = "presentation of second calling party number", .name2value = osmo_cc_present_name2value, .value2name = osmo_cc_present_value2name, .num = OSMO_CC_PRESENT_NUM },
{ .n = "calling2-screen", .i = &param.calling2_screen, .d = "screening indicator of second calling party number", .name2value = osmo_cc_screen_name2value, .value2name = osmo_cc_screen_value2name, .num = OSMO_CC_SCREEN_NUM },
{ .n = "no-calling2", .i = &param.no_calling2, .d = "disable seconds calling party number", .no_value = 1 },
{ .n = "redirecting", .s = &param.redirecting, .d = "redirecting number" },
{ .n = "redirecting-type", .i = &param.redirecting_type, .d = "type of redirecting number", .name2value = osmo_cc_type_name2value, .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM },
{ .n = "redirecting-plan", .i = &param.redirecting_plan, .d = "numbering plan of redirecting number", .name2value = osmo_cc_plan_name2value, .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM },
{ .n = "redirecting-present", .i = &param.redirecting_present, .d = "presentation of redirecting number", .name2value = osmo_cc_present_name2value, .value2name = osmo_cc_present_value2name, .num = OSMO_CC_PRESENT_NUM },
{ .n = "redirecting-screen", .i = &param.redirecting_screen, .d = "screening indicator of redirecting number", .name2value = osmo_cc_screen_name2value, .value2name = osmo_cc_screen_value2name, .num = OSMO_CC_SCREEN_NUM },
{ .n = "redirecting-reason", .i = &param.redirecting_reason, .d = "reason for redirecting the call", .name2value = osmo_cc_redir_reason_name2value, .value2name = osmo_cc_redir_reason_value2name, .num = OSMO_CC_REDIR_REASON_NUM },
{ .n = "no-redirecting", .i = &param.no_redirecting, .d = "disable redirecting number", .no_value = 1 },
{ .n = "dialing", .s = &param.dialing, .d = "alter dialed number" },
{ .n = "dialing-type", .i = &param.dialing_type, .d = "type of dialed number", .name2value = osmo_cc_type_name2value, .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM },
{ .n = "dialing-plan", .i = &param.dialing_plan, .d = "numbering plan of dialed number", .name2value = osmo_cc_plan_name2value, .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM },
{ .n = "keypad", .s = &param.keypad, .d = "keypulse digit or digits (might be required by some ISDN applications)" },
{ .n = NULL }
};
static void routing_call(call_t *call, int argc, char *argv[], struct command_def *command_def)
{
osmo_cc_call_t *att = NULL;
uint8_t coding, capability, mode;
uint8_t type, plan, present, screen, reason;
char number[256];
int e, 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:
i = parse_params(argc, argv, command_def);
/* if more calls, then forward arguments behind colon, otherwise set argc to 0 */
if (i >= 0 && i < argc) {
argv += i + 1;
argc -= i + 1;
} else
argc = 0;
if (!param.interface) {
PDEBUG(DROUTER, DEBUG_ERROR, "'call' command without 'interface' parameter.\n");
goto try_next;
}
for (e = 0; cc_ep_list[e]; e++) {
att = osmo_cc_get_attached_interface(cc_ep_list[e], param.interface);
if (att)
break;
}
if (!att) {
PDEBUG(DROUTER, DEBUG_ERROR, "'call' command with 'interface' parameter '%s' which is not attached.\n", param.interface);
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(att->ep);
call_relation_t *relation = relation_create(call);
/* link with cc */
relation->cc_ep = att->ep;
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, param.interface);
/* bearer capability */
rc = osmo_cc_get_ie_bearer(setup_msg, 0, &coding, &capability, &mode);
if (rc >= 0 || param.bearer_coding || param.bearer_capability || param.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 (param.bearer_coding)
coding = param.bearer_coding;
if (param.bearer_capability)
capability = param.bearer_capability;
if (param.bearer_mode)
mode = param.bearer_coding;
osmo_cc_add_ie_bearer(new_msg, coding, capability, mode);
}
/* calling */
if (!param.no_calling) {
rc = osmo_cc_get_ie_calling(setup_msg, 0, &type, &plan, &present, &screen, number, sizeof(number));
if (rc >= 0 || param.calling_type || param.calling_plan || param.calling_present || param.calling_screen || param.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 (param.calling_type)
type = param.calling_type;
if (param.calling_plan)
plan = param.calling_plan;
if (param.calling_present)
present = param.calling_present;
if (param.calling_screen)
screen = param.calling_screen;
if (!param.calling)
param.calling = number;
osmo_cc_add_ie_calling(new_msg, type, plan, present, screen, param.calling);
}
}
if (!param.no_calling && !param.no_calling2) {
rc = osmo_cc_get_ie_calling(setup_msg, 1, &type, &plan, &present, &screen, number, sizeof(number));
if (rc >= 0 || param.calling2_type || param.calling2_plan || param.calling2_present || param.calling2_screen || param.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 (param.calling2_type)
type = param.calling2_type;
if (param.calling2_plan)
plan = param.calling2_plan;
if (param.calling2_present)
present = param.calling2_present;
if (param.calling2_screen)
screen = param.calling2_screen;
if (!param.calling2)
param.calling2 = number;
osmo_cc_add_ie_calling(new_msg, type, plan, present, screen, param.calling2);
}
}
/* redirecting */
if (!param.no_redirecting) {
rc = osmo_cc_get_ie_redir(setup_msg, 0, &type, &plan, &present, &screen, &reason, number, sizeof(number));
if (rc >= 0 || param.redirecting_type || param.redirecting_plan || param.redirecting_present || param.redirecting_screen || param.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 (param.redirecting_type)
type = param.redirecting_type;
if (param.redirecting_plan)
plan = param.redirecting_plan;
if (param.redirecting_present)
present = param.redirecting_present;
if (param.redirecting_screen)
screen = param.redirecting_screen;
if (!param.redirecting)
param.redirecting = number;
osmo_cc_add_ie_redir(new_msg, type, plan, present, screen, reason, param.redirecting);
}
}
/* dialing */
rc = osmo_cc_get_ie_called(setup_msg, 0, &type, &plan, number, sizeof(number));
if (rc >= 0 || param.dialing_type || param.dialing_plan || param.dialing) {
/* if not preset, use default values */
if (rc < 0) {
type = OSMO_CC_TYPE_UNKNOWN;
plan = OSMO_CC_PLAN_TELEPHONY;
}
/* alter values */
if (param.dialing_type)
type = param.dialing_type;
if (param.dialing_plan)
plan = param.dialing_plan;
if (!param.dialing)
param.dialing = "";
osmo_cc_add_ie_called(new_msg, type, plan, param.dialing);
}
/* keypad */
if (param.keypad) {
if (param.keypad[0])
osmo_cc_add_ie_keypad(new_msg, param.keypad);
}
/* only if RTP-Proxy is used */
if (call->rtp_proxy) {
PDEBUG(DROUTER, DEBUG_DEBUG, "Sending our codecs to the terminator.\n");
relation->cc_session = osmo_cc_helper_audio_offer(&relation->cc_ep->session_config, relation, call->relation_list->term_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(relation->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, (param.interface) ? : "", sizeof(relation->interface) - 1);
strncpy(relation->id, (param.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, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def)
{
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, relation->cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR, 0, 0);
osmo_cc_ll_msg(relation->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, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def)
{
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(relation->cc_ep, relation->cc_callref, new_msg);
}
/* routing orders us to set call into proceeding state */
static void routing_proceeding(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def)
{
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(relation->cc_ep, relation->cc_callref, new_msg);
}
/* routing orders us to set call into alerting state */
static void routing_alerting(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def)
{
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(relation->cc_ep, relation->cc_callref, new_msg);
}
/* routing orders us to set call into answer state */
static void routing_answer(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def)
{
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(relation->cc_ep, relation->cc_callref, new_msg);
}
/* routing orders us to dsiconnect/release the call */
struct param_def param_disc_rel[] = {
{ .n = "isdn-cause", .i = &param.isdn_cause, .d = "ISDN cause number (Will be generated, if omitted.)" },
{ .n = "sip-cause", .i = &param.sip_cause, .d = "SIP cause number (Will be generated, if omitted.)" },
{ .n = NULL }
};
static void routing_disconnect(call_t *call, int argc, char *argv[], struct command_def *command_def)
{
call_relation_t *relation = call->relation_list;
osmo_cc_msg_t *new_msg;
parse_params(argc, argv, command_def);
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-DISC-IND to originator.\n", relation_name(relation));
/* send message to originator */
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_DISC_IND);
/* send SDP answer */
proxy_send_sdp_answer(relation, new_msg);
/* add cause */
osmo_cc_add_ie_cause(new_msg, relation->cc_ep->serving_location, param.isdn_cause, param.sip_cause, 0);
osmo_cc_ll_msg(relation->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, relation->cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR, 0, 0);
osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg);
}
}
static void routing_release(call_t *call, int argc, char *argv[], struct command_def *command_def)
{
call_relation_t *relation = call->relation_list;
osmo_cc_msg_t *new_msg;
parse_params(argc, argv, command_def);
PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-REL-IND to originator.\n", relation_name(relation));
/* send message to originator */
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
/* add cause */
osmo_cc_add_ie_cause(new_msg, relation->cc_ep->serving_location, param.isdn_cause, param.sip_cause, 0);
osmo_cc_ll_msg(relation->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, relation->cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR, 0, 0);
osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg);
}
/* 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, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def)
{
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, which 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, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def)
{
call_relation_t *relation = call->relation_list;
dtmf_decode_exit(&relation->dtmf_dec);
relation->dtmf_dec_enable = 0;
}
/* routing failed, release the call */
struct param_def param_error[] = {
{ .n = "string", .s = &param.error, .d = "error string to be displayed", .mandatory = 1 },
{ .n = NULL }
};
static void routing_error(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def)
{
osmo_cc_msg_t *new_msg;
call_relation_t *relation;
parse_params(argc, argv, command_def);
if (!param.error) {
PDEBUG(DROUTER, DEBUG_ERROR, "'error' command reqires an error string as parameter.\n");
return;
}
PDEBUG(DROUTER, DEBUG_ERROR, "Routing script error: '%s'\n", param.error);
/* send message to originator */
new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
osmo_cc_add_ie_cause(new_msg, call->relation_list->cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NETWORK_OOO, 0, 0);
osmo_cc_ll_msg(call->relation_list->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, relation->cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NETWORK_OOO, 0, 0);
osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg);
}
}
struct command_def command_def[] = {
{ "rtp-proxy", routing_rtp_proxy, "Turn on RTP proxy, so that audio processing is possible.", param_rtp_proxy },
{ "play", routing_play, "Play given audio file.", param_play },
{ "play-stop", routing_play_stop, "Stop playing audio file.", NULL },
{ "record", routing_record, "Record to given audio file.", param_record },
{ "record-stop", routing_record_stop, "Stop recording audio file.", NULL },
{ "tx-gain", routing_tx_gain, "Set gain of audio sent to originating interface.", param_gain },
{ "rx-gain", routing_rx_gain, "Set gain of audio received from originating interface.", param_gain },
{ "call", routing_call, "Make one or mulriple calls to given interface.", param_call },
{ "call-stop", routing_call_stop, "Stop outgoing call(s).", NULL },
{ "overlap", routing_overlap, "Set caller into overlap state, digit may be dialed.", NULL },
{ "proceeding", routing_proceeding, "Set caller into proceeding state, no digits may be dialed.", NULL },
{ "alerting", routing_alerting, "Set caller into alerting state.", NULL },
{ "answer", routing_answer, "Answer call from caller.", NULL },
{ "disconnect", routing_disconnect, "Disconnect call towards caller and release callee, if any.", param_disc_rel },
{ "release", routing_release, "Release call towards caller and release callee, if any.", param_disc_rel },
{ "dtmf", routing_dtmf, "Turn on DTMF detection.", NULL },
{ "dtmf-stop", routing_dtmf_stop, "Turn off DTMF detection.", NULL },
{ "error", routing_error, "Display error message.", param_error },
{ NULL, NULL, NULL, NULL }
};
void routing_receive_stdout(routing_t *routing, const char *string)
{
call_t *call = routing->call;
int argc = 0;
char *argv[256], *token;
int i;
/* convert string into tokens */
while ((token = osmo_cc_strtok_quotes(&string)))
argv[argc++] = strdup(token);
if (!argc)
return;
for (i = 0; command_def[i].name; i++) {
if (!strcasecmp(argv[0], command_def[i].name)) {
command_def[i].func(call, argc - 1, argv + 1, &command_def[i]);
break;
}
}
if (!command_def[i].name)
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(call->relation_list->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, call->relation_list->cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NO_ROUTE, 0, 0);
osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg);
/* destroy call */
call_destroy(call);
}
void telephone_event(call_relation_t *relation, uint8_t marker, struct telephone_event *te)
{
char digit_string[7] = "dtmf x";
if (te->event < 16) {
if (marker && te->volume <= 36) {
if (!te->e) {
PDEBUG(DROUTER, DEBUG_INFO, "Received start of Telephone-Event '%d' duration=%d ms (marker set)\n", te->event, te->duration / 8);
relation->te_started = 1;
} else
PDEBUG(DROUTER, DEBUG_INFO, "Received start and end of Telephone-Event '%d' duration=%d ms (marker set)\n", te->event, te->duration / 8);
digit_string[5] = "0123456789*#ABCD"[te->event];
} else if (!relation->te_started && !te->e && te->volume <= 36) {
PDEBUG(DROUTER, DEBUG_INFO, "Received start of Telephone-Event '%d' duration=%d ms (marker not set, this is wrong!)\n", te->event, te->duration / 8);
relation->te_started = 1;
digit_string[5] = "0123456789*#ABCD"[te->event];
} else if (relation->te_started && !te->e && te->volume <= 36) {
PDEBUG(DROUTER, DEBUG_DEBUG, "Received subsequent Telephone-Event '%d' duration=%d ms\n", te->event, te->duration / 8);
} else if (relation->te_started && te->e) {
PDEBUG(DROUTER, DEBUG_INFO, "Received end of Telephone-Event '%d'\n", te->event);
relation->te_started = 0;
} else if (!relation->te_started && te->e) {
PDEBUG(DROUTER, DEBUG_DEBUG, "Received subsequent end of Telephone-Event '%d'\n", te->event);
}
} else
PDEBUG(DROUTER, DEBUG_INFO, "Received unsupported Telephone-Event '%d'\n", te->event);
if (!relation->call->routing.routing)
return;
if (digit_string[5] != 'x')
routing_send(&relation->call->routing, digit_string);
}
void routing_help(void)
{
int i, j, k;
printf("Available routing commands:\n\n");
for (i = 0; command_def[i].name; i++) {
struct param_def *param_def = command_def[i].param_list;
printf("Command: %s\n", command_def[i].name);
printf(" Description: %s\n", command_def[i].description);
if (!param_def) {
printf(" This command does not have any parameters.\n");
continue;
}
for (j = 0; param_def[j].n; j++) {
printf(" Parameter: %s%s\n", param_def[j].n, (param_def[j].mandatory) ? " (manatory)" : "");
printf(" Description: %s\n", param_def[j].d);
if (param_def[j].o || param_def[j].t) {
for (k = 0; codecs_def[k].payload_name; k++) {
printf(" Value: '%s'\n", codecs_def[k].payload_name);
}
} else if (param_def[j].no_value) {
printf(" No value\n");
} else if (param_def[j].i && param_def[j].value2name) {
for (k = 0; k < param_def[j].num; k++) {
const char *name = param_def[j].value2name(k);
if (name && name[0] != '<')
printf(" Value: '%d' or '%s'\n", k, name);
}
}
}
}
}
#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