diff --git a/libs/freetdm/src/ftmod/ftmod_pritap/ftmod_pritap.c b/libs/freetdm/src/ftmod/ftmod_pritap/ftmod_pritap.c index 1441d54f8d..fbd7979ace 100644 --- a/libs/freetdm/src/ftmod/ftmod_pritap/ftmod_pritap.c +++ b/libs/freetdm/src/ftmod/ftmod_pritap/ftmod_pritap.c @@ -35,10 +35,24 @@ #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; @@ -46,7 +60,8 @@ typedef struct pritap { ftdm_channel_t *dchan; ftdm_span_t *span; ftdm_span_t *peerspan; - struct pritap *pritap; + ftdm_mutex_t *pcalls_lock; + passive_call_t pcalls[FTDM_MAX_CHANNELS_PHYSICAL_SPAN]; } pritap_t; static FIO_IO_UNLOAD_FUNCTION(ftdm_pritap_unload) @@ -219,21 +234,26 @@ static ftdm_state_map_t pritap_state_map = { { ZSD_INBOUND, ZSM_UNACCEPTABLE, - {FTDM_CHANNEL_STATE_HANGUP, FTDM_CHANNEL_STATE_TERMINATING, FTDM_END}, - {FTDM_CHANNEL_STATE_HANGUP_COMPLETE, FTDM_CHANNEL_STATE_DOWN, FTDM_END}, + {FTDM_CHANNEL_STATE_HANGUP, FTDM_END}, + {FTDM_CHANNEL_STATE_TERMINATING, FTDM_END}, }, { ZSD_INBOUND, ZSM_UNACCEPTABLE, - {FTDM_CHANNEL_STATE_HANGUP_COMPLETE, FTDM_END}, + {FTDM_CHANNEL_STATE_TERMINATING, FTDM_END}, {FTDM_CHANNEL_STATE_DOWN, FTDM_END}, }, { ZSD_INBOUND, ZSM_UNACCEPTABLE, - {FTDM_CHANNEL_STATE_PROGRESS, FTDM_CHANNEL_STATE_PROGRESS_MEDIA, FTDM_END}, - {FTDM_CHANNEL_STATE_HANGUP, FTDM_CHANNEL_STATE_TERMINATING, FTDM_CHANNEL_STATE_PROGRESS_MEDIA, - FTDM_CHANNEL_STATE_CANCEL, FTDM_CHANNEL_STATE_UP, FTDM_END}, + {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, @@ -242,19 +262,16 @@ static ftdm_state_map_t pritap_state_map = { {FTDM_CHANNEL_STATE_HANGUP, FTDM_CHANNEL_STATE_TERMINATING, FTDM_END}, }, - } }; static __inline__ void state_advance(ftdm_channel_t *ftdmchan) { - pritap_t *pritap = ftdmchan->span->signal_data; ftdm_status_t status; ftdm_sigmsg_t sig; - q931_call *call = (q931_call *) ftdmchan->call_data; + ftdm_channel_t *peerchan = ftdmchan->call_data; - - ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "processing state %s\n", ftdmchan->span_id, ftdmchan->chan_id, ftdm_channel_state2str(ftdmchan->state)); + 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; @@ -264,26 +281,22 @@ static __inline__ void state_advance(ftdm_channel_t *ftdmchan) switch (ftdmchan->state) { case FTDM_CHANNEL_STATE_DOWN: { - ftdmchan->call_data = NULL; ftdm_channel_done(ftdmchan); + ftdmchan->call_data = NULL; + + ftdm_channel_done(peerchan); + peerchan->call_data = NULL; } break; case FTDM_CHANNEL_STATE_PROGRESS: - { - pri_progress(pritap->pri, call, ftdmchan->chan_id, 1); - } - break; - case FTDM_CHANNEL_STATE_PROGRESS_MEDIA: - { - pri_proceeding(pritap->pri, call, ftdmchan->chan_id, 1); - } + case FTDM_CHANNEL_STATE_UP: + case FTDM_CHANNEL_STATE_HANGUP: break; case FTDM_CHANNEL_STATE_RING: { - pri_acknowledge(pritap->pri, call, ftdmchan->chan_id, 0); 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); @@ -291,34 +304,15 @@ static __inline__ void state_advance(ftdm_channel_t *ftdmchan) } break; - case FTDM_CHANNEL_STATE_UP: - { - pri_answer(pritap->pri, call, 0, 1); - } - break; - - case FTDM_CHANNEL_STATE_HANGUP: - { - if (call) { - pri_hangup(pritap->pri, call, ftdmchan->caller_data.hangup_cause); - pri_destroycall(pritap->pri, call); - ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_DOWN); - } else { - ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_RESTART); - } - } - break; - - case FTDM_CHANNEL_STATE_HANGUP_COMPLETE: - break; - case FTDM_CHANNEL_STATE_TERMINATING: { - sig.event_id = FTDM_SIGEVENT_STOP; - status = ftdm_span_send_signal(ftdmchan->span, &sig); + 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: { @@ -385,28 +379,277 @@ static int pri_io_write(struct pri *pri, void *buf, int buflen) 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) { - ftdm_log(FTDM_LOG_NOTICE, "passive event %s on span %s\n", pri_event2str(e->gen.e), pritap->span->name); + 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: + 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: @@ -502,6 +745,7 @@ static ftdm_status_t ftdm_pritap_stop(ftdm_span_t *span) ftdm_sleep(100); } + ftdm_mutex_destroy(&pritap->pcalls_lock); return FTDM_SUCCESS; } @@ -514,6 +758,8 @@ static ftdm_status_t ftdm_pritap_start(ftdm_span_t *span) 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);