/* * Copyright (c) 2010, Moises Silva * 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 #include #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: */