Asterisk - The Open Source Telephony Project GIT-master-7e7a603
func_periodic_hook.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2014, Russell Bryant
5 *
6 * Russell Bryant <russell@russellbryant.net>
7 *
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
13 *
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
17 */
18
19/*! \file
20 *
21 * \brief Periodic dialplan hooks.
22 *
23 * \author Russell Bryant <russell@russellbryant.net>
24 *
25 * \ingroup functions
26 */
27
28/*** MODULEINFO
29 <depend>app_chanspy</depend>
30 <depend>func_cut</depend>
31 <depend>func_groupcount</depend>
32 <depend>func_uri</depend>
33 <support_level>core</support_level>
34 ***/
35
36#include "asterisk.h"
37
38#include "asterisk/module.h"
39#include "asterisk/channel.h"
40#include "asterisk/pbx.h"
41#include "asterisk/app.h"
42#include "asterisk/audiohook.h"
43#include "asterisk/test.h"
44#define AST_API_MODULE
45#include "asterisk/beep.h"
46
47/*** DOCUMENTATION
48 <function name="PERIODIC_HOOK" language="en_US">
49 <synopsis>
50 Execute a periodic dialplan hook into the audio of a call.
51 </synopsis>
52 <syntax>
53 <parameter name="context" required="true">
54 <para>(On Read Only) Context for the hook extension.</para>
55 </parameter>
56 <parameter name="extension" required="true">
57 <para>(On Read Only) The hook extension.</para>
58 </parameter>
59 <parameter name="interval" required="true">
60 <para>(On Read Only) Number of seconds in between hook runs.
61 Whole seconds only.</para>
62 </parameter>
63 <parameter name="hook_id" required="true">
64 <para>(On Write Only) The hook ID.</para>
65 </parameter>
66 </syntax>
67 <description>
68 <para>For example, you could use this function to enable playing
69 a periodic <literal>beep</literal> sound in a call.</para>
70 <para/>
71 <example title="To turn on">
72 same => n,Set(BEEPID=${PERIODIC_HOOK(hooks,beep,180)})
73 </example>
74 <example title="To turn off">
75 same => n,Set(PERIODIC_HOOK(${BEEPID})=off)
76 </example>
77 <example title="To turn back on again later">
78 same => n,Set(PERIODIC_HOOK(${BEEPID})=on)
79 </example>
80 <para>It is important to note that the hook does not actually
81 run on the channel itself. It runs asynchronously on a new channel.
82 Any audio generated by the hook gets injected into the call for
83 the channel PERIODIC_HOOK() was set on.</para>
84 <para/>
85 <para>The hook dialplan will have two variables available.
86 <variable>HOOK_CHANNEL</variable> is the channel the hook is
87 enabled on. <variable>HOOK_ID</variable> is the hook ID for
88 enabling or disabling the hook.</para>
89 </description>
90 </function>
91 ***/
92
93static const char context_name[] = "__func_periodic_hook_context__";
94static const char exten_name[] = "hook";
95static const char full_exten_name[] = "hook@__func_periodic_hook_context__";
96
97static const char beep_exten[] = "beep";
98
99/*!
100 * \brief Last used hook ID
101 *
102 * This is incremented each time a hook is created to give each hook a unique
103 * ID.
104 */
105static unsigned int global_hook_id;
106
107/*! State put in a datastore to track the state of the hook */
109 /*!
110 * \brief audiohook used as a callback into this module
111 *
112 * \note The code assumes this is the first element in the struct
113 */
115 /*! Seconds between each hook run */
116 unsigned int interval;
117 /*! The last time the hook ran */
118 struct timeval last_hook;
119 /*! Dialplan context for the hook */
120 char *context;
121 /*! Dialplan extension for the hook */
122 char *exten;
123 /*! Hook ID */
124 unsigned int hook_id;
125 /*! Non-zero if the hook is currently disabled */
126 unsigned char disabled;
127};
128
129static void hook_datastore_destroy_callback(void *data)
130{
131 struct hook_state *state = data;
132
133 ast_audiohook_lock(&state->audiohook);
134 ast_audiohook_detach(&state->audiohook);
135 ast_audiohook_unlock(&state->audiohook);
136 ast_audiohook_destroy(&state->audiohook);
137
138 ast_free(state->context);
139 ast_free(state->exten);
141}
142
143static const struct ast_datastore_info hook_datastore = {
144 .type = AST_MODULE,
146};
147
148/*! Arguments to the thread that launches the hook */
150 /*! Hook ID */
151 char *hook_id;
152 /*! Name of the channel the hook was set on */
154 /*! Dialplan context for the hook */
155 char *context;
156 /*! Dialplan extension for the hook */
157 char *exten;
158};
159
161{
162 ast_free(arg->hook_id);
163 ast_free(arg->chan_name);
164 ast_free(arg->context);
165 ast_free(arg->exten);
166 ast_free(arg);
167}
168
169static void *hook_launch_thread(void *data)
170{
171 struct hook_thread_arg *arg = data;
172 struct ast_variable hook_id = {
173 .name = "HOOK_ID",
174 .value = arg->hook_id,
175 };
176 struct ast_variable chan_name_var = {
177 .name = "HOOK_CHANNEL",
178 .value = arg->chan_name,
179 .next = &hook_id,
180 };
181
183 arg->context, arg->exten, 1, NULL, AST_OUTGOING_NO_WAIT,
184 NULL, NULL, &chan_name_var, NULL, NULL, 1, NULL);
185
187
188 return NULL;
189}
190
192 struct hook_state *state)
193{
194 struct hook_thread_arg *arg;
195
196 if (!(arg = ast_calloc(1, sizeof(*arg)))) {
197 return NULL;
198 }
199
200 ast_channel_lock(chan);
202 ast_channel_unlock(chan);
203 if (!arg->chan_name) {
205 return NULL;
206 }
207
208 if (ast_asprintf(&arg->hook_id, "%u", state->hook_id) == -1) {
210 return NULL;
211 }
212
213 if (!(arg->context = ast_strdup(state->context))) {
215 return NULL;
216 }
217
218 if (!(arg->exten = ast_strdup(state->exten))) {
220 return NULL;
221 }
222
223 return arg;
224}
225
226static int do_hook(struct ast_channel *chan, struct hook_state *state)
227{
228 pthread_t t;
229 struct hook_thread_arg *arg;
230 int res;
231
232 if (!(arg = hook_thread_arg_alloc(chan, state))) {
233 return -1;
234 }
235
236 /*
237 * We don't want to block normal frame processing *at all* while we kick
238 * this off, so do it in a new thread.
239 */
241 if (res != 0) {
243 }
244
245 return res;
246}
247
248static int hook_callback(struct ast_audiohook *audiohook, struct ast_channel *chan,
249 struct ast_frame *frame, enum ast_audiohook_direction direction)
250{
251 struct hook_state *state = (struct hook_state *) audiohook; /* trust me. */
252 struct timeval now;
253 int res = 0;
254
255 if (audiohook->status == AST_AUDIOHOOK_STATUS_DONE || state->disabled) {
256 return 0;
257 }
258
259 now = ast_tvnow();
260 if (ast_tvdiff_ms(now, state->last_hook) > state->interval * 1000) {
261 if ((res = do_hook(chan, state))) {
262 const char *name;
263 ast_channel_lock(chan);
265 ast_channel_unlock(chan);
266 ast_log(LOG_WARNING, "Failed to run hook on '%s'\n", name);
267 }
268 state->last_hook = now;
269 }
270
271 return res;
272}
273
274static struct hook_state *hook_state_alloc(const char *context, const char *exten,
275 unsigned int interval, unsigned int hook_id)
276{
277 struct hook_state *state;
278
279 if (!(state = ast_calloc(1, sizeof(*state)))) {
280 return NULL;
281 }
282
283 state->context = ast_strdup(context);
284 state->exten = ast_strdup(exten);
285 state->interval = interval;
286 state->hook_id = hook_id;
287
290 state->audiohook.manipulate_callback = hook_callback;
291
292 return state;
293}
294
295static int init_hook(struct ast_channel *chan, const char *context, const char *exten,
296 unsigned int interval, unsigned int hook_id)
297{
298 struct hook_state *state;
299 struct ast_datastore *datastore;
300 char uid[32];
301
302 snprintf(uid, sizeof(uid), "%u", hook_id);
303
304 if (!(datastore = ast_datastore_alloc(&hook_datastore, uid))) {
305 return -1;
306 }
307
308 if (!(state = hook_state_alloc(context, exten, interval, hook_id))) {
309 ast_datastore_free(datastore);
310 return -1;
311 }
312 datastore->data = state;
313
314 ast_channel_lock(chan);
315 ast_channel_datastore_add(chan, datastore);
316 ast_audiohook_attach(chan, &state->audiohook);
317 ast_channel_unlock(chan);
318
319 return 0;
320}
321
322static int hook_on(struct ast_channel *chan, const char *data, unsigned int hook_id)
323{
324 char *parse = ast_strdupa(S_OR(data, ""));
327 AST_APP_ARG(exten);
328 AST_APP_ARG(interval);
329 );
330 unsigned int interval;
331
333
334 if (ast_strlen_zero(args.interval) ||
335 sscanf(args.interval, "%30u", &interval) != 1 || interval == 0) {
336 ast_log(LOG_WARNING, "Invalid hook interval: '%s'\n", S_OR(args.interval, ""));
337 return -1;
338 }
339
340 if (ast_strlen_zero(args.context) || ast_strlen_zero(args.exten)) {
341 ast_log(LOG_WARNING, "A context and extension are required for PERIODIC_HOOK().\n");
342 return -1;
343 }
344
345 ast_debug(1, "hook to %s@%s enabled on %s with interval of %u seconds\n",
346 args.exten, args.context, ast_channel_name(chan), interval);
347 ast_test_suite_event_notify("PERIODIC_HOOK_ENABLED", "Exten: %s\r\nChannel: %s\r\nInterval: %u\r\n",
348 args.exten, ast_channel_name(chan), interval);
349
350 return init_hook(chan, args.context, args.exten, interval, hook_id);
351}
352
353static int hook_off(struct ast_channel *chan, const char *hook_id)
354{
355 struct ast_datastore *datastore;
356 struct hook_state *state;
357
359 return -1;
360 }
361
362 ast_channel_lock(chan);
363
364 if (!(datastore = ast_channel_datastore_find(chan, &hook_datastore, hook_id))) {
365 ast_log(LOG_WARNING, "Hook with ID '%s' not found on channel '%s'\n", hook_id,
366 ast_channel_name(chan));
367 ast_channel_unlock(chan);
368 return -1;
369 }
370
371 state = datastore->data;
372 state->disabled = 1;
373
374 ast_channel_unlock(chan);
375
376 return 0;
377}
378
379static int hook_read(struct ast_channel *chan, const char *cmd, char *data,
380 char *buf, size_t len)
381{
382 unsigned int hook_id;
383
384 if (!chan) {
385 return -1;
386 }
387
388 hook_id = (unsigned int) ast_atomic_fetchadd_int((int *) &global_hook_id, 1);
389
390 snprintf(buf, len, "%u", hook_id);
391
392 return hook_on(chan, data, hook_id);
393}
394
395static int hook_re_enable(struct ast_channel *chan, const char *uid)
396{
397 struct ast_datastore *datastore;
398 struct hook_state *state;
399
400 if (ast_strlen_zero(uid)) {
401 return -1;
402 }
403
404 ast_channel_lock(chan);
405
406 if (!(datastore = ast_channel_datastore_find(chan, &hook_datastore, uid))) {
407 ast_log(LOG_WARNING, "Hook with ID '%s' not found on '%s'\n",
408 uid, ast_channel_name(chan));
409 ast_channel_unlock(chan);
410 return -1;
411 }
412
413 state = datastore->data;
414 state->disabled = 0;
415
416 ast_channel_unlock(chan);
417
418 return 0;
419}
420
421static int hook_write(struct ast_channel *chan, const char *cmd, char *data,
422 const char *value)
423{
424 int res;
425
426 if (!chan) {
427 return -1;
428 }
429
430 if (ast_false(value)) {
431 res = hook_off(chan, data);
432 } else if (ast_true(value)) {
433 res = hook_re_enable(chan, data);
434 } else {
435 ast_log(LOG_WARNING, "Invalid value for PERIODIC_HOOK function: '%s'\n", value);
436 res = -1;
437 }
438
439 return res;
440}
441
443 .name = "PERIODIC_HOOK",
444 .read = hook_read,
445 .write = hook_write,
446};
447
448static int unload_module(void)
449{
451
453 return 0;
454}
455
456static int load_module(void)
457{
458 int res;
459
461 ast_log(LOG_ERROR, "Failed to create %s dialplan context.\n", context_name);
463 }
464
465 /*
466 * Based on a handy recipe from the Asterisk Cookbook.
467 */
468 res = ast_add_extension(context_name, 1, exten_name, 1, "", "",
469 "Set", "EncodedChannel=${HOOK_CHANNEL}",
471 res |= ast_add_extension(context_name, 1, exten_name, 2, "", "",
472 "Set", "GROUP_NAME=${EncodedChannel}${HOOK_ID}",
474 res |= ast_add_extension(context_name, 1, exten_name, 3, "", "",
475 "Set", "GROUP(periodic-hook)=${GROUP_NAME}",
477 res |= ast_add_extension(context_name, 1, exten_name, 4, "", "", "ExecIf",
478 "$[${GROUP_COUNT(${GROUP_NAME}@periodic-hook)} > 1]?Hangup()",
480 res |= ast_add_extension(context_name, 1, exten_name, 5, "", "",
481 "Set", "ChannelToSpy=${URIDECODE(${EncodedChannel})}",
483 res |= ast_add_extension(context_name, 1, exten_name, 6, "", "",
484 "ChanSpy", "${ChannelToSpy},qEB", NULL, AST_MODULE);
485
486 res |= ast_add_extension(context_name, 1, beep_exten, 1, "", "",
487 "Answer", "", NULL, AST_MODULE);
488 res |= ast_add_extension(context_name, 1, beep_exten, 2, "", "",
489 "Playback", "beep", NULL, AST_MODULE);
490 res |= ast_add_extension(context_name, 1, beep_exten, 3, "", "",
491 "Hangup", "", NULL, AST_MODULE);
492
494
495 if (res) {
498 }
500}
501
503 unsigned int interval, char *beep_id, size_t len)
504{
506
507 snprintf(args, sizeof(args), "%s,%s,%u",
508 context_name, beep_exten, interval);
509
510 if (hook_read(chan, NULL, args, beep_id, len)) {
511 ast_log(LOG_WARNING, "Failed to enable periodic beep.\n");
512 return -1;
513 }
514
515 return 0;
516}
517
518int AST_OPTIONAL_API_NAME(ast_beep_stop)(struct ast_channel *chan, const char *beep_id)
519{
520 return hook_write(chan, NULL, (char *) beep_id, "off");
521}
522
524 .support_level = AST_MODULE_SUPPORT_CORE,
525 .load = load_module,
526 .unload = unload_module,
527 .requires = "app_chanspy,func_cut,func_groupcount,func_uri",
#define AST_MODULE
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:267
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_log
Definition: astobj2.c:42
Audiohooks Architecture.
@ AST_AUDIOHOOK_MANIPULATE_ALL_RATES
Definition: audiohook.h:75
int ast_audiohook_init(struct ast_audiohook *audiohook, enum ast_audiohook_type type, const char *source, enum ast_audiohook_init_flags flags)
Initialize an audiohook structure.
Definition: audiohook.c:100
ast_audiohook_direction
Definition: audiohook.h:48
#define ast_audiohook_lock(ah)
Lock an audiohook.
Definition: audiohook.h:313
int ast_audiohook_detach(struct ast_audiohook *audiohook)
Detach audiohook from channel.
Definition: audiohook.c:550
int ast_audiohook_attach(struct ast_channel *chan, struct ast_audiohook *audiohook)
Attach audiohook to channel.
Definition: audiohook.c:484
int ast_audiohook_destroy(struct ast_audiohook *audiohook)
Destroys an audiohook structure.
Definition: audiohook.c:124
#define ast_audiohook_unlock(ah)
Unlock an audiohook.
Definition: audiohook.h:318
@ AST_AUDIOHOOK_TYPE_MANIPULATE
Definition: audiohook.h:38
@ AST_AUDIOHOOK_STATUS_DONE
Definition: audiohook.h:45
Periodic beeps into the audio of a call.
enum cc_state state
Definition: ccss.c:393
General Asterisk PBX channel definitions.
const char * ast_channel_name(const struct ast_channel *chan)
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
Definition: channel.c:2385
#define ast_channel_lock(chan)
Definition: channel.h:2922
#define AST_MAX_CONTEXT
Definition: channel.h:135
#define ast_channel_unlock(chan)
Definition: channel.h:2923
#define AST_MAX_EXTENSION
Definition: channel.h:134
struct ast_datastore * ast_channel_datastore_find(struct ast_channel *chan, const struct ast_datastore_info *info, const char *uid)
Find a datastore on a channel.
Definition: channel.c:2399
#define ast_datastore_alloc(info, uid)
Definition: datastore.h:85
int ast_datastore_free(struct ast_datastore *datastore)
Free a data store object.
Definition: datastore.c:68
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
static const char name[]
Definition: format_mp3.c:68
direction
static void * hook_launch_thread(void *data)
static void hook_datastore_destroy_callback(void *data)
static int hook_re_enable(struct ast_channel *chan, const char *uid)
static void hook_thread_arg_destroy(struct hook_thread_arg *arg)
static const char exten_name[]
static int hook_off(struct ast_channel *chan, const char *hook_id)
static struct hook_state * hook_state_alloc(const char *context, const char *exten, unsigned int interval, unsigned int hook_id)
static unsigned int global_hook_id
Last used hook ID.
static int hook_callback(struct ast_audiohook *audiohook, struct ast_channel *chan, struct ast_frame *frame, enum ast_audiohook_direction direction)
static int hook_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
static int hook_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
static struct ast_custom_function hook_function
int AST_OPTIONAL_API_NAME() ast_beep_start(struct ast_channel *chan, unsigned int interval, char *beep_id, size_t len)
static const char full_exten_name[]
static int do_hook(struct ast_channel *chan, struct hook_state *state)
static const char context_name[]
static int load_module(void)
static int hook_on(struct ast_channel *chan, const char *data, unsigned int hook_id)
static struct hook_thread_arg * hook_thread_arg_alloc(struct ast_channel *chan, struct hook_state *state)
static int unload_module(void)
int AST_OPTIONAL_API_NAME() ast_beep_stop(struct ast_channel *chan, const char *beep_id)
static const struct ast_datastore_info hook_datastore
static const char beep_exten[]
static int init_hook(struct ast_channel *chan, const char *context, const char *exten, unsigned int interval, unsigned int hook_id)
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
Application convenience functions, designed to give consistent look and feel to Asterisk apps.
#define AST_APP_ARG(name)
Define an application argument.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_WARNING
int ast_atomic_fetchadd_int(volatile int *p, int v)
Atomically add v to *p and return the previous value of *p.
Definition: lock.h:757
Asterisk module definitions.
@ AST_MODFLAG_GLOBAL_SYMBOLS
Definition: module.h:316
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:543
@ AST_MODULE_SUPPORT_CORE
Definition: module.h:121
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
@ AST_MODULE_LOAD_SUCCESS
Definition: module.h:70
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
#define AST_OPTIONAL_API_NAME(name)
Expands to the name of the implementation function.
Definition: optional_api.h:228
Core PBX routines and definitions.
int ast_add_extension(const char *context, int replace, const char *extension, int priority, const char *label, const char *callerid, const char *application, void *data, void(*datad)(void *), const char *registrar)
Add and extension to an extension context.
Definition: pbx.c:6928
@ AST_OUTGOING_NO_WAIT
Definition: pbx.h:1140
struct ast_context * ast_context_find_or_create(struct ast_context **extcontexts, struct ast_hashtab *exttable, const char *name, const char *registrar)
Register a new context or find an existing one.
Definition: pbx.c:6149
int ast_pbx_outgoing_exten(const char *type, struct ast_format_cap *cap, const char *addr, int timeout, const char *context, const char *exten, int priority, int *reason, int synchronous, const char *cid_num, const char *cid_name, struct ast_variable *vars, const char *account, struct ast_channel **locked_channel, int early_media, const struct ast_assigned_ids *assignedids)
Synchronously or asynchronously make an outbound call and send it to a particular extension.
Definition: pbx.c:7916
#define ast_custom_function_register_escalating(acf, escalation)
Register a custom function which requires escalated privileges.
Definition: pbx.h:1567
void ast_context_destroy(struct ast_context *con, const char *registrar)
Destroy a context (matches the specified context or ANY context if NULL)
Definition: pbx.c:8221
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
@ AST_CFE_BOTH
Definition: pbx.h:1552
#define NULL
Definition: resample.c:96
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one.
Definition: strings.h:80
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true"....
Definition: utils.c:2199
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
int attribute_pure ast_false(const char *val)
Make sure something is false. Determine if a string containing a boolean value is "false"....
Definition: utils.c:2216
enum ast_audiohook_status status
Definition: audiohook.h:108
Main Channel structure associated with a channel.
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
const char * name
Definition: pbx.h:119
Structure for a data store type.
Definition: datastore.h:31
const char * type
Definition: datastore.h:32
Structure for a data store object.
Definition: datastore.h:64
const char * uid
Definition: datastore.h:65
void * data
Definition: datastore.h:66
Data structure associated with a single frame of data.
Structure for variables, used for configurations and for channel variables.
unsigned int hook_id
unsigned char disabled
unsigned int interval
struct timeval last_hook
struct ast_audiohook audiohook
audiohook used as a callback into this module
int value
Definition: syslog.c:37
Test Framework API.
#define ast_test_suite_event_notify(s, f,...)
Definition: test.h:189
const char * args
int64_t ast_tvdiff_ms(struct timeval end, struct timeval start)
Computes the difference (in milliseconds) between two struct timeval instances.
Definition: time.h:107
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
#define ast_pthread_create_detached_background(a, b, c, d)
Definition: utils.h:597