Merge "stasis: Allow filtering by formatter"
[asterisk/asterisk.git] / funcs / func_holdintercept.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2015, Digium, Inc.
5  *
6  * Matt Jordan <mjordan@digium.com>
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 Function that intercepts HOLD frames from channels and raises events
22  *
23  * \author Matt Jordan <mjordan@digium.com>
24  *
25  * \ingroup functions
26  */
27
28 /*** MODULEINFO
29         <support_level>core</support_level>
30  ***/
31
32 #include "asterisk.h"
33
34 #include "asterisk/module.h"
35 #include "asterisk/channel.h"
36 #include "asterisk/pbx.h"
37 #include "asterisk/app.h"
38 #include "asterisk/frame.h"
39 #include "asterisk/stasis.h"
40 #include "asterisk/stasis_channels.h"
41
42 /*** DOCUMENTATION
43         <function name="HOLD_INTERCEPT" language="en_US">
44                 <synopsis>
45                         Intercepts hold frames on a channel and raises an event instead of passing the frame on
46                 </synopsis>
47                 <syntax>
48                         <parameter name="action" required="true">
49                                 <optionlist>
50                                         <option name="remove">
51                                                 <para>W/O. Removes the hold interception function.</para>
52                                         </option>
53                                         <option name="set">
54                                                 <para>W/O. Enable hold interception on the channel. When
55                                                 enabled, the channel will intercept any hold action that
56                                                 is signalled from the device, and instead simply raise an
57                                                 event (AMI/ARI) indicating that the channel wanted to put other
58                                                 parties on hold.</para>
59                                         </option>
60                                 </optionlist>
61                         </parameter>
62                 </syntax>
63         </function>
64 ***/
65
66 /*! \brief Private data structure used with the function's datastore */
67 struct hold_intercept_data {
68         int framehook_id;
69 };
70
71 /*! \brief The channel datastore the function uses to store state */
72 static const struct ast_datastore_info hold_intercept_datastore = {
73         .type = "hold_intercept",
74 };
75
76 /*! \internal \brief Disable hold interception on the channel */
77 static int remove_hold_intercept(struct ast_channel *chan)
78 {
79         struct ast_datastore *datastore = NULL;
80         struct hold_intercept_data *data;
81         SCOPED_CHANNELLOCK(chan_lock, chan);
82
83         datastore = ast_channel_datastore_find(chan, &hold_intercept_datastore, NULL);
84         if (!datastore) {
85                 ast_log(AST_LOG_WARNING, "Cannot remove HOLD_INTERCEPT from %s: HOLD_INTERCEPT not currently enabled\n",
86                         ast_channel_name(chan));
87                 return -1;
88         }
89         data = datastore->data;
90
91         if (ast_framehook_detach(chan, data->framehook_id)) {
92                 ast_log(AST_LOG_WARNING, "Failed to remove HOLD_INTERCEPT framehook from channel %s\n",
93                         ast_channel_name(chan));
94                 return -1;
95         }
96
97         if (ast_channel_datastore_remove(chan, datastore)) {
98                 ast_log(AST_LOG_WARNING, "Failed to remove HOLD_INTERCEPT datastore from channel %s\n",
99                         ast_channel_name(chan));
100                 return -1;
101         }
102         ast_datastore_free(datastore);
103
104         return 0;
105 }
106
107 /*! \brief Frame hook that is called to intercept hold/unhold */
108 static struct ast_frame *hold_intercept_framehook(struct ast_channel *chan,
109         struct ast_frame *f, enum ast_framehook_event event, void *data)
110 {
111         int frame_type;
112
113         if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) {
114                 return f;
115         }
116
117         if (f->frametype != AST_FRAME_CONTROL) {
118                 return f;
119         }
120
121         frame_type = f->subclass.integer;
122         if (frame_type != AST_CONTROL_HOLD && frame_type != AST_CONTROL_UNHOLD) {
123                 return f;
124         }
125
126         /* Munch munch */
127         ast_frfree(f);
128         f = &ast_null_frame;
129
130         ast_channel_publish_cached_blob(chan,
131                 frame_type == AST_CONTROL_HOLD ? ast_channel_hold_type() : ast_channel_unhold_type(),
132                 NULL);
133
134         return f;
135 }
136
137 /*! \brief Callback function which informs upstream if we are consuming a frame of a specific type */
138 static int hold_intercept_framehook_consume(void *data, enum ast_frame_type type)
139 {
140         return (type == AST_FRAME_CONTROL ? 1 : 0);
141 }
142
143 /*! \internal \brief Enable hold interception on the channel */
144 static int set_hold_intercept(struct ast_channel *chan)
145 {
146         struct ast_datastore *datastore;
147         struct hold_intercept_data *data;
148         static struct ast_framehook_interface hold_framehook_interface = {
149                 .version = AST_FRAMEHOOK_INTERFACE_VERSION,
150                 .event_cb = hold_intercept_framehook,
151                 .consume_cb = hold_intercept_framehook_consume,
152                 .disable_inheritance = 1,
153         };
154         SCOPED_CHANNELLOCK(chan_lock, chan);
155
156         datastore = ast_channel_datastore_find(chan, &hold_intercept_datastore, NULL);
157         if (datastore) {
158                 ast_log(AST_LOG_WARNING, "HOLD_INTERCEPT already set on '%s'\n",
159                         ast_channel_name(chan));
160                 return 0;
161         }
162
163         datastore = ast_datastore_alloc(&hold_intercept_datastore, NULL);
164         if (!datastore) {
165                 return -1;
166         }
167
168         data = ast_calloc(1, sizeof(*data));
169         if (!data) {
170                 ast_datastore_free(datastore);
171                 return -1;
172         }
173
174         data->framehook_id = ast_framehook_attach(chan, &hold_framehook_interface);
175         if (data->framehook_id < 0) {
176                 ast_log(AST_LOG_WARNING, "Failed to attach HOLD_INTERCEPT framehook to '%s'\n",
177                         ast_channel_name(chan));
178                 ast_datastore_free(datastore);
179                 ast_free(data);
180                 return -1;
181         }
182         datastore->data = data;
183
184         ast_channel_datastore_add(chan, datastore);
185
186         return 0;
187 }
188
189 /*! \internal \brief HOLD_INTERCEPT write function callback */
190 static int hold_intercept_fn_write(struct ast_channel *chan, const char *function,
191         char *data, const char *value)
192 {
193         int res;
194
195         if (!chan) {
196                 return -1;
197         }
198
199         if (ast_strlen_zero(data)) {
200                 ast_log(AST_LOG_WARNING, "HOLD_INTERCEPT requires an argument\n");
201                 return -1;
202         }
203
204         if (!strcasecmp(data, "set")) {
205                 res = set_hold_intercept(chan);
206         } else if (!strcasecmp(data, "remove")) {
207                 res = remove_hold_intercept(chan);
208         } else {
209                 ast_log(AST_LOG_WARNING, "HOLD_INTERCEPT: unknown option %s\n", data);
210                 res = -1;
211         }
212
213         return res;
214 }
215
216 /*! \brief Definition of the HOLD_INTERCEPT function */
217 static struct ast_custom_function hold_intercept_function = {
218         .name = "HOLD_INTERCEPT",
219         .write = hold_intercept_fn_write,
220 };
221
222 /*! \internal \brief Unload the module */
223 static int unload_module(void)
224 {
225         return ast_custom_function_unregister(&hold_intercept_function);
226 }
227
228 /*! \internal \brief Load the module */
229 static int load_module(void)
230 {
231         return ast_custom_function_register(&hold_intercept_function) ? AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS;
232 }
233
234 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Hold interception dialplan function");