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