Asterisk - The Open Source Telephony Project  21.4.1
app_dtmfstore.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2021, Naveen Albert
5  *
6  * Naveen Albert <asterisk@phreaknet.org>
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 Technology independent asynchronous DTMF collection
22  *
23  * \author Naveen Albert <asterisk@phreaknet.org>
24  *
25  * \ingroup functions
26  *
27  */
28 
29 /*** MODULEINFO
30  <support_level>extended</support_level>
31  ***/
32 
33 #include "asterisk.h"
34 
35 #include "asterisk/module.h"
36 #include "asterisk/channel.h"
37 #include "asterisk/pbx.h"
38 #include "asterisk/framehook.h"
39 #include "asterisk/app.h"
40 #include "asterisk/conversions.h"
41 
42 /*** DOCUMENTATION
43  <application name="StoreDTMF" language="en_US">
44  <since>
45  <version>16.20.0</version>
46  <version>18.6.0</version>
47  <version>19.0.0</version>
48  </since>
49  <synopsis>
50  Stores DTMF digits transmitted or received on a channel.
51  </synopsis>
52  <syntax>
53  <parameter name="direction" required="true">
54  <para>Must be <literal>TX</literal> or <literal>RX</literal> to
55  store digits, or <literal>remove</literal> to disable.</para>
56  </parameter>
57  </syntax>
58  <description>
59  <para>The StoreDTMF function can be used to obtain digits sent in the
60  <literal>TX</literal> or <literal>RX</literal> direction of any channel.</para>
61  <para>The arguments are:</para>
62  <para><replaceable>var_name</replaceable>: Name of variable to which to append
63  digits.</para>
64  <para><replaceable>max_digits</replaceable>: The maximum number of digits to
65  store in the variable. Defaults to 0 (no maximum). After reading <literal>
66  maximum</literal> digits, no more digits will be stored.</para>
67  <example title="Store digits in CDR variable">
68  same => n,StoreDTMF(TX,CDR(digits))
69  </example>
70  <example title="Store up to 24 digits">
71  same => n,StoreDTMF(RX,testvar,24)
72  </example>
73  <example title="Disable digit collection">
74  same => n,StoreDTMF(remove)
75  </example>
76  </description>
77  </application>
78  ***/
79 
80 static char *app = "StoreDTMF";
81 
82 /*! \brief Private data structure used with the function's datastore */
84  int framehook_id;
85  char *rx_var;
86  char *tx_var;
87  int maxdigits;
88 };
89 
90 static void datastore_destroy_cb(void *data) {
91  struct dtmf_store_data *d;
92  d = data;
93  if (d) {
94  if (d->rx_var) {
95  ast_free(d->rx_var);
96  }
97  if (d->tx_var) {
98  ast_free(d->tx_var);
99  }
100  ast_free(data);
101  }
102 }
103 
104 /*! \brief The channel datastore the function uses to store state */
106  .type = "dtmf_store",
107  .destroy = datastore_destroy_cb
108 };
109 
110 /*! \internal \brief Store digits tx/rx on the channel */
111 static int remove_dtmf_store(struct ast_channel *chan)
112 {
113  struct ast_datastore *datastore = NULL;
114  struct dtmf_store_data *data;
115  SCOPED_CHANNELLOCK(chan_lock, chan);
116 
117  datastore = ast_channel_datastore_find(chan, &dtmf_store_datastore, NULL);
118  if (!datastore) {
119  ast_log(AST_LOG_WARNING, "Cannot remove StoreDTMF from %s: StoreDTMF not currently enabled\n",
120  ast_channel_name(chan));
121  return -1;
122  }
123  data = datastore->data;
124 
125  if (ast_framehook_detach(chan, data->framehook_id)) {
126  ast_log(AST_LOG_WARNING, "Failed to remove StoreDTMF framehook from channel %s\n",
127  ast_channel_name(chan));
128  return -1;
129  }
130 
131  if (ast_channel_datastore_remove(chan, datastore)) {
132  ast_log(AST_LOG_WARNING, "Failed to remove StoreDTMF datastore from channel %s\n",
133  ast_channel_name(chan));
134  return -1;
135  }
136  ast_datastore_free(datastore);
137 
138  return 0;
139 }
140 
141 /*! \brief Frame hook that is called to intercept digit/undigit */
142 static struct ast_frame *dtmf_store_framehook(struct ast_channel *chan,
143  struct ast_frame *f, enum ast_framehook_event event, void *data)
144 {
145  char currentdata[512];
146  char varnamesub[64];
147  char *varname = NULL;
148  struct dtmf_store_data *framedata = data;
149  int len;
150 
151  if (!f || !framedata) {
152  return f;
153  }
154 
155  if ((event != AST_FRAMEHOOK_EVENT_WRITE) && (event != AST_FRAMEHOOK_EVENT_READ)) {
156  return f;
157  }
158 
159  if (f->frametype != AST_FRAME_DTMF_END) {
160  return f;
161  }
162 
163  /* If this is DTMF then store the digits */
164  if (event == AST_FRAMEHOOK_EVENT_READ && framedata->rx_var) { /* coming from source */
165  varname = framedata->rx_var;
166  } else if (event == AST_FRAMEHOOK_EVENT_WRITE && framedata->tx_var) { /* going to source */
167  varname = framedata->tx_var;
168  }
169 
170  if (!varname) {
171  return f;
172  }
173 
174  sprintf(varnamesub, "${%s}", varname);
175  pbx_substitute_variables_helper(chan, varnamesub, currentdata, 511);
176  /* pbx_builtin_getvar_helper works for regular vars but not CDR vars */
177  if (ast_strlen_zero(currentdata)) { /* var doesn't exist yet */
178  ast_debug(3, "Creating new digit store: %s\n", varname);
179  }
180  len = strlen(currentdata);
181  if (framedata->maxdigits > 0 && len >= framedata->maxdigits) {
182  ast_debug(3, "Reached digit limit: %d\n", framedata->maxdigits);
183  remove_dtmf_store(chan); /* reached max digit count, stop now */
184  return f;
185  } else {
186  char newdata[len + 2]; /* one more char + terminator */
187  if (len > 0) {
188  ast_copy_string(newdata, currentdata, len + 2);
189  }
190  newdata[len] = (unsigned) f->subclass.integer;
191  newdata[len + 1] = '\0';
192  ast_debug(3, "Appending to digit store: now %s\n", newdata);
193  pbx_builtin_setvar_helper(chan, varname, newdata);
194  }
195  return f;
196 }
197 
198 /*! \internal \brief Enable digit interception on the channel */
199 static int dtmfstore_exec(struct ast_channel *chan, const char *appdata)
200 {
201  struct ast_datastore *datastore;
202  struct dtmf_store_data *data;
203  static struct ast_framehook_interface digit_framehook_interface = {
204  .version = AST_FRAMEHOOK_INTERFACE_VERSION,
205  .event_cb = dtmf_store_framehook,
206  .disable_inheritance = 1,
207  };
208  char *parse = ast_strdupa(appdata);
210  AST_APP_ARG(direction);
211  AST_APP_ARG(varname);
212  AST_APP_ARG(maxdigits);
213  );
214  SCOPED_CHANNELLOCK(chan_lock, chan);
215  AST_STANDARD_APP_ARGS(args, parse);
216 
217  if (ast_strlen_zero(appdata)) {
218  ast_log(AST_LOG_WARNING, "StoreDTMF requires an argument\n");
219  return -1;
220  }
221 
222  if (!strcasecmp(args.direction, "remove")) {
223  return remove_dtmf_store(chan);
224  }
225 
226  datastore = ast_channel_datastore_find(chan, &dtmf_store_datastore, NULL);
227  if (datastore) {
228  ast_log(AST_LOG_WARNING, "StoreDTMF already set on '%s'\n",
229  ast_channel_name(chan));
230  return 0;
231  }
232 
233  datastore = ast_datastore_alloc(&dtmf_store_datastore, NULL);
234  if (!datastore) {
235  return -1;
236  }
237 
238  data = ast_calloc(1, sizeof(*data));
239  if (!data) {
240  ast_datastore_free(datastore);
241  return -1;
242  }
243 
244  digit_framehook_interface.data = data;
245 
246  data->rx_var = NULL;
247  data->tx_var = NULL;
248  data->maxdigits = 0;
249 
250  if (!strcasecmp(args.direction, "tx")) {
251  data->tx_var = ast_strdup(args.varname);
252  } else if (!strcasecmp(args.direction, "rx")) {
253  data->rx_var = ast_strdup(args.varname);
254  } else {
255  ast_log(LOG_ERROR, "Direction must be either RX or TX\n");
256  return -1;
257  }
258 
259  if (!ast_strlen_zero(args.maxdigits)) {
260  if (ast_str_to_int(args.maxdigits,&(data->maxdigits))) {
261  ast_log(LOG_ERROR, "Invalid integer: %s\n", args.maxdigits);
262  return -1;
263  }
264  if (data->maxdigits < 0) {
265  ast_log(LOG_ERROR, "Invalid natural number: %d\n", data->maxdigits);
266  return -1;
267  } else if (data->maxdigits == 0) {
268  ast_log(LOG_WARNING, "No maximum digit count set\n");
269  }
270  }
271 
272  data->framehook_id = ast_framehook_attach(chan, &digit_framehook_interface);
273  if (data->framehook_id < 0) {
274  ast_log(AST_LOG_WARNING, "Failed to attach StoreDTMF framehook to '%s'\n",
275  ast_channel_name(chan));
276  ast_datastore_free(datastore);
277  ast_free(data);
278  return -1;
279  }
280  datastore->data = data;
281 
282  ast_channel_datastore_add(chan, datastore);
283 
284  return 0;
285 }
286 
287 static int unload_module(void)
288 {
289  return ast_unregister_application(app);
290 }
291 
292 static int load_module(void)
293 {
294  return ast_register_application_xml(app, dtmfstore_exec);
295 }
296 
297 AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Technology independent async DTMF storage");
const char * type
Definition: datastore.h:32
Main Channel structure associated with a channel.
static struct ast_frame * dtmf_store_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data)
Frame hook that is called to intercept digit/undigit.
Asterisk main include file. File version handling, generic pbx functions.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
ast_framehook_event
These are the types of events that the framehook's event callback can receive.
Definition: framehook.h:151
int ast_framehook_detach(struct ast_channel *chan, int framehook_id)
Detach an framehook from a channel.
Definition: framehook.c:177
Structure for a data store type.
Definition: datastore.h:31
Definition: astman.c:222
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
Structure for a data store object.
Definition: datastore.h:64
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
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx_app.c:392
int ast_datastore_free(struct ast_datastore *datastore)
Free a data store object.
Definition: datastore.c:68
struct ast_frame_subclass subclass
int ast_framehook_attach(struct ast_channel *chan, struct ast_framehook_interface *i)
Attach an framehook onto a channel for frame interception.
Definition: framehook.c:132
static const struct ast_datastore_info dtmf_store_datastore
The channel datastore the function uses to store state.
#define SCOPED_CHANNELLOCK(varname, chan)
scoped lock specialization for channels.
Definition: lock.h:619
General Asterisk PBX channel definitions.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
Conversion utility functions.
#define ast_debug(level,...)
Log a DEBUG message.
Core PBX routines and definitions.
int ast_str_to_int(const char *str, int *res)
Convert the given string to a signed integer.
Definition: conversions.c:44
Private data structure used with the function's datastore.
Definition: app_dtmfstore.c:83
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
Add a variable to the channel variable stack, removing the most recently set value for the same name...
FrameHook Architecture.
void * data
Definition: datastore.h:66
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
Data structure associated with a single frame of data.
enum ast_frame_type frametype
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
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_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
int ast_channel_datastore_remove(struct ast_channel *chan, struct ast_datastore *datastore)
Remove a datastore from a channel.
Definition: channel.c:2394
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition: module.h:640
#define AST_APP_ARG(name)
Define an application argument.