1740 lines
57 KiB
C
Raw Normal View History

/*
* Copyright (c) 2009, Moises Silva <moy@sangoma.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of the original author; nor the names of any contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef __linux__
#ifndef _BSD_SOURCE
#define _BSD_SOURCE /* for strsep() */
#endif
#include <syscall.h>
#include <poll.h>
#include <string.h>
#endif
#include <stdio.h>
#include <openr2.h>
#include "freetdm.h"
#include "private/ftdm_core.h"
/* debug thread count for r2 legs */
static ftdm_mutex_t* g_thread_count_mutex;
static int32_t g_thread_count = 0;
typedef int openr2_call_status_t;
/* when the users kills a span we clear this flag to kill the signaling thread */
/* FIXME: what about the calls that are already up-and-running? */
typedef enum {
FTDM_R2_RUNNING = (1 << 0),
} ftdm_r2_flag_t;
typedef enum {
FTDM_R2_WAITING_ACK = (1 << 0),
} ftdm_r2_call_flag_t;
/* private call information stored in ftdmchan->call_data void* ptr */
#define R2CALL(ftdmchan) ((ftdm_r2_call_t*)((ftdmchan)->call_data))
typedef struct ftdm_r2_call_t {
openr2_chan_t *r2chan;
ftdm_r2_call_flag_t flags;
int accepted:1;
int answer_pending:1;
int state_ack_pending:1;
int disconnect_rcvd:1;
int ftdm_started:1;
ftdm_channel_state_t chanstate;
ftdm_size_t dnis_index;
ftdm_size_t ani_index;
char name[10];
unsigned long txdrops;
} ftdm_r2_call_t;
/* this is just used as place holder in the stack when configuring the span to avoid using bunch of locals */
typedef struct ft_r2_conf_s {
/* openr2 types */
openr2_variant_t variant;
openr2_calling_party_category_t category;
openr2_log_level_t loglevel;
/* strings */
char *logdir;
char *advanced_protocol_file;
/* ints */
int32_t max_ani;
int32_t max_dnis;
int32_t mfback_timeout;
int32_t metering_pulse_timeout;
/* booleans */
int immediate_accept;
int skip_category;
int get_ani_first;
int call_files;
int mf_files;
int double_answer;
int charge_calls;
int forced_release;
int allow_collect_calls;
} ft_r2_conf_t;
/* r2 configuration stored in span->signal_data */
typedef struct ftdm_r2_data_s {
/* span flags */
ftdm_r2_flag_t flags;
/* openr2 handle for the R2 variant context */
openr2_context_t *r2context;
/* category to use when making calls */
openr2_calling_party_category_t category;
/* whether to use OR2_CALL_WITH_CHARGE or OR2_CALL_NO_CHARGE when accepting a call */
int charge_calls:1;
/* allow or reject collect calls */
int allow_collect_calls:1;
/* whether to use forced release when hanging up */
int forced_release:1;
/* whether accept the call when offered, or wait until the user decides to accept */
int accept_on_offer:1;
/* max time spent in ms doing real work in a single loop */
int jobmax;
/* total working loops */
unsigned long loops;
/* LWP */
unsigned long monitor_thread_id;
} ftdm_r2_data_t;
/* one element per span will be stored in g_mod_data_hash global var to keep track of them
and destroy them on module unload */
typedef struct ftdm_r2_span_pvt_s {
openr2_context_t *r2context; /* r2 context allocated for this span */
ftdm_hash_t *r2calls; /* hash table of allocated call data per channel for this span */
} ftdm_r2_span_pvt_t;
/* span monitor thread */
static void *ftdm_r2_run(ftdm_thread_t *me, void *obj);
/* hash of all the private span allocations
we need to keep track of them to destroy them when unloading the module
since freetdm does not notify signaling modules when destroying a span
span -> ftdm_r2_mod_allocs_t */
static ftdm_hash_t *g_mod_data_hash;
/* IO interface for the command API */
static ftdm_io_interface_t g_ftdm_r2_interface;
static int ftdm_r2_state_advance(ftdm_channel_t *ftdmchan);
/* functions not available on windows */
#ifdef WIN32
#include <mmsystem.h>
static __inline int gettimeofday(struct timeval *tp, void *nothing)
{
#ifdef WITHOUT_MM_LIB
SYSTEMTIME st;
time_t tt;
struct tm tmtm;
/* mktime converts local to UTC */
GetLocalTime (&st);
tmtm.tm_sec = st.wSecond;
tmtm.tm_min = st.wMinute;
tmtm.tm_hour = st.wHour;
tmtm.tm_mday = st.wDay;
tmtm.tm_mon = st.wMonth - 1;
tmtm.tm_year = st.wYear - 1900; tmtm.tm_isdst = -1;
tt = mktime (&tmtm);
tp->tv_sec = tt;
tp->tv_usec = st.wMilliseconds * 1000;
#else
/**
** The earlier time calculations using GetLocalTime
** had a time resolution of 10ms.The timeGetTime, part
** of multimedia apis offer a better time resolution
** of 1ms.Need to link against winmm.lib for this
**/
unsigned long Ticks = 0;
unsigned long Sec =0;
unsigned long Usec = 0;
Ticks = timeGetTime();
Sec = Ticks/1000;
Usec = (Ticks - (Sec*1000))*1000;
tp->tv_sec = Sec;
tp->tv_usec = Usec;
#endif /* WITHOUT_MM_LIB */
(void)nothing;
return 0;
}
static char *strsep(char **stringp, const char *delim)
{
char *start = *stringp;
char *ptr;
if (!start)
return NULL;
if (!*delim)
ptr = start + strlen(start);
else {
ptr = strpbrk(start, delim);
if (!ptr) {
*stringp = NULL;
return start;
}
}
*ptr = '\0';
*stringp = ptr + 1;
return start;
}
#endif /* WIN32 */
static void ftdm_r2_set_chan_sig_status(ftdm_channel_t *ftdmchan, ftdm_signaling_status_t status)
{
ftdm_sigmsg_t sig;
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "Signalling link status changed to %s\n", ftdm_signaling_status2str(status));
memset(&sig, 0, sizeof(sig));
sig.chan_id = ftdmchan->chan_id;
sig.span_id = ftdmchan->span_id;
sig.channel = ftdmchan;
sig.event_id = FTDM_SIGEVENT_SIGSTATUS_CHANGED;
sig.sigstatus = status;
if (ftdm_span_send_signal(ftdmchan->span, &sig) != FTDM_SUCCESS) {
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "Failed to change channel status to %s\n", ftdm_signaling_status2str(status));
}
return;
}
static ftdm_call_cause_t ftdm_r2_cause_to_ftdm_cause(ftdm_channel_t *fchan, openr2_call_disconnect_cause_t cause)
{
switch (cause) {
case OR2_CAUSE_NORMAL_CLEARING:
return FTDM_CAUSE_NORMAL_CLEARING;
case OR2_CAUSE_BUSY_NUMBER:
return FTDM_CAUSE_USER_BUSY;
case OR2_CAUSE_NETWORK_CONGESTION:
return FTDM_CAUSE_SWITCH_CONGESTION;
case OR2_CAUSE_UNALLOCATED_NUMBER:
return FTDM_CAUSE_NO_ROUTE_DESTINATION;
case OR2_CAUSE_NUMBER_CHANGED:
return FTDM_CAUSE_NUMBER_CHANGED;
case OR2_CAUSE_OUT_OF_ORDER:
return FTDM_CAUSE_NETWORK_OUT_OF_ORDER;
case OR2_CAUSE_NO_ANSWER:
return FTDM_CAUSE_NO_ANSWER;
case OR2_CAUSE_UNSPECIFIED:
return FTDM_CAUSE_NORMAL_UNSPECIFIED;
case OR2_CAUSE_COLLECT_CALL_REJECTED:
return FTDM_CAUSE_CALL_REJECTED;
case OR2_CAUSE_FORCED_RELEASE:
return FTDM_CAUSE_NORMAL_CLEARING;
}
ftdm_log_chan(fchan, FTDM_LOG_WARNING, "Mapping openr2 cause %d to unspecified\n", cause);
return FTDM_CAUSE_NORMAL_UNSPECIFIED;
}
static openr2_call_disconnect_cause_t ftdm_r2_ftdm_cause_to_openr2_cause(ftdm_channel_t *fchan)
{
switch (fchan->caller_data.hangup_cause) {
case FTDM_CAUSE_NORMAL_CLEARING:
return OR2_CAUSE_NORMAL_CLEARING;
case FTDM_CAUSE_USER_BUSY:
return OR2_CAUSE_BUSY_NUMBER;
case FTDM_CAUSE_SWITCH_CONGESTION:
return OR2_CAUSE_NETWORK_CONGESTION;
case FTDM_CAUSE_NO_ROUTE_DESTINATION:
return OR2_CAUSE_UNALLOCATED_NUMBER;
case FTDM_CAUSE_NUMBER_CHANGED:
return OR2_CAUSE_NUMBER_CHANGED;
case FTDM_CAUSE_NETWORK_OUT_OF_ORDER:
case FTDM_CAUSE_SERVICE_UNAVAILABLE:
return OR2_CAUSE_OUT_OF_ORDER;
case FTDM_CAUSE_NO_ANSWER:
case FTDM_CAUSE_NO_USER_RESPONSE:
return OR2_CAUSE_NO_ANSWER;
case FTDM_CAUSE_NORMAL_UNSPECIFIED:
return OR2_CAUSE_UNSPECIFIED;
}
ftdm_log_chan(fchan, FTDM_LOG_WARNING, "freetdm hangup cause %d mapped to openr2 cause %s\n",
fchan->caller_data.hangup_cause, openr2_proto_get_disconnect_string(OR2_CAUSE_UNSPECIFIED));
return OR2_CAUSE_UNSPECIFIED;
}
static void ft_r2_clean_call(ftdm_r2_call_t *call)
{
openr2_chan_t *r2chan = call->r2chan;
memset(call, 0, sizeof(*call));
call->r2chan = r2chan;
}
static void ft_r2_accept_call(ftdm_channel_t *ftdmchan)
{
openr2_chan_t *r2chan = R2CALL(ftdmchan)->r2chan;
// FIXME: not always accept as no charge, let the user decide that
// also we should check the return code from openr2_chan_accept_call and handle error condition
// hanging up the call with protocol error as the reason, this openr2 API will fail only when there something
// wrong at the I/O layer or the library itself
openr2_chan_accept_call(r2chan, OR2_CALL_NO_CHARGE);
}
static void ft_r2_answer_call(ftdm_channel_t *ftdmchan)
{
openr2_chan_t *r2chan = R2CALL(ftdmchan)->r2chan;
// FIXME
// 1. check openr2_chan_answer_call return code
// 2. The openr2_chan_answer_call_with_mode should be used depending on user settings
// openr2_chan_answer_call_with_mode(r2chan, OR2_ANSWER_SIMPLE);
openr2_chan_answer_call(r2chan);
R2CALL(ftdmchan)->answer_pending = 0;
}
/* this function must be called with the chan mutex held! */
static FIO_CHANNEL_OUTGOING_CALL_FUNCTION(r2_outgoing_call)
{
openr2_call_status_t callstatus;
ftdm_r2_data_t *r2data;
r2data = ftdmchan->span->signal_data;
if (ftdmchan->state != FTDM_CHANNEL_STATE_DOWN) {
/* collision, an incoming seized the channel between our take and use timing */
ftdm_log_chan(ftdmchan,
FTDM_LOG_CRIT, "R2 cannot dial out in channel in state %s, try another channel!.\n", ftdm_channel_state2str(ftdmchan->state));
return FTDM_FAIL;
}
ft_r2_clean_call(ftdmchan->call_data);
R2CALL(ftdmchan)->ftdm_started = 1;
R2CALL(ftdmchan)->chanstate = FTDM_CHANNEL_STATE_DOWN;
ftdm_set_state(ftdmchan, FTDM_CHANNEL_STATE_DIALING);
callstatus = openr2_chan_make_call(R2CALL(ftdmchan)->r2chan,
ftdmchan->caller_data.cid_num.digits,
ftdmchan->caller_data.dnis.digits,
OR2_CALLING_PARTY_CATEGORY_NATIONAL_SUBSCRIBER);
if (callstatus) {
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_CRIT, "Failed to make call in R2 channel, openr2_chan_make_call failed\n");
return FTDM_FAIL;
}
if (ftdmchan->state != FTDM_CHANNEL_STATE_DIALING) {
ftdm_log_chan(ftdmchan, FTDM_LOG_WARNING, "Collision after call attempt, try another channel, new state = %s\n",
ftdm_channel_state2str(ftdmchan->state));
ftdm_clear_flag(R2CALL(ftdmchan), FTDM_R2_WAITING_ACK);
return FTDM_BREAK;
}
/* non-threaded implementation, we're done here */
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "R2 call placed in non-threaded mode\n");
return FTDM_SUCCESS;
}
static ftdm_status_t ftdm_r2_start(ftdm_span_t *span)
{
ftdm_r2_data_t *r2_data = span->signal_data;
ftdm_set_flag(r2_data, FTDM_R2_RUNNING);
return ftdm_thread_create_detached(ftdm_r2_run, span);
}
static ftdm_status_t ftdm_r2_stop(ftdm_span_t *span)
{
ftdm_r2_data_t *r2_data = span->signal_data;
while (ftdm_test_flag(r2_data, FTDM_R2_RUNNING)) {
ftdm_log(FTDM_LOG_DEBUG, "Waiting for R2 span %s\n", span->name);
ftdm_sleep(100);
}
return FTDM_SUCCESS;
}
/* always called from the monitor thread */
static void ftdm_r2_on_call_init(openr2_chan_t *r2chan)
{
ftdm_r2_call_t *r2call;
ftdm_channel_t *ftdmchan = openr2_chan_get_client_data(r2chan);
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_NOTICE, "Received request to start call\n");
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_INUSE)) {
ftdm_log_chan(ftdmchan, FTDM_LOG_CRIT, "Cannot start call when channel is in use (state = %s)\n", ftdm_channel_state2str(ftdmchan->state));
2010-11-09 12:22:53 -05:00
return;
}
if (ftdmchan->state != FTDM_CHANNEL_STATE_DOWN) {
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "Cannot handle request to start call in state %s\n", ftdm_channel_state2str(ftdmchan->state));
return;
}
if (ftdm_channel_open_chan(ftdmchan) != FTDM_SUCCESS) {
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "Failed to open channel during incoming call! [%s]\n", ftdmchan->last_error);
return;
}
/* mark the channel in use (so no outgoing calls can be placed here) */
ftdm_channel_use(ftdmchan);
memset(ftdmchan->caller_data.dnis.digits, 0, sizeof(ftdmchan->caller_data.collected));
memset(ftdmchan->caller_data.ani.digits, 0, sizeof(ftdmchan->caller_data.collected));
/* clean the call data structure but keep the R2 processing flag on! */
ft_r2_clean_call(ftdmchan->call_data);
r2call = R2CALL(ftdmchan);
if (ftdmchan->state == FTDM_CHANNEL_STATE_DOWN) {
R2CALL(ftdmchan)->chanstate = FTDM_CHANNEL_STATE_DOWN;
} else {
R2CALL(ftdmchan)->chanstate = FTDM_CHANNEL_STATE_DIALING;
}
ftdm_set_state(ftdmchan, FTDM_CHANNEL_STATE_COLLECT);
}
/* only called for incoming calls when the ANI, DNIS etc is complete and the user has to decide either to accept or reject the call */
static void ftdm_r2_on_call_offered(openr2_chan_t *r2chan, const char *ani, const char *dnis, openr2_calling_party_category_t category)
{
ftdm_channel_t *ftdmchan = openr2_chan_get_client_data(r2chan);
ftdm_log_chan(ftdmchan, FTDM_LOG_NOTICE, "Call offered with ANI = %s, DNIS = %s, Priority = (%d)\n", ani, dnis, category);
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_RING);
}
static void ftdm_r2_on_call_accepted(openr2_chan_t *r2chan, openr2_call_mode_t mode)
{
ftdm_channel_t *ftdmchan = openr2_chan_get_client_data(r2chan);
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_NOTICE, "Call accepted\n");
/* at this point the MF signaling has ended and there is no point on keep reading */
openr2_chan_disable_read(r2chan);
R2CALL(ftdmchan)->accepted = 1;
if (OR2_DIR_BACKWARD == openr2_chan_get_direction(r2chan)) {
R2CALL(ftdmchan)->state_ack_pending = 1;
if (R2CALL(ftdmchan)->answer_pending) {
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Answer was pending, answering now.\n");
ft_r2_answer_call(ftdmchan);
return;
}
} else {
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_PROGRESS_MEDIA);
}
}
static void ftdm_r2_on_call_answered(openr2_chan_t *r2chan)
{
ftdm_channel_t *ftdmchan = openr2_chan_get_client_data(r2chan);
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_NOTICE, "Call answered\n");
/* notify the upper layer of progress in the outbound call */
if (OR2_DIR_FORWARD == openr2_chan_get_direction(r2chan)) {
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_UP);
}
}
/* may be called in the signaling or media thread depending on whether the hangup is product of MF or CAS signaling */
static void ftdm_r2_on_call_disconnect(openr2_chan_t *r2chan, openr2_call_disconnect_cause_t cause)
{
ftdm_sigmsg_t sigev;
ftdm_r2_data_t *r2data;
ftdm_channel_t *ftdmchan = openr2_chan_get_client_data(r2chan);
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_NOTICE, "Call disconnected\n");
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Got openr2 disconnection, clearing call\n");
R2CALL(ftdmchan)->disconnect_rcvd = 1;
if (ftdmchan->state == FTDM_CHANNEL_STATE_HANGUP) {
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Call had been disconnected already by the user\n");
/* just ack the hangup to go down */
openr2_chan_disconnect_call(r2chan, OR2_CAUSE_NORMAL_CLEARING);
return;
}
/* if the call has not been started yet we must go to HANGUP right here */
if (!R2CALL(ftdmchan)->ftdm_started) {
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_HANGUP);
return;
}
ftdmchan->caller_data.hangup_cause = ftdm_r2_cause_to_ftdm_cause(ftdmchan, cause);
/* notify the user of the call terminating */
memset(&sigev, 0, sizeof(sigev));
sigev.chan_id = ftdmchan->chan_id;
sigev.span_id = ftdmchan->span_id;
sigev.channel = ftdmchan;
sigev.event_id = FTDM_SIGEVENT_STOP;
r2data = ftdmchan->span->signal_data;
ftdm_span_send_signal(ftdmchan->span, &sigev);
}
static void ftdm_r2_on_call_end(openr2_chan_t *r2chan)
{
ftdm_channel_t *ftdmchan = openr2_chan_get_client_data(r2chan);
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_NOTICE, "Call finished\n");
/* the call is done as far as the stack is concerned, lets move to down here */
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_DOWN);
/* in some circumstances openr2 can call on_call_init right after this, so let's advance the state right here */
ftdm_r2_state_advance(ftdmchan);
}
static void ftdm_r2_on_call_read(openr2_chan_t *r2chan, const unsigned char *buf, int buflen)
{
#if 0
ftdm_log(FTDM_LOG_NOTICE, "Call read data on chan %d\n", openr2_chan_get_number(r2chan));
#endif
}
static void ftdm_r2_on_hardware_alarm(openr2_chan_t *r2chan, int alarm)
{
ftdm_channel_t *ftdmchan = openr2_chan_get_client_data(r2chan);
ftdm_log_chan(ftdmchan, FTDM_LOG_WARNING, "Alarm notification: %d\n", alarm);
}
static void ftdm_r2_on_os_error(openr2_chan_t *r2chan, int errorcode)
{
ftdm_channel_t *ftdmchan = openr2_chan_get_client_data(r2chan);
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "OS error: %s\n", strerror(errorcode));
}
static void ftdm_r2_on_protocol_error(openr2_chan_t *r2chan, openr2_protocol_error_t reason)
{
ftdm_sigmsg_t sigev;
ftdm_r2_data_t *r2data;
ftdm_channel_t *ftdmchan = openr2_chan_get_client_data(r2chan);
if (ftdmchan->state == FTDM_CHANNEL_STATE_DOWN) {
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_ERROR, "Got protocol error when we're already down!\n");
}
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_ERROR, "Protocol error\n");
R2CALL(ftdmchan)->disconnect_rcvd = 1;
if (!R2CALL(ftdmchan)->ftdm_started) {
ftdm_set_state(ftdmchan, FTDM_CHANNEL_STATE_HANGUP);
return;
}
ftdmchan->caller_data.hangup_cause = FTDM_CAUSE_PROTOCOL_ERROR;
/* FIXME: go to terminating and notify the user from the terminating handler instead of notifying here */
memset(&sigev, 0, sizeof(sigev));
sigev.chan_id = ftdmchan->chan_id;
sigev.span_id = ftdmchan->span_id;
sigev.channel = ftdmchan;
sigev.event_id = FTDM_SIGEVENT_STOP;
r2data = ftdmchan->span->signal_data;
ftdm_span_send_signal(ftdmchan->span, &sigev);
}
static void ftdm_r2_on_line_blocked(openr2_chan_t *r2chan)
{
ftdm_channel_t *ftdmchan = openr2_chan_get_client_data(r2chan);
ftdm_log_chan(ftdmchan, FTDM_LOG_NOTICE, "Far end blocked in state %s\n", ftdm_channel_state2str(ftdmchan->state));
ftdm_r2_set_chan_sig_status(ftdmchan, FTDM_SIG_STATE_SUSPENDED);
}
static void ftdm_r2_on_line_idle(openr2_chan_t *r2chan)
{
ftdm_channel_t *ftdmchan = openr2_chan_get_client_data(r2chan);
ftdm_log_chan(ftdmchan, FTDM_LOG_NOTICE, "Far end unblocked in state %s\n", ftdm_channel_state2str(ftdmchan->state));
ftdm_r2_set_chan_sig_status(ftdmchan, FTDM_SIG_STATE_UP);
}
static void ftdm_r2_write_log(openr2_log_level_t level, const char *file, const char *function, int line, const char *message)
{
switch (level) {
case OR2_LOG_NOTICE:
ftdm_log(file, function, line, FTDM_LOG_LEVEL_NOTICE, "%s", message);
break;
case OR2_LOG_WARNING:
ftdm_log(file, function, line, FTDM_LOG_LEVEL_WARNING, "%s", message);
break;
case OR2_LOG_ERROR:
ftdm_log(file, function, line, FTDM_LOG_LEVEL_ERROR, "%s", message);
break;
case OR2_LOG_STACK_TRACE:
case OR2_LOG_MF_TRACE:
case OR2_LOG_CAS_TRACE:
case OR2_LOG_DEBUG:
case OR2_LOG_EX_DEBUG:
ftdm_log(file, function, line, FTDM_LOG_LEVEL_DEBUG, "%s", message);
break;
default:
ftdm_log(FTDM_LOG_WARNING, "We should handle logging level %d here.\n", level);
ftdm_log(file, function, line, FTDM_LOG_LEVEL_DEBUG, "%s", message);
break;
}
}
static void ftdm_r2_on_context_log(openr2_context_t *r2context, const char *file, const char *function, unsigned int line,
openr2_log_level_t level, const char *fmt, va_list ap)
{
#define CONTEXT_TAG "Context -"
char logmsg[256];
char completemsg[sizeof(logmsg) + sizeof(CONTEXT_TAG) - 1];
vsnprintf(logmsg, sizeof(logmsg), fmt, ap);
snprintf(completemsg, sizeof(completemsg), CONTEXT_TAG "%s", logmsg);
ftdm_r2_write_log(level, file, function, line, completemsg);
#undef CONTEXT_TAG
}
static void ftdm_r2_on_chan_log(openr2_chan_t *r2chan, const char *file, const char *function, unsigned int line,
openr2_log_level_t level, const char *fmt, va_list ap)
{
ftdm_channel_t *ftdmchan = openr2_chan_get_client_data(r2chan);
#define CHAN_TAG "Chan "
char logmsg[512];
char completemsg[sizeof(logmsg) + sizeof(CHAN_TAG) - 1];
vsnprintf(logmsg, sizeof(logmsg), fmt, ap);
snprintf(completemsg, sizeof(completemsg), CHAN_TAG "%d:%d [%s] %s",
ftdmchan->span_id, ftdmchan->chan_id, ftdm_channel_state2str(ftdmchan->state), logmsg);
ftdm_r2_write_log(level, file, function, line, completemsg);
#undef CHAN_TAG
}
static int ftdm_r2_on_dnis_digit_received(openr2_chan_t *r2chan, char digit)
{
ftdm_sigmsg_t sigev;
ftdm_r2_data_t *r2data;
ftdm_channel_t *ftdmchan = openr2_chan_get_client_data(r2chan);
ftdm_size_t collected_len = R2CALL(ftdmchan)->dnis_index;
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "DNIS digit %c received\n", digit);
/* save the digit we just received */
ftdmchan->caller_data.dnis.digits[collected_len] = digit;
collected_len++;
ftdmchan->caller_data.dnis.digits[collected_len] = '\0';
R2CALL(ftdmchan)->dnis_index = collected_len;
/* notify the user about the new digit and check if we should stop requesting more DNIS */
memset(&sigev, 0, sizeof(sigev));
sigev.chan_id = ftdmchan->chan_id;
sigev.span_id = ftdmchan->span_id;
sigev.channel = ftdmchan;
sigev.event_id = FTDM_SIGEVENT_COLLECTED_DIGIT;
r2data = ftdmchan->span->signal_data;
if (ftdm_span_send_signal(ftdmchan->span, &sigev) == FTDM_BREAK) {
ftdm_log_chan(ftdmchan, FTDM_LOG_NOTICE, "Requested to stop getting DNIS. Current DNIS = %s\n", ftdmchan->caller_data.dnis.digits);
return OR2_STOP_DNIS_REQUEST;
}
/* the only other reason to stop requesting DNIS is that there is no more room to save it */
if (collected_len == (sizeof(ftdmchan->caller_data.dnis.digits) - 1)) {
ftdm_log_chan(ftdmchan, FTDM_LOG_WARNING, "No more room for DNIS. Current DNIS = %s\n", ftdmchan->caller_data.dnis.digits);
return OR2_STOP_DNIS_REQUEST;
}
return OR2_CONTINUE_DNIS_REQUEST;
}
static void ftdm_r2_on_ani_digit_received(openr2_chan_t *r2chan, char digit)
{
ftdm_channel_t *ftdmchan = openr2_chan_get_client_data(r2chan);
ftdm_size_t collected_len = R2CALL(ftdmchan)->ani_index;
/* check if we should drop ANI */
if (collected_len == (sizeof(ftdmchan->caller_data.ani.digits) - 1)) {
ftdm_log_chan(ftdmchan, FTDM_LOG_WARNING, "No more room for ANI, digit dropped: %c\n", digit);
return;
}
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "ANI digit %c received\n", digit);
/* save the digit we just received */
ftdmchan->caller_data.ani.digits[collected_len] = digit;
collected_len++;
ftdmchan->caller_data.ani.digits[collected_len] = '\0';
R2CALL(ftdmchan)->ani_index = collected_len;
}
static openr2_event_interface_t ftdm_r2_event_iface = {
/* .on_call_init */ ftdm_r2_on_call_init,
/* .on_call_offered */ ftdm_r2_on_call_offered,
/* .on_call_accepted */ ftdm_r2_on_call_accepted,
/* .on_call_answered */ ftdm_r2_on_call_answered,
/* .on_call_disconnect */ ftdm_r2_on_call_disconnect,
/* .on_call_end */ ftdm_r2_on_call_end,
/* .on_call_read */ ftdm_r2_on_call_read,
/* .on_hardware_alarm */ ftdm_r2_on_hardware_alarm,
/* .on_os_error */ ftdm_r2_on_os_error,
/* .on_protocol_error */ ftdm_r2_on_protocol_error,
/* .on_line_blocked */ ftdm_r2_on_line_blocked,
/* .on_line_idle */ ftdm_r2_on_line_idle,
/* cast seems to be needed to get rid of the annoying warning regarding format attribute */
/* .on_context_log */ (openr2_handle_context_logging_func)ftdm_r2_on_context_log,
/* .on_dnis_digit_received */ ftdm_r2_on_dnis_digit_received,
/* .on_ani_digit_received */ ftdm_r2_on_ani_digit_received,
/* so far we do nothing with billing pulses */
/* .on_billing_pulse_received */ NULL,
};
static int ftdm_r2_io_set_cas(openr2_chan_t *r2chan, int cas)
{
ftdm_channel_t *ftdm_chan = openr2_chan_get_fd(r2chan);
ftdm_status_t status = ftdm_channel_command(ftdm_chan, FTDM_COMMAND_SET_CAS_BITS, &cas);
if (FTDM_FAIL == status) {
return -1;
}
return 0;
}
static int ftdm_r2_io_get_cas(openr2_chan_t *r2chan, int *cas)
{
ftdm_channel_t *ftdm_chan = openr2_chan_get_fd(r2chan);
ftdm_status_t status = ftdm_channel_command(ftdm_chan, FTDM_COMMAND_GET_CAS_BITS, cas);
if (FTDM_FAIL == status) {
return -1;
}
return 0;
}
static int ftdm_r2_io_flush_write_buffers(openr2_chan_t *r2chan)
{
ftdm_channel_t *ftdm_chan = openr2_chan_get_fd(r2chan);
ftdm_status_t status = ftdm_channel_command(ftdm_chan, FTDM_COMMAND_FLUSH_TX_BUFFERS, NULL);
if (FTDM_FAIL == status) {
return -1;
}
return 0;
}
static int ftdm_r2_io_write(openr2_chan_t *r2chan, const void *buf, int size)
{
ftdm_channel_t *ftdm_chan = openr2_chan_get_fd(r2chan);
ftdm_size_t outsize = size;
ftdm_status_t status = ftdm_channel_write(ftdm_chan, (void *)buf, size, &outsize);
if (FTDM_FAIL == status) {
return -1;
}
return outsize;
}
static int ftdm_r2_io_read(openr2_chan_t *r2chan, const void *buf, int size)
{
ftdm_channel_t *ftdm_chan = openr2_chan_get_fd(r2chan);
ftdm_size_t outsize = size;
ftdm_status_t status = ftdm_channel_read(ftdm_chan, (void *)buf, &outsize);
if (FTDM_FAIL == status) {
return -1;
}
return outsize;
}
static int ftdm_r2_io_wait(openr2_chan_t *r2chan, int *flags, int block)
{
ftdm_status_t status;
int32_t timeout;
ftdm_wait_flag_t ftdmflags = 0;
ftdm_channel_t *ftdm_chan = openr2_chan_get_fd(r2chan);
timeout = block ? -1 : 0;
if (*flags & OR2_IO_READ) {
ftdmflags |= FTDM_READ;
}
if (*flags & OR2_IO_WRITE) {
ftdmflags |= FTDM_WRITE;
}
if (*flags & OR2_IO_OOB_EVENT) {
ftdmflags |= FTDM_EVENTS;
}
status = ftdm_channel_wait(ftdm_chan, &ftdmflags, timeout);
if (FTDM_SUCCESS != status && FTDM_TIMEOUT != status) {
return -1;
}
*flags = 0;
if (ftdmflags & FTDM_READ) {
*flags |= OR2_IO_READ;
}
if (ftdmflags & FTDM_WRITE) {
*flags |= OR2_IO_WRITE;
}
if (ftdmflags & FTDM_EVENTS) {
*flags |= OR2_IO_OOB_EVENT;
}
return 0;
}
/* The following openr2 hooks never get called, read on for reasoning ... */
/* since freetdm takes care of opening the file descriptor and using openr2_chan_new_from_fd, openr2 should never call this hook */
static openr2_io_fd_t ftdm_r2_io_open(openr2_context_t *r2context, int channo)
{
ftdm_log(FTDM_LOG_ERROR, "I should not be called (I/O open)!!\n");
return NULL;
}
/* since freetdm takes care of closing the file descriptor and uses openr2_chan_new_from_fd, openr2 should never call this hook */
static int ftdm_r2_io_close(openr2_chan_t *r2chan)
{
ftdm_channel_t *fchan = openr2_chan_get_client_data(r2chan);
ftdm_log_chan_msg(fchan, FTDM_LOG_ERROR, "I should not be called (I/O close)!!\n");
return 0;
}
/* since freetdm takes care of opening the file descriptor and using openr2_chan_new_from_fd, openr2 should never call this hook */
static int ftdm_r2_io_setup(openr2_chan_t *r2chan)
{
ftdm_channel_t *fchan = openr2_chan_get_client_data(r2chan);
ftdm_log_chan_msg(fchan, FTDM_LOG_ERROR, "I should not be called (I/O Setup)!!\n");
return 0;
}
static int ftdm_r2_io_get_oob_event(openr2_chan_t *r2chan, openr2_oob_event_t *event)
{
ftdm_status_t status;
ftdm_event_t *fevent = NULL;
ftdm_channel_t *ftdmchan = openr2_chan_get_fd(r2chan);
*event = OR2_OOB_EVENT_NONE;
status = ftdm_channel_read_event(ftdmchan, &fevent);
if (status != FTDM_SUCCESS) {
//ftdm_log_chan_msg(ftdmchan, FTDM_LOG_ERROR, "failed to retrieve freetdm event!\n");
return -1;
}
if (fevent->e_type != FTDM_EVENT_OOB)
return 0;
switch (fevent->enum_id) {
case FTDM_OOB_CAS_BITS_CHANGE:
{
*event = OR2_OOB_EVENT_CAS_CHANGE;
}
break;
case FTDM_OOB_ALARM_TRAP:
{
*event = OR2_OOB_EVENT_ALARM_ON;
}
break;
case FTDM_OOB_ALARM_CLEAR:
{
*event = OR2_OOB_EVENT_ALARM_OFF;
}
break;
}
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "returning event: %s\n", ftdm_signal_event2str(*event));
return 0;
}
static openr2_io_interface_t ftdm_r2_io_iface = {
/* .open */ ftdm_r2_io_open, /* never called */
/* .close */ ftdm_r2_io_close, /* never called */
/* .set_cas */ ftdm_r2_io_set_cas,
/* .get_cas */ ftdm_r2_io_get_cas,
/* .flush_write_buffers */ ftdm_r2_io_flush_write_buffers,
/* .write */ ftdm_r2_io_write,
/* .read */ ftdm_r2_io_read,
/* .setup */ ftdm_r2_io_setup, /* never called */
/* .wait */ ftdm_r2_io_wait,
/* .get_oob_event */ ftdm_r2_io_get_oob_event /* never called */
};
static FIO_SIG_CONFIGURE_FUNCTION(ftdm_r2_configure_span)
//ftdm_status_t (ftdm_span_t *span, fio_signal_cb_t sig_cb, va_list ap)
{
int i = 0;
int conf_failure = 0;
char *var = NULL;
char *val = NULL;
ftdm_r2_data_t *r2data = NULL;
ftdm_r2_span_pvt_t *spanpvt = NULL;
ftdm_r2_call_t *r2call = NULL;
openr2_chan_t *r2chan = NULL;
openr2_log_level_t tmplevel;
char *clevel = NULL;
char *logval = NULL;
ft_r2_conf_t r2conf =
{
/* .variant */ OR2_VAR_ITU,
/* .category */ OR2_CALLING_PARTY_CATEGORY_NATIONAL_SUBSCRIBER,
/* .loglevel */ OR2_LOG_ERROR | OR2_LOG_WARNING,
/* .logdir */ NULL,
/* .advanced_protocol_file */ NULL,
/* .max_ani */ 10,
/* .max_dnis */ 4,
/* .mfback_timeout */ -1,
/* .metering_pulse_timeout */ -1,
/* .immediate_accept */ -1,
/* .skip_category */ -1,
/* .get_ani_first */ -1,
/* .call_files */ 0,
/* .mf_files */ 0,
/* .double_answer */ 0,
/* .charge_calls */ -1,
/* .forced_release */ -1,
/* .allow_collect_calls */ -1
};
assert(sig_cb != NULL);
if (span->signal_type) {
snprintf(span->last_error, sizeof(span->last_error), "Span is already configured for signalling.");
return FTDM_FAIL;
}
while ((var = va_arg(ap, char *))) {
ftdm_log(FTDM_LOG_DEBUG, "Reading R2 parameter %s for span %d\n", var, span->span_id);
if (!strcasecmp(var, "variant")) {
if (!(val = va_arg(ap, char *))) {
break;
}
if (ftdm_strlen_zero_buf(val)) {
ftdm_log(FTDM_LOG_NOTICE, "Ignoring empty R2 variant parameter\n");
continue;
}
r2conf.variant = openr2_proto_get_variant(val);
if (r2conf.variant == OR2_VAR_UNKNOWN) {
ftdm_log(FTDM_LOG_ERROR, "Unknown R2 variant %s\n", val);
conf_failure = 1;
break;
}
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d for variant %s\n", span->span_id, val);
} else if (!strcasecmp(var, "category")) {
if (!(val = va_arg(ap, char *))) {
break;
}
if (ftdm_strlen_zero_buf(val)) {
ftdm_log(FTDM_LOG_NOTICE, "Ignoring empty R2 category parameter\n");
continue;
}
r2conf.category = openr2_proto_get_category(val);
if (r2conf.category == OR2_CALLING_PARTY_CATEGORY_UNKNOWN) {
ftdm_log(FTDM_LOG_ERROR, "Unknown R2 caller category %s\n", val);
conf_failure = 1;
break;
}
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with default category %s\n", span->span_id, val);
} else if (!strcasecmp(var, "logdir")) {
if (!(val = va_arg(ap, char *))) {
break;
}
if (ftdm_strlen_zero_buf(val)) {
ftdm_log(FTDM_LOG_NOTICE, "Ignoring empty R2 logdir parameter\n");
continue;
}
r2conf.logdir = val;
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with logdir %s\n", span->span_id, val);
} else if (!strcasecmp(var, "logging")) {
if (!(val = va_arg(ap, char *))) {
break;
}
if (ftdm_strlen_zero_buf(val)) {
ftdm_log(FTDM_LOG_NOTICE, "Ignoring empty R2 logging parameter\n");
continue;
}
logval = ftdm_malloc(strlen(val)+1); /* alloca man page scared me, so better to use good ol' malloc */
if (!logval) {
ftdm_log(FTDM_LOG_WARNING, "Ignoring R2 logging parameter: '%s', failed to alloc memory\n", val);
continue;
}
strcpy(logval, val);
while (logval) {
clevel = strsep(&logval, ",");
if (-1 == (tmplevel = openr2_log_get_level(clevel))) {
ftdm_log(FTDM_LOG_WARNING, "Ignoring invalid R2 logging level: '%s'\n", clevel);
continue;
}
r2conf.loglevel |= tmplevel;
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with loglevel %s\n", span->span_id, clevel);
}
ftdm_safe_free(logval);
} else if (!strcasecmp(var, "advanced_protocol_file")) {
if (!(val = va_arg(ap, char *))) {
break;
}
if (ftdm_strlen_zero_buf(val)) {
ftdm_log(FTDM_LOG_NOTICE, "Ignoring empty R2 advanced_protocol_file parameter\n");
continue;
}
r2conf.advanced_protocol_file = val;
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with advanced protocol file %s\n", span->span_id, val);
} else if (!strcasecmp(var, "allow_collect_calls")) {
r2conf.allow_collect_calls = va_arg(ap, int);
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with allow collect calls max ani = %d\n", span->span_id, r2conf.allow_collect_calls);
} else if (!strcasecmp(var, "double_answer")) {
r2conf.double_answer = va_arg(ap, int);
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with double answer = %d\n", span->span_id, r2conf.double_answer);
} else if (!strcasecmp(var, "immediate_accept")) {
r2conf.immediate_accept = va_arg(ap, int);
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with immediate accept = %d\n", span->span_id, r2conf.immediate_accept);
} else if (!strcasecmp(var, "skip_category")) {
r2conf.skip_category = va_arg(ap, int);
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with skip category = %d\n", span->span_id, r2conf.skip_category);
} else if (!strcasecmp(var, "forced_release")) {
r2conf.forced_release = va_arg(ap, int);
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with forced release = %d\n", span->span_id, r2conf.forced_release);
} else if (!strcasecmp(var, "charge_calls")) {
r2conf.charge_calls = va_arg(ap, int);
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with charge calls = %d\n", span->span_id, r2conf.charge_calls);
} else if (!strcasecmp(var, "get_ani_first")) {
r2conf.get_ani_first = va_arg(ap, int);
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with get ani first = %d\n", span->span_id, r2conf.get_ani_first);
} else if (!strcasecmp(var, "call_files")) {
r2conf.call_files = va_arg(ap, int);
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with call files = %d\n", span->span_id, r2conf.call_files);
} else if (!strcasecmp(var, "mf_files")) {
r2conf.mf_files = va_arg(ap, int);
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with mf files = %d\n", span->span_id, r2conf.mf_files);
} else if (!strcasecmp(var, "mfback_timeout")) {
r2conf.mfback_timeout = va_arg(ap, int);
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with MF backward timeout = %dms\n", span->span_id, r2conf.mfback_timeout);
} else if (!strcasecmp(var, "metering_pulse_timeout")) {
r2conf.metering_pulse_timeout = va_arg(ap, int);
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with metering pulse timeout = %dms\n", span->span_id, r2conf.metering_pulse_timeout);
} else if (!strcasecmp(var, "max_ani")) {
r2conf.max_ani = va_arg(ap, int);
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with max ani = %d\n", span->span_id, r2conf.max_ani);
} else if (!strcasecmp(var, "max_dnis")) {
r2conf.max_dnis = va_arg(ap, int);
ftdm_log(FTDM_LOG_DEBUG, "Configuring R2 span %d with max dnis = %d\n", span->span_id, r2conf.max_dnis);
} else {
snprintf(span->last_error, sizeof(span->last_error), "Unknown R2 parameter [%s]", var);
return FTDM_FAIL;
}
}
if (conf_failure) {
snprintf(span->last_error, sizeof(span->last_error), "R2 configuration error");
return FTDM_FAIL;
}
r2data = ftdm_malloc(sizeof(*r2data));
if (!r2data) {
snprintf(span->last_error, sizeof(span->last_error), "Failed to allocate R2 data.");
return FTDM_FAIL;
}
memset(r2data, 0, sizeof(*r2data));
spanpvt = ftdm_malloc(sizeof(*spanpvt));
if (!spanpvt) {
snprintf(span->last_error, sizeof(span->last_error), "Failed to allocate private span data container.");
goto fail;
}
memset(spanpvt, 0, sizeof(*spanpvt));
r2data->r2context = openr2_context_new(r2conf.variant, &ftdm_r2_event_iface, r2conf.max_ani, r2conf.max_dnis);
if (!r2data->r2context) {
snprintf(span->last_error, sizeof(span->last_error), "Cannot create openr2 context for span.");
goto fail;
}
openr2_context_set_io_type(r2data->r2context, OR2_IO_CUSTOM, &ftdm_r2_io_iface);
openr2_context_set_log_level(r2data->r2context, r2conf.loglevel);
openr2_context_set_ani_first(r2data->r2context, r2conf.get_ani_first);
openr2_context_set_skip_category_request(r2data->r2context, r2conf.skip_category);
openr2_context_set_mf_back_timeout(r2data->r2context, r2conf.mfback_timeout);
openr2_context_set_metering_pulse_timeout(r2data->r2context, r2conf.metering_pulse_timeout);
openr2_context_set_double_answer(r2data->r2context, r2conf.double_answer);
openr2_context_set_immediate_accept(r2data->r2context, r2conf.immediate_accept);
if (r2conf.logdir && r2conf.logdir[0]) {
ftdm_log(FTDM_LOG_DEBUG, "Setting openr2 for span %s logdir to %s\n", span->name, r2conf.logdir);
openr2_context_set_log_directory(r2data->r2context, r2conf.logdir);
}
if (r2conf.advanced_protocol_file) {
openr2_context_configure_from_advanced_file(r2data->r2context, r2conf.advanced_protocol_file);
}
spanpvt->r2calls = create_hashtable(FTDM_MAX_CHANNELS_SPAN, ftdm_hash_hashfromstring, ftdm_hash_equalkeys);
if (!spanpvt->r2calls) {
snprintf(span->last_error, sizeof(span->last_error), "Cannot create channel calls hash for span.");
goto fail;
}
for (i = 1; (i <= span->chan_count) && (i <= FTDM_MAX_CHANNELS_SPAN); i++) {
r2chan = openr2_chan_new_from_fd(r2data->r2context, span->channels[i], span->channels[i]->physical_chan_id);
if (!r2chan) {
snprintf(span->last_error, sizeof(span->last_error), "Cannot create all openr2 channels for span.");
goto fail;
}
openr2_chan_set_log_level(r2chan, r2conf.loglevel);
if (r2conf.call_files) {
openr2_chan_enable_call_files(r2chan);
#if 0
if (r2conf.mf_files) {
openr2_chan_enable_mf_files(r2chan);
}
#endif
}
r2call = ftdm_malloc(sizeof(*r2call));
if (!r2call) {
snprintf(span->last_error, sizeof(span->last_error), "Cannot create all R2 call data structures for the span.");
ftdm_safe_free(r2chan);
goto fail;
}
memset(r2call, 0, sizeof(*r2call));
openr2_chan_set_logging_func(r2chan, ftdm_r2_on_chan_log);
openr2_chan_set_client_data(r2chan, span->channels[i]);
2010-11-09 12:22:53 -05:00
r2call->r2chan = r2chan;
span->channels[i]->call_data = r2call;
/* value and key are the same so just free one of them */
snprintf(r2call->name, sizeof(r2call->name), "chancall%d", i);
hashtable_insert(spanpvt->r2calls, (void *)r2call->name, r2call, HASHTABLE_FLAG_FREE_VALUE);
}
r2data->flags = 0;
spanpvt->r2context = r2data->r2context;
/* just the value must be freed by the hash */
hashtable_insert(g_mod_data_hash, (void *)span->name, spanpvt, HASHTABLE_FLAG_FREE_VALUE);
span->start = ftdm_r2_start;
span->stop = ftdm_r2_stop;
span->sig_read = NULL;
span->signal_cb = sig_cb;
span->signal_type = FTDM_SIGTYPE_R2;
span->signal_data = r2data;
span->outgoing_call = r2_outgoing_call;
/* use signals queue */
ftdm_set_flag(span, FTDM_SPAN_USE_SIGNALS_QUEUE);
return FTDM_SUCCESS;
fail:
if (r2data && r2data->r2context) {
openr2_context_delete(r2data->r2context);
}
if (spanpvt && spanpvt->r2calls) {
hashtable_destroy(spanpvt->r2calls);
}
ftdm_safe_free(r2data);
ftdm_safe_free(spanpvt);
return FTDM_FAIL;
}
/* the channel must be locked when calling this function */
static int ftdm_r2_state_advance(ftdm_channel_t *ftdmchan)
{
ftdm_sigmsg_t sigev;
int ret;
openr2_chan_t *r2chan = R2CALL(ftdmchan)->r2chan;
memset(&sigev, 0, sizeof(sigev));
sigev.chan_id = ftdmchan->chan_id;
sigev.span_id = ftdmchan->span_id;
sigev.channel = ftdmchan;
ret = 0;
if (R2CALL(ftdmchan)->state_ack_pending) {
ftdm_clear_flag(ftdmchan, FTDM_CHANNEL_STATE_CHANGE);
ftdm_channel_complete_state(ftdmchan);
R2CALL(ftdmchan)->state_ack_pending = 0;
}
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_STATE_CHANGE) && (R2CALL(ftdmchan)->chanstate != ftdmchan->state)) {
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "Executing state handler for %s\n", ftdm_channel_state2str(ftdmchan->state));
R2CALL(ftdmchan)->chanstate = ftdmchan->state;
if (!ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OUTBOUND) && !R2CALL(ftdmchan)->accepted &&
(ftdmchan->state == FTDM_CHANNEL_STATE_PROGRESS ||
ftdmchan->state == FTDM_CHANNEL_STATE_PROGRESS_MEDIA ||
ftdmchan->state == FTDM_CHANNEL_STATE_UP) ) {
/* if an accept ack will be required we should not acknowledge the state change just yet,
it will be done below after processing the MF signals, otherwise we have a race condition between freetdm calling
openr2_chan_answer_call and openr2 accepting the call first, if freetdm calls openr2_chan_answer_call before the accept cycle
completes, openr2 will fail to answer the call */
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "State ack for state %s will have to wait a bit\n", ftdm_channel_state2str(ftdmchan->state));
} else if (ftdmchan->state != FTDM_CHANNEL_STATE_DOWN){
ftdm_clear_flag(ftdmchan, FTDM_CHANNEL_STATE_CHANGE);
ftdm_channel_complete_state(ftdmchan);
}
switch (ftdmchan->state) {
/* starting an incoming call */
case FTDM_CHANNEL_STATE_COLLECT:
{
uint32_t interval = 0;
ftdm_channel_command(ftdmchan, FTDM_COMMAND_GET_INTERVAL, &interval);
ftdm_assert(interval != 0, "Invalid interval!");
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "Starting processing of incoming call with interval %d\n", interval);
}
break;
/* starting an outgoing call */
case FTDM_CHANNEL_STATE_DIALING:
{
uint32_t interval = 0;
ftdm_channel_command(ftdmchan, FTDM_COMMAND_GET_INTERVAL, &interval);
ftdm_assert(interval != 0, "Invalid interval!");
ftdm_log_chan(ftdmchan,
FTDM_LOG_DEBUG, "Starting processing of outgoing call in channel with interval %d\n", interval);
}
break;
/* incoming call was offered */
case FTDM_CHANNEL_STATE_RING:
/* notify the user about the new call */
sigev.event_id = FTDM_SIGEVENT_START;
if (ftdm_span_send_signal(ftdmchan->span, &sigev) != FTDM_SUCCESS) {
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_NOTICE, "Failed to handle call offered\n");
openr2_chan_disconnect_call(r2chan, OR2_CAUSE_OUT_OF_ORDER);
ftdm_set_state(ftdmchan, FTDM_CHANNEL_STATE_CANCEL);
break;
}
R2CALL(ftdmchan)->ftdm_started = 1;
break;
/* the call is making progress */
case FTDM_CHANNEL_STATE_PROGRESS:
case FTDM_CHANNEL_STATE_PROGRESS_MEDIA:
{
if (!ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OUTBOUND)) {
if (!R2CALL(ftdmchan)->accepted) {
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Accepting call\n");
ft_r2_accept_call(ftdmchan);
}
} else {
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Notifying progress\n");
sigev.event_id = FTDM_SIGEVENT_PROGRESS_MEDIA;
if (ftdm_span_send_signal(ftdmchan->span, &sigev) != FTDM_SUCCESS) {
ftdm_set_state(ftdmchan, FTDM_CHANNEL_STATE_HANGUP);
}
}
}
break;
/* the call was answered */
case FTDM_CHANNEL_STATE_UP:
{
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Call was answered\n");
if (!ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OUTBOUND)) {
if (!R2CALL(ftdmchan)->accepted) {
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Call has not been accepted, need to accept first\n");
// the answering will be done in the on_call_accepted handler
ft_r2_accept_call(ftdmchan);
R2CALL(ftdmchan)->answer_pending = 1;
} else {
ft_r2_answer_call(ftdmchan);
}
} else {
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Notifying of call answered\n");
sigev.event_id = FTDM_SIGEVENT_UP;
if (ftdm_span_send_signal(ftdmchan->span, &sigev) != FTDM_SUCCESS) {
ftdm_set_state(ftdmchan, FTDM_CHANNEL_STATE_HANGUP);
}
}
}
break;
/* just got hangup */
case FTDM_CHANNEL_STATE_HANGUP:
{
openr2_call_disconnect_cause_t disconnect_cause = ftdm_r2_ftdm_cause_to_openr2_cause(ftdmchan);
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "Clearing call, cause = %s\n", openr2_proto_get_disconnect_string(disconnect_cause));
openr2_chan_enable_read(r2chan);
if (!R2CALL(ftdmchan)->disconnect_rcvd) {
/* this will disconnect the call, but need to wait for the call end before moving to DOWN */
openr2_chan_disconnect_call(r2chan, disconnect_cause);
} else {
/* just ack the hangup, on_call_end will be called by openr2 right after */
openr2_chan_disconnect_call(r2chan, disconnect_cause);
}
}
break;
/* just got hangup from the freetdm side due to abnormal failure */
case FTDM_CHANNEL_STATE_CANCEL:
{
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Unable to receive call\n");
openr2_chan_enable_read(r2chan);
openr2_chan_disconnect_call(r2chan, OR2_CAUSE_OUT_OF_ORDER);
}
break;
/* finished call for good */
case FTDM_CHANNEL_STATE_DOWN:
{
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Call is down\n");
if (R2CALL(ftdmchan)->txdrops) {
ftdm_log_chan(ftdmchan, FTDM_LOG_WARNING, "dropped %d tx packets\n", R2CALL(ftdmchan)->txdrops);
}
ret = 1;
}
break;
default:
{
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "Unhandled channel state change: %s\n", ftdm_channel_state2str(ftdmchan->state));
}
break;
}
}
if (ret) {
ftdm_channel_t *closed_chan;
closed_chan = ftdmchan;
ftdm_channel_close(&closed_chan);
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "State processing ended.\n");
}
return ret;
}
static void *ftdm_r2_run(ftdm_thread_t *me, void *obj)
{
openr2_chan_t *r2chan;
2010-11-09 12:22:53 -05:00
ftdm_r2_call_t *r2call = NULL;
ftdm_channel_t *ftdmchan = NULL;
ftdm_status_t status;
ftdm_span_t *span = (ftdm_span_t *) obj;
ftdm_r2_data_t *r2data = span->signal_data;
int waitms = 20;
int i, res;
int ms;
struct timeval start, end;
short *poll_events = ftdm_malloc(sizeof(short)*span->chan_count);
ftdm_event_t *event = NULL;
#ifdef __linux__
r2data->monitor_thread_id = syscall(SYS_gettid);
#endif
ftdm_log(FTDM_LOG_DEBUG, "OpenR2 monitor thread %lu started.\n", r2data->monitor_thread_id);
r2chan = NULL;
for (i = 1; i <= span->chan_count; i++) {
r2chan = R2CALL(span->channels[i])->r2chan;
openr2_chan_set_idle(r2chan);
openr2_chan_process_cas_signaling(r2chan);
}
memset(&start, 0, sizeof(start));
memset(&end, 0, sizeof(end));
while (ftdm_running() && ftdm_test_flag(r2data, FTDM_R2_RUNNING)) {
r2data->loops++;
res = gettimeofday(&end, NULL);
if (start.tv_sec) {
ms = ((end.tv_sec - start.tv_sec) * 1000)
+ ((( 1000000 + end.tv_usec - start.tv_usec) / 1000) - 1000);
if (ms > r2data->jobmax) {
r2data->jobmax = ms;
}
}
#ifndef WIN32
2010-11-09 12:22:53 -05:00
/* figure out what event to poll each channel for. POLLPRI when the channel is down,
* POLLPRI|POLLIN|POLLOUT otherwise */
memset(poll_events, 0, sizeof(short)*span->chan_count);
for (i = 0; i < span->chan_count; i++) {
r2chan = R2CALL(span->channels[(i+1)])->r2chan;
ftdmchan = openr2_chan_get_client_data(r2chan);
poll_events[i] = ftdmchan->state == FTDM_CHANNEL_STATE_DOWN ? POLLPRI : (POLLPRI | POLLIN | POLLOUT);
}
status = ftdm_span_poll_event(span, waitms, poll_events);
#else
status = ftdm_span_poll_event(span, waitms, NULL);
#endif
res = gettimeofday(&start, NULL);
if (res) {
ftdm_log(FTDM_LOG_CRIT, "Failure gettimeofday [%s]\n", strerror(errno));
}
if (FTDM_FAIL == status) {
ftdm_log(FTDM_LOG_CRIT, "Failure waiting I/O! [%s]\n", span->channels[1]->last_error);
continue;
}
/* XXX
* when ftdm_span_poll_event() returns FTDM_SUCCESS, means there are events pending on the span.
* is it possible to know on which channels those events are pending, without traversing the span?
* XXX */
for (i = 1; i <= span->chan_count; i++) {
r2chan = R2CALL(span->channels[i])->r2chan;
r2call = R2CALL(ftdmchan);
ftdmchan = openr2_chan_get_client_data(r2chan);
2010-11-09 12:22:53 -05:00
status = ftdm_channel_read_event(ftdmchan, &event);
ftdm_mutex_lock(ftdmchan->mutex);
if (status == FTDM_SUCCESS) {
switch (event->enum_id) {
case FTDM_OOB_CAS_BITS_CHANGE:
{
ftdm_log(FTDM_LOG_DEBUG, "Handling CAS on channel %d.\n", i);
openr2_chan_process_cas_signaling(r2chan);
}
break;
case FTDM_OOB_ALARM_TRAP:
case FTDM_OOB_ALARM_CLEAR:
{
ftdm_log(FTDM_LOG_DEBUG, "OOB EVENT: %s\n", ftdm_signal_event2str(event->enum_id));
openr2_chan_process_oob_events(r2chan);
}
break;
2010-11-09 12:22:53 -05:00
}
}
2010-11-09 12:22:53 -05:00
if (!ftdm_r2_state_advance(ftdmchan)) {
ftdm_mutex_unlock(ftdmchan->mutex);
}
openr2_chan_process_signaling(r2chan);
if (!ftdm_r2_state_advance(ftdmchan)) {
2010-11-09 12:22:53 -05:00
ftdm_mutex_unlock(ftdmchan->mutex);
}
}
ftdm_span_trigger_signals(span);
}
for (i = 1; i <= span->chan_count; i++) {
r2chan = R2CALL(span->channels[i])->r2chan;
openr2_chan_set_blocked(r2chan);
}
ftdm_clear_flag(r2data, FTDM_R2_RUNNING);
ftdm_log(FTDM_LOG_DEBUG, "R2 thread ending.\n");
return NULL;
}
static void __inline__ block_channel(ftdm_channel_t *fchan, ftdm_stream_handle_t *stream)
{
openr2_chan_t *r2chan = R2CALL(fchan)->r2chan;
ftdm_mutex_lock(fchan->mutex);
if (fchan->state != FTDM_CHANNEL_STATE_DOWN) {
stream->write_function(stream, "cannot block channel %d:%d because has a call in progress\n",
fchan->span_id, fchan->chan_id);
} else if (ftdm_test_flag(fchan, FTDM_CHANNEL_SUSPENDED)) {
stream->write_function(stream, "cannot block channel %d:%d because is already blocked\n",
fchan->span_id, fchan->chan_id);
} else {
if (!openr2_chan_set_blocked(r2chan)) {
ftdm_set_flag(fchan, FTDM_CHANNEL_SUSPENDED);
stream->write_function(stream, "blocked channel %d:%d\n",
fchan->span_id, fchan->chan_id);
} else {
stream->write_function(stream, "failed to block channel %d:%d\n",
fchan->span_id, fchan->chan_id);
}
}
ftdm_mutex_unlock(fchan->mutex);
}
static void __inline__ unblock_channel(ftdm_channel_t *fchan, ftdm_stream_handle_t *stream)
{
openr2_chan_t *r2chan = R2CALL(fchan)->r2chan;
ftdm_mutex_lock(fchan->mutex);
if (ftdm_test_flag(fchan, FTDM_CHANNEL_SUSPENDED)) {
if (!openr2_chan_set_idle(r2chan)) {
ftdm_clear_flag(fchan, FTDM_CHANNEL_SUSPENDED);
stream->write_function(stream, "unblocked channel %d:%d\n",
fchan->span_id, fchan->chan_id);
} else {
stream->write_function(stream, "failed to unblock channel %d:%d\n",
fchan->span_id, fchan->chan_id);
}
} else {
stream->write_function(stream, "cannot unblock channel %d:%d because is not blocked\n",
fchan->span_id, fchan->chan_id);
}
ftdm_mutex_unlock(fchan->mutex);
}
static FIO_API_FUNCTION(ftdm_r2_api)
{
ftdm_span_t *span = NULL;
char *mycmd = NULL, *argv[10] = { 0 };
int argc = 0;
int span_id = 0;
int chan_id = 0;
int i = 0;
ftdm_r2_data_t *r2data = NULL;
openr2_chan_t *r2chan = NULL;
openr2_context_t *r2context = NULL;
openr2_variant_t r2variant;
if (data) {
mycmd = ftdm_strdup(data);
argc = ftdm_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
}
if (argc >= 2) {
if (!strcasecmp(argv[0], "block")) {
int span_id = atoi(argv[1]);
if (ftdm_span_find_by_name(argv[1], &span) == FTDM_SUCCESS || ftdm_span_find(span_id, &span) == FTDM_SUCCESS) {
if (span->start != ftdm_r2_start) {
stream->write_function(stream, "-ERR invalid span.\n");
goto done;
}
if (argc > 2) {
chan_id = atoi(argv[2]);
if (chan_id && chan_id <= span->chan_count) {
block_channel(span->channels[chan_id], stream);
} else {
stream->write_function(stream, "-ERR invalid chan %d.\n", chan_id);
}
} else {
for (i = 1; i <= span->chan_count; i++) {
block_channel(span->channels[i], stream);
}
}
stream->write_function(stream, "+OK blocked.\n");
goto done;
} else {
stream->write_function(stream, "-ERR invalid span.\n");
goto done;
}
}
if (!strcasecmp(argv[0], "unblock")) {
span_id = atoi(argv[1]);
if (ftdm_span_find_by_name(argv[1], &span) == FTDM_SUCCESS || ftdm_span_find(span_id, &span) == FTDM_SUCCESS) {
if (span->start != ftdm_r2_start) {
stream->write_function(stream, "-ERR invalid span.\n");
goto done;
}
if (argc > 2) {
chan_id = atoi(argv[2]);
if (chan_id && chan_id <= span->chan_count) {
unblock_channel(span->channels[chan_id], stream);
} else {
stream->write_function(stream, "-ERR invalid chan %d.\n", chan_id);
}
} else {
for (i = 1; i <= span->chan_count; i++) {
unblock_channel(span->channels[i], stream);
}
}
stream->write_function(stream, "+OK.\n");
goto done;
} else {
stream->write_function(stream, "-ERR invalid span.\n");
goto done;
}
}
if (!strcasecmp(argv[0], "status")) {
//openr2_chan_stats_t stats;
span_id = atoi(argv[1]);
if (ftdm_span_find_by_name(argv[1], &span) == FTDM_SUCCESS || ftdm_span_find(span_id, &span) == FTDM_SUCCESS) {
if (span->start != ftdm_r2_start) {
stream->write_function(stream, "-ERR not an R2 span.\n");
goto done;
}
if (!(r2data = span->signal_data)) {
stream->write_function(stream, "-ERR invalid span. No R2 singal data in span.\n");
goto done;
}
r2context = r2data->r2context;
r2variant = openr2_context_get_variant(r2context);
stream->write_function(stream,
"Variant: %s\n"
"Max ANI: %d\n"
"Max DNIS: %d\n"
"ANI First: %s\n"
"Immediate Accept: %s\n"
"Side: %s\n"
"Job Max ms: %d\n"
"Job Loops: %lu\n"
"Monitor Thread: %lu\n",
openr2_proto_get_variant_string(r2variant),
openr2_context_get_max_ani(r2context),
openr2_context_get_max_dnis(r2context),
openr2_context_get_ani_first(r2context) ? "Yes" : "No",
openr2_context_get_immediate_accept(r2context) ? "Yes" : "No",
"no side",
r2data->jobmax,
r2data->loops,
r2data->monitor_thread_id);
stream->write_function(stream, "\n");
stream->write_function(stream, "%4s %-12.12s %-12.12s\n", "Channel", "Tx CAS", "Rx CAS");
for (i = 1; i <= span->chan_count; i++) {
r2chan = R2CALL(span->channels[i])->r2chan;
stream->write_function(stream, "%4d %-12.12s %-12.12s\n",
span->channels[i]->chan_id,
openr2_chan_get_tx_cas_string(r2chan),
openr2_chan_get_rx_cas_string(r2chan));
}
stream->write_function(stream, "\n");
stream->write_function(stream, "+OK.\n");
goto done;
} else {
stream->write_function(stream, "-ERR invalid span.\n");
goto done;
}
}
}
if (argc == 1) {
if (!strcasecmp(argv[0], "threads")) {
ftdm_mutex_lock(g_thread_count_mutex);
stream->write_function(stream, "%d R2 channel threads up\n", g_thread_count);
ftdm_mutex_unlock(g_thread_count_mutex);
stream->write_function(stream, "+OK.\n");
goto done;
}
if (!strcasecmp(argv[0], "version")) {
stream->write_function(stream, "OpenR2 version: %s, revision: %s\n", openr2_get_version(), openr2_get_revision());
stream->write_function(stream, "+OK.\n");
goto done;
}
if (!strcasecmp(argv[0], "variants")) {
int32_t numvariants = 0;
const openr2_variant_entry_t *variants = openr2_proto_get_variant_list(&numvariants);
if (!variants) {
stream->write_function(stream, "-ERR failed to retrieve openr2 variant list.\n");
goto done;
}
#define VARIANT_FORMAT "%4s %40s\n"
stream->write_function(stream, VARIANT_FORMAT, "Variant Code", "Country");
numvariants--;
for (; numvariants; numvariants--) {
stream->write_function(stream, VARIANT_FORMAT, variants[numvariants].name, variants[numvariants].country);
}
stream->write_function(stream, "+OK.\n");
#undef VARIANT_FORMAT
goto done;
}
}
stream->write_function(stream, "-ERR invalid command.\n");
done:
ftdm_safe_free(mycmd);
return FTDM_SUCCESS;
}
static FIO_IO_LOAD_FUNCTION(ftdm_r2_io_init)
{
assert(fio != NULL);
memset(&g_ftdm_r2_interface, 0, sizeof(g_ftdm_r2_interface));
g_ftdm_r2_interface.name = "r2";
g_ftdm_r2_interface.api = ftdm_r2_api;
*fio = &g_ftdm_r2_interface;
return FTDM_SUCCESS;
}
static FIO_SIG_LOAD_FUNCTION(ftdm_r2_init)
{
g_mod_data_hash = create_hashtable(10, ftdm_hash_hashfromstring, ftdm_hash_equalkeys);
if (!g_mod_data_hash) {
return FTDM_FAIL;
}
ftdm_mutex_create(&g_thread_count_mutex);
return FTDM_SUCCESS;
}
static FIO_SIG_UNLOAD_FUNCTION(ftdm_r2_destroy)
{
ftdm_hash_iterator_t *i = NULL;
ftdm_r2_span_pvt_t *spanpvt = NULL;
const void *key = NULL;
void *val = NULL;
for (i = hashtable_first(g_mod_data_hash); i; i = hashtable_next(i)) {
hashtable_this(i, &key, NULL, &val);
if (key && val) {
spanpvt = val;
openr2_context_delete(spanpvt->r2context);
hashtable_destroy(spanpvt->r2calls);
}
}
hashtable_destroy(g_mod_data_hash);
ftdm_mutex_destroy(&g_thread_count_mutex);
return FTDM_SUCCESS;
}
2010-11-04 19:15:39 -02:00
EX_DECLARE_DATA ftdm_module_t ftdm_module = {
"r2",
ftdm_r2_io_init,
NULL,
ftdm_r2_init,
ftdm_r2_configure_span,
ftdm_r2_destroy
};
/* For Emacs:
* Local Variables:
* mode:c
* indent-tabs-mode:t
* tab-width:4
* c-basic-offset:4
* End:
* For VIM:
* vim:set softtabstop=4 shiftwidth=4 tabstop=4
*/