927 lines
26 KiB
C

/*
* Copyright (c) 2010, 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.
*/
#include <libpri.h>
#include <poll.h>
#include "private/ftdm_core.h"
#define PRI_SPAN(p) (((p) >> 8) & 0xff)
#define PRI_CHANNEL(p) ((p) & 0xff)
typedef enum {
PRITAP_RUNNING = (1 << 0),
} pritap_flags_t;
typedef struct {
void *callref;
ftdm_number_t callingnum;
ftdm_number_t callingani;
ftdm_number_t callednum;
ftdm_channel_t *fchan;
char callingname[80];
int proceeding:1;
int inuse:1;
} passive_call_t;
typedef struct pritap {
int32_t flags;
struct pri *pri;
int debug;
ftdm_channel_t *dchan;
ftdm_span_t *span;
ftdm_span_t *peerspan;
ftdm_mutex_t *pcalls_lock;
passive_call_t pcalls[FTDM_MAX_CHANNELS_PHYSICAL_SPAN];
} pritap_t;
static FIO_IO_UNLOAD_FUNCTION(ftdm_pritap_unload)
{
return FTDM_SUCCESS;
}
static FIO_CHANNEL_GET_SIG_STATUS_FUNCTION(pritap_get_channel_sig_status)
{
*status = FTDM_SIG_STATE_UP;
return FTDM_SUCCESS;
}
static FIO_SPAN_GET_SIG_STATUS_FUNCTION(pritap_get_span_sig_status)
{
*status = FTDM_SIG_STATE_UP;
return FTDM_SUCCESS;
}
static FIO_CHANNEL_OUTGOING_CALL_FUNCTION(pritap_outgoing_call)
{
ftdm_log(FTDM_LOG_ERROR, "Cannot dial on PRI tapping line!\n");
return FTDM_FAIL;
}
static void s_pri_error(struct pri *pri, char *s)
{
ftdm_log(FTDM_LOG_ERROR, "%s", s);
}
static void s_pri_message(struct pri *pri, char *s)
{
ftdm_log(FTDM_LOG_DEBUG, "%s", s);
}
static int parse_debug(const char *in)
{
int flags = 0;
if (!in) {
return 0;
}
if (strstr(in, "q921_raw")) {
flags |= PRI_DEBUG_Q921_RAW;
}
if (strstr(in, "q921_dump")) {
flags |= PRI_DEBUG_Q921_DUMP;
}
if (strstr(in, "q921_state")) {
flags |= PRI_DEBUG_Q921_STATE;
}
if (strstr(in, "config")) {
flags |= PRI_DEBUG_CONFIG;
}
if (strstr(in, "q931_dump")) {
flags |= PRI_DEBUG_Q931_DUMP;
}
if (strstr(in, "q931_state")) {
flags |= PRI_DEBUG_Q931_STATE;
}
if (strstr(in, "q931_anomaly")) {
flags |= PRI_DEBUG_Q931_ANOMALY;
}
if (strstr(in, "apdu")) {
flags |= PRI_DEBUG_APDU;
}
if (strstr(in, "aoc")) {
flags |= PRI_DEBUG_AOC;
}
if (strstr(in, "all")) {
flags |= PRI_DEBUG_ALL;
}
if (strstr(in, "none")) {
flags = 0;
}
return flags;
}
static ftdm_io_interface_t ftdm_pritap_interface;
static ftdm_status_t ftdm_pritap_start(ftdm_span_t *span);
static FIO_API_FUNCTION(ftdm_pritap_api)
{
char *mycmd = NULL, *argv[10] = { 0 };
int argc = 0;
if (data) {
mycmd = ftdm_strdup(data);
argc = ftdm_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
}
if (argc > 2) {
if (!strcasecmp(argv[0], "debug")) {
ftdm_span_t *span = NULL;
if (ftdm_span_find_by_name(argv[1], &span) == FTDM_SUCCESS) {
pritap_t *pritap = span->signal_data;
if (span->start != ftdm_pritap_start) {
stream->write_function(stream, "%s: -ERR invalid span.\n", __FILE__);
goto done;
}
pri_set_debug(pritap->pri, parse_debug(argv[2]));
stream->write_function(stream, "%s: +OK debug set.\n", __FILE__);
goto done;
} else {
stream->write_function(stream, "%s: -ERR invalid span.\n", __FILE__);
goto done;
}
}
}
stream->write_function(stream, "%s: -ERR invalid command.\n", __FILE__);
done:
ftdm_safe_free(mycmd);
return FTDM_SUCCESS;
}
static FIO_IO_LOAD_FUNCTION(ftdm_pritap_io_init)
{
memset(&ftdm_pritap_interface, 0, sizeof(ftdm_pritap_interface));
ftdm_pritap_interface.name = "pritap";
ftdm_pritap_interface.api = ftdm_pritap_api;
*fio = &ftdm_pritap_interface;
return FTDM_SUCCESS;
}
static FIO_SIG_LOAD_FUNCTION(ftdm_pritap_init)
{
pri_set_error(s_pri_error);
pri_set_message(s_pri_message);
return FTDM_SUCCESS;
}
static ftdm_state_map_t pritap_state_map = {
{
{
ZSD_INBOUND,
ZSM_UNACCEPTABLE,
{FTDM_CHANNEL_STATE_DOWN, FTDM_END},
{FTDM_CHANNEL_STATE_RING, FTDM_END}
},
{
ZSD_INBOUND,
ZSM_UNACCEPTABLE,
{FTDM_CHANNEL_STATE_RING, FTDM_END},
{FTDM_CHANNEL_STATE_HANGUP, FTDM_CHANNEL_STATE_TERMINATING, FTDM_CHANNEL_STATE_PROGRESS, FTDM_CHANNEL_STATE_PROGRESS_MEDIA, FTDM_CHANNEL_STATE_UP, FTDM_END}
},
{
ZSD_INBOUND,
ZSM_UNACCEPTABLE,
{FTDM_CHANNEL_STATE_HANGUP, FTDM_END},
{FTDM_CHANNEL_STATE_TERMINATING, FTDM_END},
},
{
ZSD_INBOUND,
ZSM_UNACCEPTABLE,
{FTDM_CHANNEL_STATE_TERMINATING, FTDM_END},
{FTDM_CHANNEL_STATE_DOWN, FTDM_END},
},
{
ZSD_INBOUND,
ZSM_UNACCEPTABLE,
{FTDM_CHANNEL_STATE_PROGRESS, FTDM_END},
{FTDM_CHANNEL_STATE_HANGUP, FTDM_CHANNEL_STATE_TERMINATING, FTDM_CHANNEL_STATE_PROGRESS_MEDIA, FTDM_CHANNEL_STATE_UP, FTDM_END},
},
{
ZSD_INBOUND,
ZSM_UNACCEPTABLE,
{FTDM_CHANNEL_STATE_PROGRESS_MEDIA, FTDM_END},
{FTDM_CHANNEL_STATE_HANGUP, FTDM_CHANNEL_STATE_TERMINATING, FTDM_CHANNEL_STATE_UP, FTDM_END},
},
{
ZSD_INBOUND,
ZSM_UNACCEPTABLE,
{FTDM_CHANNEL_STATE_UP, FTDM_END},
{FTDM_CHANNEL_STATE_HANGUP, FTDM_CHANNEL_STATE_TERMINATING, FTDM_END},
},
}
};
static __inline__ void state_advance(ftdm_channel_t *ftdmchan)
{
ftdm_status_t status;
ftdm_sigmsg_t sig;
ftdm_channel_t *peerchan = ftdmchan->call_data;
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "processing state %s\n", ftdm_channel_state2str(ftdmchan->state));
memset(&sig, 0, sizeof(sig));
sig.chan_id = ftdmchan->chan_id;
sig.span_id = ftdmchan->span_id;
sig.channel = ftdmchan;
switch (ftdmchan->state) {
case FTDM_CHANNEL_STATE_DOWN:
{
ftdmchan->call_data = NULL;
ftdm_channel_close(&ftdmchan);
peerchan->call_data = NULL;
ftdm_channel_close(&peerchan);
}
break;
case FTDM_CHANNEL_STATE_PROGRESS:
case FTDM_CHANNEL_STATE_PROGRESS_MEDIA:
case FTDM_CHANNEL_STATE_UP:
case FTDM_CHANNEL_STATE_HANGUP:
break;
case FTDM_CHANNEL_STATE_RING:
{
sig.event_id = FTDM_SIGEVENT_START;
if ((status = ftdm_span_send_signal(ftdmchan->span, &sig) != FTDM_SUCCESS)) {
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_HANGUP);
}
}
break;
case FTDM_CHANNEL_STATE_TERMINATING:
{
if (ftdmchan->last_state != FTDM_CHANNEL_STATE_HANGUP) {
sig.event_id = FTDM_SIGEVENT_STOP;
status = ftdm_span_send_signal(ftdmchan->span, &sig);
}
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_DOWN);
}
break;
default:
{
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "ignoring state change from %s to %s\n", ftdm_channel_state2str(ftdmchan->last_state), ftdm_channel_state2str(ftdmchan->state));
}
break;
}
return;
}
static __inline__ void pritap_check_state(ftdm_span_t *span)
{
if (ftdm_test_flag(span, FTDM_SPAN_STATE_CHANGE)) {
uint32_t j;
ftdm_clear_flag_locked(span, FTDM_SPAN_STATE_CHANGE);
for(j = 1; j <= span->chan_count; j++) {
if (ftdm_test_flag((span->channels[j]), FTDM_CHANNEL_STATE_CHANGE)) {
ftdm_mutex_lock(span->channels[j]->mutex);
ftdm_clear_flag((span->channels[j]), FTDM_CHANNEL_STATE_CHANGE);
state_advance(span->channels[j]);
ftdm_channel_complete_state(span->channels[j]);
ftdm_mutex_unlock(span->channels[j]->mutex);
}
}
}
}
static int pri_io_read(struct pri *pri, void *buf, int buflen)
{
int res;
ftdm_status_t zst;
pritap_t *pritap = pri_get_userdata(pri);
ftdm_size_t len = buflen;
if ((zst = ftdm_channel_read(pritap->dchan, buf, &len)) != FTDM_SUCCESS) {
if (zst == FTDM_FAIL) {
ftdm_log(FTDM_LOG_CRIT, "span %d D channel read fail! [%s]\n", pritap->span->span_id, pritap->dchan->last_error);
} else {
ftdm_log(FTDM_LOG_CRIT, "span %d D channel read timeout!\n", pritap->span->span_id);
}
return -1;
}
res = (int)len;
memset(&((unsigned char*)buf)[res],0,2);
res += 2;
return res;
}
static int pri_io_write(struct pri *pri, void *buf, int buflen)
{
pritap_t *pritap = pri_get_userdata(pri);
ftdm_size_t len = buflen - 2;
if (ftdm_channel_write(pritap->dchan, buf, buflen, &len) != FTDM_SUCCESS) {
ftdm_log(FTDM_LOG_CRIT, "span %d D channel write failed! [%s]\n", pritap->span->span_id, pritap->dchan->last_error);
return -1;
}
return (int)buflen;
}
static int tap_pri_get_crv(struct pri *ctrl, q931_call *call)
{
int callmode = 0;
int crv = pri_get_crv(ctrl, call, &callmode);
crv <<= 3;
crv |= (callmode & 0x7);
return crv;
}
static passive_call_t *tap_pri_get_pcall_bycrv(pritap_t *pritap, int crv)
{
int i;
int tstcrv;
ftdm_mutex_lock(pritap->pcalls_lock);
for (i = 0; i < ftdm_array_len(pritap->pcalls); i++) {
tstcrv = pritap->pcalls[i].callref ? tap_pri_get_crv(pritap->pri, pritap->pcalls[i].callref) : 0;
if (pritap->pcalls[i].callref && tstcrv == crv) {
if (!pritap->pcalls[i].inuse) {
ftdm_log(FTDM_LOG_ERROR, "Found crv %d in slot %d of span %s with call %p but is no longer in use!\n",
crv, i, pritap->span->name, pritap->pcalls[i].callref);
continue;
}
ftdm_mutex_unlock(pritap->pcalls_lock);
return &pritap->pcalls[i];
}
}
ftdm_mutex_unlock(pritap->pcalls_lock);
return NULL;
}
static passive_call_t *tap_pri_get_pcall(pritap_t *pritap, void *callref)
{
int i;
int crv;
ftdm_mutex_lock(pritap->pcalls_lock);
for (i = 0; i < ftdm_array_len(pritap->pcalls); i++) {
if (pritap->pcalls[i].callref && !pritap->pcalls[i].inuse) {
crv = tap_pri_get_crv(pritap->pri, pritap->pcalls[i].callref);
/* garbage collection */
ftdm_log(FTDM_LOG_DEBUG, "Garbage collecting callref %d/%p from span %s in slot %d\n",
crv, pritap->pcalls[i].callref, pritap->span->name, i);
pri_passive_destroycall(pritap->pri, pritap->pcalls[i].callref);
memset(&pritap->pcalls[i], 0, sizeof(pritap->pcalls[0]));
}
if (callref == pritap->pcalls[i].callref) {
pritap->pcalls[i].inuse = 1;
ftdm_mutex_unlock(pritap->pcalls_lock);
return &pritap->pcalls[i];
}
}
ftdm_mutex_unlock(pritap->pcalls_lock);
return NULL;
}
static void tap_pri_put_pcall(pritap_t *pritap, void *callref)
{
int i;
int crv;
int tstcrv;
if (!callref) {
ftdm_log(FTDM_LOG_ERROR, "Cannot put pcall for null callref in span %s\n", pritap->span->name);
return;
}
ftdm_mutex_lock(pritap->pcalls_lock);
crv = tap_pri_get_crv(pritap->pri, callref);
for (i = 0; i < ftdm_array_len(pritap->pcalls); i++) {
if (!pritap->pcalls[i].callref) {
continue;
}
tstcrv = tap_pri_get_crv(pritap->pri, pritap->pcalls[i].callref);
if (tstcrv == crv) {
ftdm_log(FTDM_LOG_DEBUG, "releasing slot %d in span %s used by callref %d/%p\n", i,
pritap->span->name, crv, pritap->pcalls[i].callref);
if (!pritap->pcalls[i].inuse) {
ftdm_log(FTDM_LOG_ERROR, "slot %d in span %s used by callref %d/%p was released already?\n",
i, pritap->span->name, crv, pritap->pcalls[i].callref);
}
pritap->pcalls[i].inuse = 0;
}
}
ftdm_mutex_unlock(pritap->pcalls_lock);
}
static __inline__ ftdm_channel_t *tap_pri_get_fchan(pritap_t *pritap, passive_call_t *pcall, int channel)
{
ftdm_channel_t *fchan = NULL;
int chanpos = PRI_CHANNEL(channel);
if (!chanpos || chanpos > pritap->span->chan_count) {
ftdm_log(FTDM_LOG_CRIT, "Invalid pri tap channel %d requested in span %s\n", channel, pritap->span->name);
return NULL;
}
fchan = pritap->span->channels[PRI_CHANNEL(channel)];
if (ftdm_test_flag(fchan, FTDM_CHANNEL_INUSE)) {
ftdm_log(FTDM_LOG_ERROR, "Channel %d requested in span %s is already in use!\n", channel, pritap->span->name);
return NULL;
}
if (ftdm_channel_open_chan(fchan) != FTDM_SUCCESS) {
ftdm_log(FTDM_LOG_ERROR, "Could not open tap channel %d requested in span %s\n", channel, pritap->span->name);
return NULL;
}
memset(&fchan->caller_data, 0, sizeof(fchan->caller_data));
ftdm_set_string(fchan->caller_data.cid_num.digits, pcall->callingnum.digits);
if (!ftdm_strlen_zero(pcall->callingname)) {
ftdm_set_string(fchan->caller_data.cid_name, pcall->callingname);
} else {
ftdm_set_string(fchan->caller_data.cid_name, pcall->callingnum.digits);
}
ftdm_set_string(fchan->caller_data.ani.digits, pcall->callingani.digits);
ftdm_set_string(fchan->caller_data.dnis.digits, pcall->callednum.digits);
return fchan;
}
static void handle_pri_passive_event(pritap_t *pritap, pri_event *e)
{
passive_call_t *pcall = NULL;
passive_call_t *peerpcall = NULL;
ftdm_channel_t *fchan = NULL;
ftdm_channel_t *peerfchan = NULL;
int layer1, transcap = 0;
int crv = 0;
pritap_t *peertap = pritap->peerspan->signal_data;
switch (e->e) {
case PRI_EVENT_RING:
/* we cannot use ftdm_channel_t because we still dont know which channel will be used
* (ie, flexible channel was requested), thus, we need our own list of call references */
crv = tap_pri_get_crv(pritap->pri, e->ring.call);
ftdm_log(FTDM_LOG_DEBUG, "Ring on channel %s:%d:%d with callref %d\n",
pritap->span->name, PRI_SPAN(e->ring.channel), PRI_CHANNEL(e->ring.channel), crv);
pcall = tap_pri_get_pcall_bycrv(pritap, crv);
if (pcall) {
ftdm_log(FTDM_LOG_WARNING, "There is a call with callref %d already, ignoring duplicated ring event\n", crv);
break;
}
pcall = tap_pri_get_pcall(pritap, NULL);
if (!pcall) {
ftdm_log(FTDM_LOG_ERROR, "Failed to get a free passive PRI call slot for callref %d, this is a bug!\n", crv);
break;
}
pcall->callref = e->ring.call;
ftdm_set_string(pcall->callingnum.digits, e->ring.callingnum);
ftdm_set_string(pcall->callingani.digits, e->ring.callingani);
ftdm_set_string(pcall->callednum.digits, e->ring.callednum);
ftdm_set_string(pcall->callingname, e->ring.callingname);
break;
case PRI_EVENT_PROGRESS:
crv = tap_pri_get_crv(pritap->pri, e->ring.call);
ftdm_log(FTDM_LOG_DEBUG, "Progress on channel %s:%d:%d with callref %d\n",
pritap->span->name, PRI_SPAN(e->proceeding.channel), PRI_CHANNEL(e->proceeding.channel), crv);
break;
case PRI_EVENT_PROCEEDING:
crv = tap_pri_get_crv(pritap->pri, e->proceeding.call);
/* at this point we should know the real b chan that will be used and can therefore proceed to notify about the call, but
* only if a couple of call tests are passed first */
ftdm_log(FTDM_LOG_DEBUG, "Proceeding on channel %s:%d:%d with callref %d\n",
pritap->span->name, PRI_SPAN(e->proceeding.channel), PRI_CHANNEL(e->proceeding.channel), crv);
/* check that we already know about this call in the peer PRI (which was the one receiving the PRI_EVENT_RING event) */
if (!(pcall = tap_pri_get_pcall_bycrv(peertap, crv))) {
ftdm_log(FTDM_LOG_DEBUG,
"ignoring proceeding in channel %s:%d:%d for callref %d since we don't know about it",
pritap->span->name, PRI_SPAN(e->proceeding.channel), PRI_CHANNEL(e->proceeding.channel), crv);
break;
}
if (pcall->proceeding) {
ftdm_log(FTDM_LOG_DEBUG, "Ignoring duplicated proceeding with callref %d\n", crv);
break;
}
peerpcall = tap_pri_get_pcall(pritap, NULL);
if (!peerpcall) {
ftdm_log(FTDM_LOG_ERROR, "Failed to get a free peer PRI passive call slot for callref %d in span %s, this is a bug!\n",
crv, pritap->span->name);
break;
}
peerpcall->callref = e->proceeding.call;
/* check that the layer 1 and trans capability are supported */
layer1 = pri_get_layer1(peertap->pri, pcall->callref);
transcap = pri_get_transcap(peertap->pri, pcall->callref);
if (PRI_LAYER_1_ULAW != layer1 && PRI_LAYER_1_ALAW != layer1) {
ftdm_log(FTDM_LOG_NOTICE, "Not monitoring callref %d with unsupported layer 1 format %d\n", crv, layer1);
break;
}
if (transcap != PRI_TRANS_CAP_SPEECH && transcap != PRI_TRANS_CAP_3_1K_AUDIO && transcap != PRI_TRANS_CAP_7K_AUDIO) {
ftdm_log(FTDM_LOG_NOTICE, "Not monitoring callref %d with unsupported capability %d\n", crv, transcap);
break;
}
fchan = tap_pri_get_fchan(pritap, pcall, e->proceeding.channel);
if (!fchan) {
ftdm_log(FTDM_LOG_ERROR, "Proceeding requested on odd/unavailable channel %s:%d:%d for callref %d\n",
pritap->span->name, PRI_SPAN(e->proceeding.channel), PRI_CHANNEL(e->proceeding.channel), crv);
break;
}
pcall->fchan = fchan;
peerfchan = tap_pri_get_fchan(peertap, pcall, e->proceeding.channel);
if (!peerfchan) {
ftdm_log(FTDM_LOG_ERROR, "Proceeding requested on odd/unavailable channel %s:%d:%d for callref %d\n",
peertap->span->name, PRI_SPAN(e->proceeding.channel), PRI_CHANNEL(e->proceeding.channel), crv);
break;
}
peerpcall->fchan = fchan;
fchan->call_data = peerfchan;
peerfchan->call_data = fchan;
ftdm_set_state_locked(fchan, FTDM_CHANNEL_STATE_RING);
break;
case PRI_EVENT_ANSWER:
crv = tap_pri_get_crv(pritap->pri, e->answer.call);
ftdm_log(FTDM_LOG_DEBUG, "Answer on channel %s:%d:%d with callref %d\n",
pritap->span->name, PRI_SPAN(e->answer.channel), PRI_CHANNEL(e->answer.channel), crv);
if (!(pcall = tap_pri_get_pcall_bycrv(pritap, crv))) {
ftdm_log(FTDM_LOG_DEBUG,
"ignoring answer in channel %s:%d:%d for callref %d since we don't know about it",
pritap->span->name, PRI_SPAN(e->proceeding.channel), PRI_CHANNEL(e->proceeding.channel), crv);
break;
}
ftdm_log_chan(pcall->fchan, FTDM_LOG_NOTICE, "Tapped call was answered in state %s\n", ftdm_channel_state2str(pcall->fchan->state));
break;
case PRI_EVENT_HANGUP_REQ:
crv = tap_pri_get_crv(pritap->pri, e->hangup.call);
ftdm_log(FTDM_LOG_DEBUG, "Hangup on channel %s:%d:%d with callref %d\n",
pritap->span->name, PRI_SPAN(e->answer.channel), PRI_CHANNEL(e->answer.channel), crv);
if (!(pcall = tap_pri_get_pcall_bycrv(pritap, crv))) {
ftdm_log(FTDM_LOG_DEBUG,
"ignoring hangup in channel %s:%d:%d for callref %d since we don't know about it",
pritap->span->name, PRI_SPAN(e->proceeding.channel), PRI_CHANNEL(e->proceeding.channel), crv);
break;
}
fchan = pcall->fchan;
ftdm_set_state_locked(fchan, FTDM_CHANNEL_STATE_TERMINATING);
break;
case PRI_EVENT_HANGUP_ACK:
crv = tap_pri_get_crv(pritap->pri, e->hangup.call);
ftdm_log(FTDM_LOG_DEBUG, "Hangup ack on channel %s:%d:%d with callref %d\n",
pritap->span->name, PRI_SPAN(e->answer.channel), PRI_CHANNEL(e->answer.channel), crv);
tap_pri_put_pcall(pritap, e->hangup.call);
tap_pri_put_pcall(peertap, e->hangup.call);
break;
default:
ftdm_log(FTDM_LOG_DEBUG, "Ignoring passive event %s on span %s\n", pri_event2str(e->gen.e), pritap->span->name);
break;
}
}
static void *ftdm_pritap_run(ftdm_thread_t *me, void *obj)
{
ftdm_span_t *span = (ftdm_span_t *) obj;
pritap_t *pritap = span->signal_data;
pri_event *event = NULL;
struct pollfd dpoll = { 0, 0, 0 };
int rc = 0;
ftdm_log(FTDM_LOG_DEBUG, "Tapping PRI thread started on span %d\n", span->span_id);
pritap->span = span;
ftdm_set_flag(span, FTDM_SPAN_IN_THREAD);
if (ftdm_channel_open(span->span_id, pritap->dchan->chan_id, &pritap->dchan) != FTDM_SUCCESS) {
ftdm_log(FTDM_LOG_ERROR, "Failed to open D-channel for span %s\n", span->name);
goto done;
}
if ((pritap->pri = pri_new_cb(pritap->dchan->sockfd, PRI_NETWORK, PRI_SWITCH_NI2, pri_io_read, pri_io_write, pritap))){
pri_set_debug(pritap->pri, pritap->debug);
} else {
ftdm_log(FTDM_LOG_CRIT, "Failed to create tapping PRI\n");
goto done;
}
dpoll.fd = pritap->dchan->sockfd;
while (ftdm_running() && !ftdm_test_flag(span, FTDM_SPAN_STOP_THREAD)) {
pritap_check_state(span);
dpoll.revents = 0;
dpoll.events = POLLIN;
rc = poll(&dpoll, 1, 10);
if (rc < 0) {
if (errno == EINTR) {
ftdm_log(FTDM_LOG_DEBUG, "D-channel waiting interrupted, continuing ...\n");
continue;
}
ftdm_log(FTDM_LOG_ERROR, "poll failed: %s\n", strerror(errno));
continue;
}
pri_schedule_run(pritap->pri);
if (rc) {
if (dpoll.revents & POLLIN) {
event = pri_read_event(pritap->pri);
if (event) {
handle_pri_passive_event(pritap, event);
}
} else {
ftdm_log(FTDM_LOG_WARNING, "nothing to read?\n");
}
}
pritap_check_state(span);
}
done:
ftdm_log(FTDM_LOG_DEBUG, "Tapping PRI thread ended on span %d\n", span->span_id);
ftdm_clear_flag(span, FTDM_SPAN_IN_THREAD);
ftdm_clear_flag(pritap, PRITAP_RUNNING);
return NULL;
}
static ftdm_status_t ftdm_pritap_stop(ftdm_span_t *span)
{
pritap_t *pritap = span->signal_data;
if (!ftdm_test_flag(pritap, PRITAP_RUNNING)) {
return FTDM_FAIL;
}
ftdm_set_flag(span, FTDM_SPAN_STOP_THREAD);
while (ftdm_test_flag(span, FTDM_SPAN_IN_THREAD)) {
ftdm_sleep(100);
}
ftdm_mutex_destroy(&pritap->pcalls_lock);
return FTDM_SUCCESS;
}
static ftdm_status_t ftdm_pritap_sig_read(ftdm_channel_t *ftdmchan, void *data, ftdm_size_t size)
{
ftdm_status_t status;
fio_codec_t codec_func;
ftdm_channel_t *peerchan = ftdmchan->call_data;
int16_t chanbuf[size];
int16_t peerbuf[size];
int16_t mixedbuf[size];
int i = 0;
ftdm_size_t sizeread = size;
if (!FTDM_IS_VOICE_CHANNEL(ftdmchan) || !ftdmchan->call_data) {
return FTDM_SUCCESS;
}
if (ftdmchan->native_codec != peerchan->native_codec) {
ftdm_log_chan(ftdmchan, FTDM_LOG_CRIT, "Invalid peer channel with format %d, ours = %d\n",
peerchan->native_codec, ftdmchan->native_codec);
return FTDM_FAIL;
}
memcpy(chanbuf, data, size);
status = peerchan->fio->read(peerchan, peerbuf, &sizeread);
if (status != FTDM_SUCCESS) {
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_ERROR, "Failed to read from peer channel!\n");
return FTDM_FAIL;
}
if (sizeread != size) {
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "read from peer channel only %d bytes!\n", sizeread);
return FTDM_FAIL;
}
codec_func = peerchan->native_codec == FTDM_CODEC_ULAW ? fio_ulaw2slin : peerchan->native_codec == FTDM_CODEC_ALAW ? fio_alaw2slin : NULL;
if (codec_func) {
sizeread = size;
codec_func(chanbuf, sizeof(chanbuf), &sizeread);
sizeread = size;
codec_func(peerbuf, sizeof(peerbuf), &sizeread);
}
for (i = 0; i < size; i++) {
mixedbuf[i] = ftdm_saturated_add(chanbuf[i], peerbuf[i]);
}
codec_func = peerchan->native_codec == FTDM_CODEC_ULAW ? fio_slin2ulaw : peerchan->native_codec == FTDM_CODEC_ALAW ? fio_slin2alaw : NULL;
if (codec_func) {
size = sizeof(mixedbuf);
codec_func(mixedbuf, size, &size);
}
memcpy(data, mixedbuf, size);
return FTDM_SUCCESS;
}
static ftdm_status_t ftdm_pritap_start(ftdm_span_t *span)
{
ftdm_status_t ret;
pritap_t *pritap = span->signal_data;
if (ftdm_test_flag(pritap, PRITAP_RUNNING)) {
return FTDM_FAIL;
}
ftdm_mutex_create(&pritap->pcalls_lock);
ftdm_clear_flag(span, FTDM_SPAN_STOP_THREAD);
ftdm_clear_flag(span, FTDM_SPAN_IN_THREAD);
ftdm_set_flag(pritap, PRITAP_RUNNING);
ret = ftdm_thread_create_detached(ftdm_pritap_run, span);
if (ret != FTDM_SUCCESS) {
return ret;
}
return ret;
}
static FIO_CONFIGURE_SPAN_SIGNALING_FUNCTION(ftdm_pritap_configure_span)
{
uint32_t i;
const char *var, *val;
const char *debug = NULL;
ftdm_channel_t *dchan = NULL;
pritap_t *pritap = NULL;
ftdm_span_t *peerspan = NULL;
unsigned paramindex = 0;
if (span->trunk_type >= FTDM_TRUNK_NONE) {
ftdm_log(FTDM_LOG_WARNING, "Invalid trunk type '%s' defaulting to T1.\n", ftdm_trunk_type2str(span->trunk_type));
span->trunk_type = FTDM_TRUNK_T1;
}
for (i = 1; i <= span->chan_count; i++) {
if (span->channels[i]->type == FTDM_CHAN_TYPE_DQ921) {
dchan = span->channels[i];
}
}
if (!dchan) {
ftdm_log(FTDM_LOG_ERROR, "No d-channel specified in freetdm.conf!\n", ftdm_trunk_type2str(span->trunk_type));
return FTDM_FAIL;
}
for (paramindex = 0; ftdm_parameters[paramindex].var; paramindex++) {
var = ftdm_parameters[paramindex].var;
val = ftdm_parameters[paramindex].val;
ftdm_log(FTDM_LOG_DEBUG, "Tapping PRI key=value, %s=%s\n", var, val);
if (!strcasecmp(var, "debug")) {
debug = val;
} else if (!strcasecmp(var, "peerspan")) {
if (ftdm_span_find_by_name(val, &peerspan) != FTDM_SUCCESS) {
ftdm_log(FTDM_LOG_ERROR, "Invalid tapping peer span %s\n", val);
break;
}
} else {
ftdm_log(FTDM_LOG_ERROR, "Unknown pri tapping parameter [%s]", var);
}
}
if (!peerspan) {
ftdm_log(FTDM_LOG_ERROR, "No valid peerspan was specified!\n");
return FTDM_FAIL;
}
pritap = ftdm_calloc(1, sizeof(*pritap));
if (!pritap) {
return FTDM_FAIL;
}
pritap->debug = parse_debug(debug);
pritap->dchan = dchan;
pritap->peerspan = peerspan;
span->start = ftdm_pritap_start;
span->stop = ftdm_pritap_stop;
span->sig_read = ftdm_pritap_sig_read;
span->signal_cb = sig_cb;
span->signal_data = pritap;
span->signal_type = FTDM_SIGTYPE_ISDN;
span->outgoing_call = pritap_outgoing_call;
span->get_channel_sig_status = pritap_get_channel_sig_status;
span->get_span_sig_status = pritap_get_span_sig_status;
span->state_map = &pritap_state_map;
return FTDM_SUCCESS;
}
/**
* \brief FreeTDM pritap signaling and IO module definition
*/
ftdm_module_t ftdm_module = {
"pritap",
ftdm_pritap_io_init,
ftdm_pritap_unload,
ftdm_pritap_init,
NULL,
NULL,
ftdm_pritap_configure_span,
};
/* 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:
*/