]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
freetdm: make pri tapping actually work - missing audio mix
authorMoises Silva <moy@sangoma.com>
Sat, 31 Jul 2010 21:33:20 +0000 (17:33 -0400)
committerMoises Silva <moy@sangoma.com>
Sat, 31 Jul 2010 21:33:20 +0000 (17:33 -0400)
libs/freetdm/src/ftmod/ftmod_pritap/ftmod_pritap.c

index 1441d54f8d062db69bace72656e2be22bc0339e4..fbd7979ace688a893b0199ced8761267b031f6da 100644 (file)
 #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;
@@ -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);                    
-               }
-               break;
+                       ftdmchan->call_data = NULL;
 
-       case FTDM_CHANNEL_STATE_PROGRESS:
-               {
-                       pri_progress(pritap->pri, call, ftdmchan->chan_id, 1);
+                       ftdm_channel_done(peerchan);
+                       peerchan->call_data = NULL;
                }
                break;
 
+       case FTDM_CHANNEL_STATE_PROGRESS:
        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);