--- asterisk-1.4.17/apps/app_dial.c 2007-12-07 11:38:48.000000000 -0500
+++ asterisk-1.4.17/apps/app_dial.c 2008-01-26 04:52:17.000000000 -0500
@@ -25,6 +25,10 @@
* \ingroup applications
*/
+/*** MODULEINFO
+ Curl
+ ***/
+
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision: 91783 $")
@@ -62,6 +66,7 @@
#include "asterisk/privacy.h"
#include "asterisk/stringfields.h"
#include "asterisk/global_datastores.h"
+#include
static char *app = "Dial";
@@ -130,9 +135,19 @@
" the DTMF sequence defined for call parking in features.conf.\n"
" K - Allow the calling party to enable parking of the call by sending\n"
" the DTMF sequence defined for call parking in features.conf.\n"
-" L(x[:y][:z]) - Limit the call to 'x' ms. Play a warning when 'y' ms are\n"
-" left. Repeat the warning every 'z' ms. The following special\n"
-" variables can be used with this option:\n"
+" L(x[:y][:z][:rtcc url]) - Limit the call to 'x' ms. Play a warning when 'y' ms are\n"
+" left. Repeat the warning every 'z' ms.\n"
+" If a real-time call control (rtcc) URL is specified it will be is\n"
+" specified it will be periodically called via Curl. The return value\n"
+" will be added to x to set the duration of the call.\n"
+" Header values added appended to the URL querystring are:\n"
+" - accountcode, the accountcode of the caller if set,\n"
+" - dst, the destination for the call,\n"
+" - duration, the length of time in seconds that the call has been up,\n"
+" - timelimit, the current time limit in seconds that has been set on the call,\n"
+" - channelid, the unique channel id assigned to the caller (source).\n"
+" - seqno, sequence number of the rtcc check, incremented after each call.\n"
+" The following special variables can be used with this option:\n"
" * LIMIT_PLAYAUDIO_CALLER yes|no (default yes)\n"
" Play sounds to the caller.\n"
" * LIMIT_PLAYAUDIO_CALLEE yes|no\n"
@@ -141,6 +156,17 @@
" * LIMIT_CONNECT_FILE File to play when call begins.\n"
" * LIMIT_WARNING_FILE File to play as warning if 'y' is defined.\n"
" The default is to say the time remaining.\n"
+" * RTCC_INTERVAL The interval in ms at which the the real-time call\n"
+" control URL will be called (60000 by default).\n"
+" * RTCC_INIT_INTERVAL The first interval in ms after the call is\n"
+" answered that the first real-time request should\n"
+" be made, defaults to RTCC_INTERVAL.\n"
+" * RTCC_EXPIRY_INTERVAL Allows for a method of operation where the rtcc\n"
+" is made when there is only this amount of time \n"
+" remaining. If this is set it overrules the interval\n"
+" settings. By default it is disbaled.\n"
+" * RTCC_START_SEQNUM Start sequence number for the real-time call\n"
+" control requests. Defaults to 1.\n"
" m([class]) - Provide hold music to the calling party until a requested\n"
" channel answers. A specific MusicOnHold class can be\n"
" specified.\n"
@@ -214,6 +240,28 @@
" The 'dialargs' are specified in the same format that arguments are provided\n"
"to the Dial application.\n";
+#define RTCC_CURLTIMEOUT_SECONDS 5 /* The timeout value for a Curl call after which it will be cancelled. */
+#define RTCC_INTERVAL_MILLISECONDS 60000 /* The default interval at which the rtcc check will be made. */
+#define RTCC_MININTERVAL_MILLISECONDS 5000 /* The minimum interval at which the rtcc check will be made when using an expiry iterval. */
+#define RTCC_SEQNUM_START 1 /* The default start value for the real-time call control sequence number. */
+
+static pthread_t rtcc_thread = AST_PTHREADT_NULL; /* The thread the real-time call control scheduler will be run on. */
+static struct sched_context* rtcc_sched_con; /* The real-time call control scheduling context. */
+
+/* Structure passed to the real-time call control scheduler after a call has been answered. */
+struct rtcc_struct
+{
+ struct ast_bridge_config * config;
+ const char * caller_accountcode;
+ const char * caller_channel_id;
+ const char * caller_exten;
+ const char * callcontrol_url;
+ int seqnum; /* Inceremented for each real-time call control check. */
+ int rtcc_check_interval;
+ int rtcc_expiry_interval; /* Calculates the interval by subtracting this value from the time remaining. */
+ int hungup;
+};
+
enum {
OPT_ANNOUNCE = (1 << 0),
OPT_RESETCDR = (1 << 1),
@@ -797,6 +845,172 @@
return 0;
}
+/* Callback function for the real-time call control Curl method. The Curl method must return an
+ integer value which indicates how long the call should be extended by in milliseconds. */
+static size_t rtcc_curl_write(void *buffer, size_t size, size_t nmemb, void *userp)
+{
+ int timelimit = atoi((const char*)buffer);
+ *(int*)userp = timelimit;
+ return nmemb;
+}
+
+/* The function that will be preiodically called by the real-time call control thread on calls
+ that have requested real-time call control. The function makes a Curl call to the URL passed in
+ in the origial Dial command and usees the return value to extend the length of the call or
+ terminate it. */
+static int rtcccallback(const void* data) {
+ CURL *curl;
+ CURLcode res;
+ struct rtcc_struct * rtcc_data = (struct rtcc_struct *)data;
+ char callcontrol_url [1024];
+ int timelimit = 0;
+ long int timelimit_seconds = 0;
+ long int time_remaining = 0;
+ int next_rtcc_check = 0; //rtcc_data->rtcc_check_interval; /* Used as the return value and dictates when the next real-time call control check will be perfomed. */
+ struct timeval call_duration;
+
+ ast_log(LOG_DEBUG, "call control accountcode=%s, dst=%s.\n", rtcc_data->caller_accountcode, rtcc_data->caller_exten);
+
+ if(rtcc_data->hungup) {
+ ast_log(LOG_DEBUG, "call control for %s to %s on completed bridge, halting.\n", rtcc_data->caller_accountcode, rtcc_data->caller_exten);
+ ast_free(rtcc_data);
+ return 0;
+ }
+
+ curl = curl_easy_init();
+ if(curl) {
+ call_duration = ast_tvsub(ast_tvnow(), rtcc_data->config->start_time);
+ timelimit_seconds = rtcc_data->config->timelimit / 1000;
+
+ /* Constuct the real-time call control URL. */
+ if(strchr(rtcc_data->callcontrol_url, '?')) {
+ sprintf(callcontrol_url, "%s&accountcode=%s&dst=%s&duration=%li&timelimit=%li&channelid=%s&seqno=%i", rtcc_data->callcontrol_url, rtcc_data->caller_accountcode, rtcc_data->caller_exten, call_duration.tv_sec, timelimit_seconds, rtcc_data->caller_channel_id, rtcc_data->seqnum);
+ }
+ else {
+ sprintf(callcontrol_url, "%s?accountcode=%s&dst=%s&duration=%li&timelimit=%li&channelid=%s&seqno=%i", rtcc_data->callcontrol_url, rtcc_data->caller_accountcode, rtcc_data->caller_exten, call_duration.tv_sec, timelimit_seconds, rtcc_data->caller_channel_id, rtcc_data->seqnum);
+ }
+ ast_log(LOG_DEBUG, "call control url %s", callcontrol_url);
+
+ curl_easy_setopt(curl, CURLOPT_URL, callcontrol_url);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &timelimit);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, rtcc_curl_write);
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, RTCC_CURLTIMEOUT_SECONDS);
+ curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, RTCC_CURLTIMEOUT_SECONDS);
+ res = curl_easy_perform(curl);
+
+ if(res == 0) {
+ /* Extend the call. */
+ if(rtcc_data->config && !rtcc_data->hungup) {
+ rtcc_data->config->timelimit += (timelimit * 1000);
+ rtcc_data->config->nexteventts = ast_tvadd(rtcc_data->config->nexteventts, ast_samp2tv(timelimit, 1));
+
+ timelimit_seconds = rtcc_data->config->timelimit / 1000;
+ time_remaining = timelimit_seconds - ast_tvsub(ast_tvnow(), rtcc_data->config->start_time).tv_sec;
+ ast_verbose(VERBOSE_PREFIX_3 "rtcc reserved %is for %s to %s (channelid=%s), time remaining %lis.\n", timelimit, rtcc_data->caller_accountcode, rtcc_data->caller_exten, rtcc_data->caller_channel_id, time_remaining);
+
+ if(timelimit < 0) {
+ /* If a timelimit of less than 0 is returned by the real-time call control server it means it wants to hangup the call or it's the last reservation. In either case no further call control checks are required for this call. */
+ next_rtcc_check = 0;
+ }
+ else {
+ if(rtcc_data->rtcc_expiry_interval > 0) {
+ next_rtcc_check = (time_remaining * 1000 - rtcc_data->rtcc_expiry_interval > RTCC_MININTERVAL_MILLISECONDS) ? time_remaining * 1000 - rtcc_data->rtcc_expiry_interval : RTCC_MININTERVAL_MILLISECONDS;
+ } else {
+ next_rtcc_check = rtcc_data->rtcc_check_interval;
+ }
+ }
+ }
+ } else {
+ ast_log(LOG_WARNING, "Call control curl error from %s (%i) %s.\n", callcontrol_url, res, curl_easy_strerror(res));
+ }
+
+ rtcc_data->seqnum += 1;
+ curl_easy_cleanup(curl);
+ }
+
+ /* Call could have been hungup while waiting for Curl call to return. */
+ if(rtcc_data->hungup) {
+ ast_log(LOG_DEBUG, "call control for %s to %s on completed bridge, halting.\n", rtcc_data->caller_accountcode, rtcc_data->caller_exten);
+ ast_free(rtcc_data);
+ return 0;
+ }
+
+ return next_rtcc_check;
+}
+
+/* Checks the time limits on in-progress calls that have requested real-time call control.
+ Calls that have requested control add an entry to the real-time call control schedule and
+ then periodically have their time limit updated by this function. */
+static void* monitor_rtcc_sched(void * data)
+{
+ ast_mutex_t rtcc_mutex;
+ ast_cond_t rtcc_cond;
+ struct timespec timeout = {0,0};
+ struct timeval now;
+
+ ast_mutex_init(&rtcc_mutex);
+ ast_cond_init(&rtcc_cond, NULL);
+
+ ast_log(LOG_DEBUG, "Call control monitor thread starting.\n");
+
+ for(;;)
+ {
+ ast_sched_runq(rtcc_sched_con);
+
+ int nexteventms = ast_sched_wait(rtcc_sched_con);
+ if(nexteventms == -1) {
+ break;
+ }
+
+ now = ast_tvadd(ast_tvnow(), ast_samp2tv(nexteventms, 1000));
+ timeout.tv_sec = now.tv_sec;
+ timeout.tv_nsec = now.tv_usec * 1000;
+
+ ast_mutex_lock(&rtcc_mutex);
+ ast_cond_timedwait(&rtcc_cond, &rtcc_mutex, &timeout);
+ ast_mutex_unlock(&rtcc_mutex);
+ }
+
+ ast_log(LOG_DEBUG, "Call control monitor thread stopping.\n");
+
+ ast_mutex_destroy(&rtcc_mutex);
+ ast_cond_destroy(&rtcc_cond);
+
+ rtcc_thread = AST_PTHREADT_NULL;
+
+ return 0;
+}
+
+/* Starts the thread to monitor the real-time call control schedule. The schedule
+ contains the list of all calls that are availing of real-time call control and
+ allows them to be monitored by a single thread. */
+static int start_rtcc_thread(void)
+{
+ AST_MUTEX_DEFINE_STATIC(rtcc_threadcontrol_lock);
+
+ ast_log(LOG_DEBUG, "Call control thread starting.\n");
+
+ /* If we're supposed to be stopped -- stay stopped */
+ if (rtcc_thread == AST_PTHREADT_STOP)
+ return 0;
+ ast_mutex_lock(&rtcc_threadcontrol_lock);
+ if (rtcc_thread == pthread_self()) {
+ ast_mutex_unlock(&rtcc_threadcontrol_lock);
+ ast_log(LOG_WARNING, "Cannot kill myself.\n");
+ return -1;
+ }
+ if (rtcc_thread == AST_PTHREADT_NULL) {
+ /* Start a new real-time call control thread */
+ if (ast_pthread_create_background(&rtcc_thread, NULL, monitor_rtcc_sched, NULL) < 0) {
+ ast_mutex_unlock(&rtcc_threadcontrol_lock);
+ ast_log(LOG_ERROR, "Unable to start call control thread.\n");
+ return -1;
+ }
+ }
+ ast_mutex_unlock(&rtcc_threadcontrol_lock);
+ return 0;
+}
+
static int dial_exec_full(struct ast_channel *chan, void *data, struct ast_flags *peerflags, int *continue_exec)
{
int res = -1;
@@ -840,6 +1054,13 @@
char *opt_args[OPT_ARG_ARRAY_SIZE];
struct ast_datastore *datastore = NULL;
int fulldial = 0, num_dialed = 0;
+ struct rtcc_struct * rtcc_data = NULL;
+ char * callcontrol_url_str = NULL;
+ int rtcc_interval = RTCC_INTERVAL_MILLISECONDS;
+ int rtcc_init_interval = RTCC_INTERVAL_MILLISECONDS;
+ int rtcc_expiry_interval = 0;
+ int rtcc_seqnum = RTCC_SEQNUM_START;
+ int schedId = 0;
if (ast_strlen_zero(data)) {
ast_log(LOG_WARNING, "Dial requires an argument (technology/number)\n");
@@ -890,18 +1111,23 @@
}
if (ast_test_flag(&opts, OPT_DURATION_LIMIT) && !ast_strlen_zero(opt_args[OPT_ARG_DURATION_LIMIT])) {
- char *limit_str, *warning_str, *warnfreq_str;
+ char *optionslimit_str, *limit_str, *warning_str, *warnfreq_str;
const char *var;
- warnfreq_str = opt_args[OPT_ARG_DURATION_LIMIT];
- limit_str = strsep(&warnfreq_str, ":");
- warning_str = strsep(&warnfreq_str, ":");
+ optionslimit_str = opt_args[OPT_ARG_DURATION_LIMIT];
+ limit_str = strsep(&optionslimit_str, ":");
+ warning_str = strsep(&optionslimit_str, ":");
timelimit = atol(limit_str);
if (warning_str)
play_warning = atol(warning_str);
- if (warnfreq_str)
+ if (optionslimit_str) {
+ warnfreq_str = strsep(&optionslimit_str, ":");
warning_freq = atol(warnfreq_str);
+ }
+ if (optionslimit_str) {
+ callcontrol_url_str = optionslimit_str;
+ }
if (!timelimit) {
ast_log(LOG_WARNING, "Dial does not accept L(%s), hanging up.\n", limit_str);
@@ -942,10 +1168,38 @@
var = pbx_builtin_getvar_helper(chan,"LIMIT_CONNECT_FILE");
start_sound = S_OR(var, NULL); /* XXX not much of a point in doing this! */
+ var = pbx_builtin_getvar_helper(chan, "RTCC_INTERVAL");
+ if(!ast_strlen_zero(var)) {
+ rtcc_interval = atoi(var);
+ if(rtcc_interval <= 0) {
+ rtcc_interval = RTCC_INTERVAL_MILLISECONDS;
+ }
+ rtcc_init_interval = rtcc_interval;
+ }
+
+ var = pbx_builtin_getvar_helper(chan, "RTCC_INIT_INTERVAL");
+ if(!ast_strlen_zero(var)) {
+ rtcc_init_interval = atoi(var);
+
+ if(rtcc_init_interval <= 0) {
+ rtcc_init_interval = rtcc_interval;
+ }
+ }
+
+ var = pbx_builtin_getvar_helper(chan, "RTCC_EXPIRY_INTERVAL");
+ if(!ast_strlen_zero(var)) {
+ rtcc_expiry_interval = atoi(var);
+ }
+
+ var = pbx_builtin_getvar_helper(chan, "RTCC_START_SEQNUM");
+ if(!ast_strlen_zero(var)) {
+ rtcc_seqnum = atoi(var);
+ }
+
/* undo effect of S(x) in case they are both used */
calldurationlimit = 0;
/* more efficient to do it like S(x) does since no advanced opts */
- if (!play_warning && !start_sound && !end_sound && timelimit) {
+ if (!play_warning && !start_sound && !end_sound && !callcontrol_url_str && timelimit) {
calldurationlimit = timelimit / 1000;
if (option_verbose > 2)
ast_verbose(VERBOSE_PREFIX_3 "Setting call duration limit to %d seconds.\n", calldurationlimit);
@@ -957,6 +1211,11 @@
ast_verbose(VERBOSE_PREFIX_4 "play_to_caller = %s\n", play_to_caller ? "yes" : "no");
ast_verbose(VERBOSE_PREFIX_4 "play_to_callee = %s\n", play_to_callee ? "yes" : "no");
ast_verbose(VERBOSE_PREFIX_4 "warning_freq = %ld\n", warning_freq);
+ ast_verbose(VERBOSE_PREFIX_4 "rtcc_url = %s\n", callcontrol_url_str);
+ ast_verbose(VERBOSE_PREFIX_4 "rtcc_interval = %i\n", rtcc_interval);
+ ast_verbose(VERBOSE_PREFIX_4 "rtcc_initintvl = %i\n", rtcc_init_interval);
+ ast_verbose(VERBOSE_PREFIX_4 "rtcc_expintvl = %i\n", rtcc_expiry_interval);
+ ast_verbose(VERBOSE_PREFIX_4 "rtcc_seqnum = %i\n", rtcc_seqnum);
ast_verbose(VERBOSE_PREFIX_4 "start_sound = %s\n", start_sound);
ast_verbose(VERBOSE_PREFIX_4 "warning_sound = %s\n", warning_sound);
ast_verbose(VERBOSE_PREFIX_4 "end_sound = %s\n", end_sound);
@@ -1698,7 +1957,50 @@
ast_channel_setoption(chan,
AST_OPTION_OPRMODE,&oprmode,sizeof(struct oprmode),0);
}
+
+ /* If real-time call control requested initiate once answered. */
+ if(callcontrol_url_str)
+ {
+ if(!rtcc_sched_con) {
+ rtcc_sched_con = sched_context_create();
+ }
+
+ /* Add new rtcc job to schedule. */
+ int initial_interval = rtcc_init_interval;
+ if(rtcc_expiry_interval > 0) {
+ initial_interval = timelimit - rtcc_expiry_interval;
+ }
+
+ rtcc_data = ast_malloc(sizeof(*rtcc_data));
+ rtcc_data->config = &config;
+ rtcc_data->caller_accountcode = chan->accountcode;
+ rtcc_data->caller_exten = chan->exten;
+ rtcc_data->caller_channel_id = chan->uniqueid;
+ rtcc_data->callcontrol_url = callcontrol_url_str;
+ rtcc_data->hungup = 0;
+ rtcc_data->rtcc_check_interval = rtcc_interval;
+ rtcc_data->rtcc_expiry_interval = rtcc_expiry_interval;
+ rtcc_data->seqnum = rtcc_seqnum;
+ schedId = ast_sched_add_variable(rtcc_sched_con, initial_interval, &rtcccallback, rtcc_data, 1);
+
+ if(rtcc_thread == AST_PTHREADT_NULL) {
+ start_rtcc_thread();
+ }
+ }
+
res = ast_bridge_call(chan,peer,&config);
+
+ if(rtcc_data)
+ {
+ rtcc_data->hungup = 1;
+
+ /* Remove rtcc job from schedule. */
+ if(ast_sched_when(rtcc_sched_con, schedId) != -1) {
+ ast_sched_del(rtcc_sched_con, schedId);
+ ast_free(rtcc_data);
+ }
+ }
+
time(&end_time);
{
char toast[80];
--- asterisk-1.4.17/include/asterisk/channel.h 2007-12-03 18:12:17.000000000 -0500
+++ asterisk-1.4.17/include/asterisk/channel.h 2008-01-28 17:26:28.000000000 -0500
@@ -518,6 +518,7 @@
const char *start_sound;
int firstpass;
unsigned int flags;
+ struct timeval nexteventts;
};
struct chanmon;
diff -urN --exclude '=*.o' asterisk-1.4.17-orig/main/channel.c asterisk-1.4.17/main/channel.c
--- asterisk-1.4.17/main/channel.c 2007-12-27 16:40:02.000000000 -0500
+++ asterisk-1.4.17/main/channel.c 2008-01-28 17:32:26.000000000 -0500
@@ -4012,7 +4012,7 @@
static enum ast_bridge_result ast_generic_bridge(struct ast_channel *c0, struct ast_channel *c1,
struct ast_bridge_config *config, struct ast_frame **fo,
- struct ast_channel **rc, struct timeval bridge_end)
+ struct ast_channel **rc)
{
/* Copy voice back and forth between the two channels. */
struct ast_channel *cs[3];
@@ -4050,8 +4050,8 @@
res = AST_BRIDGE_RETRY;
break;
}
- if (bridge_end.tv_sec) {
- to = ast_tvdiff_ms(bridge_end, ast_tvnow());
+ if (config->nexteventts.tv_sec) {
+ to = ast_tvdiff_ms(config->nexteventts, ast_tvnow());
if (to <= 0) {
if (config->timelimit)
res = AST_BRIDGE_RETRY;
@@ -4166,7 +4166,6 @@
int o0nativeformats;
int o1nativeformats;
long time_left_ms=0;
- struct timeval nexteventts = { 0, };
char caller_warning = 0;
char callee_warning = 0;
@@ -4222,11 +4221,11 @@
o1nativeformats = c1->nativeformats;
if (config->feature_timer) {
- nexteventts = ast_tvadd(config->start_time, ast_samp2tv(config->feature_timer, 1000));
+ config->nexteventts = ast_tvadd(config->start_time, ast_samp2tv(config->feature_timer, 1000));
} else if (config->timelimit) {
- nexteventts = ast_tvadd(config->start_time, ast_samp2tv(config->timelimit, 1000));
+ config->nexteventts = ast_tvadd(config->start_time, ast_samp2tv(config->timelimit, 1000));
if (caller_warning || callee_warning)
- nexteventts = ast_tvsub(nexteventts, ast_samp2tv(config->play_warning, 1000));
+ config->nexteventts = ast_tvsub(config->nexteventts, ast_samp2tv(config->play_warning, 1000));
}
if (!c0->tech->send_digit_begin)
@@ -4240,9 +4239,9 @@
to = -1;
- if (!ast_tvzero(nexteventts)) {
+ if (!ast_tvzero(config->nexteventts)) {
now = ast_tvnow();
- to = ast_tvdiff_ms(nexteventts, now);
+ to = ast_tvdiff_ms(config->nexteventts, now);
if (to <= 0) {
if (!config->timelimit) {
res = AST_BRIDGE_COMPLETE;
@@ -4278,9 +4277,9 @@
bridge_playfile(c1, c0, config->warning_sound, t);
}
if (config->warning_freq && (time_left_ms > (config->warning_freq + 5000)))
- nexteventts = ast_tvadd(nexteventts, ast_samp2tv(config->warning_freq, 1000));
+ config->nexteventts = ast_tvadd(config->nexteventts, ast_samp2tv(config->warning_freq, 1000));
else
- nexteventts = ast_tvadd(config->start_time, ast_samp2tv(config->timelimit, 1000));
+ config->nexteventts = ast_tvadd(config->start_time, ast_samp2tv(config->timelimit, 1000));
}
}
@@ -4389,7 +4388,7 @@
o0nativeformats = c0->nativeformats;
o1nativeformats = c1->nativeformats;
}
- res = ast_generic_bridge(c0, c1, config, fo, rc, nexteventts);
+ res = ast_generic_bridge(c0, c1, config, fo, rc);
if (res != AST_BRIDGE_RETRY)
break;
}