--- 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; }