[Asterisk-code-review] app_dtmfstore: New application to store digits (asterisk[19])

Friendly Automation asteriskteam at digium.com
Mon Aug 2 14:29:50 CDT 2021


Friendly Automation has submitted this change. ( https://gerrit.asterisk.org/c/asterisk/+/16250 )

Change subject: app_dtmfstore: New application to store digits
......................................................................

app_dtmfstore: New application to store digits

Adds application to asynchronously collect digits
dialed on a channel in the TX or RX direction
using a framehook and stores them in a specified
variable, up to a configurable number of digits.

ASTERISK-29477

Change-Id: I51aa93fc9507f7636ac44806c4420ce690423e6f
---
A apps/app_dtmfstore.c
A doc/CHANGES-staging/app_dtmfstore.txt
2 files changed, 292 insertions(+), 0 deletions(-)

Approvals:
  George Joseph: Looks good to me, approved
  Friendly Automation: Approved for Submit



diff --git a/apps/app_dtmfstore.c b/apps/app_dtmfstore.c
new file mode 100644
index 0000000..4e97334
--- /dev/null
+++ b/apps/app_dtmfstore.c
@@ -0,0 +1,286 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Naveen Albert
+ *
+ * Naveen Albert <asterisk at phreaknet.org>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Technology independent asynchronous DTMF collection
+ *
+ * \author Naveen Albert <asterisk at phreaknet.org>
+ *
+ * \ingroup functions
+ *
+ */
+
+/*** MODULEINFO
+	<support_level>extended</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/framehook.h"
+#include "asterisk/app.h"
+#include "asterisk/conversions.h"
+
+/*** DOCUMENTATION
+	<application name="StoreDTMF" language="en_US">
+		<synopsis>
+			Stores DTMF digits transmitted or received on a channel.
+		</synopsis>
+		<syntax>
+			<parameter name="direction" required="true">
+				<para>Must be <literal>TX</literal> or <literal>RX</literal>.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>The StoreDTMF function can be used to obtain digits sent in the
+			<literal>TX</literal> or <literal>RX</literal> direction of any channel.</para>
+			<para>The arguments are:</para>
+			<para><replaceable>var_name</replaceable>: Name of variable to which to append
+			digits.</para>
+			<para><replaceable>max_digits</replaceable>: The maximum number of digits to
+			store in the variable. Defaults to 0 (no maximum). After reading <literal>
+			maximum</literal> digits, no more digits will be stored.</para>
+			<para>For example:</para>
+			<para>StoreDTMF(TX,CDR(digits))</para>
+			<para>StoreDTMF(RX,testvar,24)</para>
+			<para>StoreDTMF(remove)</para>
+		</description>
+	</application>
+ ***/
+
+static char *app = "StoreDTMF";
+
+/*! \brief Private data structure used with the function's datastore */
+struct dtmf_store_data {
+	int framehook_id;
+	char *rx_var;
+	char *tx_var;
+	int maxdigits;
+};
+
+static void datastore_destroy_cb(void *data) {
+	struct dtmf_store_data *d;
+	d = data;
+	if (d) {
+		if (d->rx_var) {
+			ast_free(d->rx_var);
+		}
+		if (d->tx_var) {
+			ast_free(d->tx_var);
+		}
+		ast_free(data);
+	}
+}
+
+/*! \brief The channel datastore the function uses to store state */
+static const struct ast_datastore_info dtmf_store_datastore = {
+	.type = "dtmf_store",
+	.destroy = datastore_destroy_cb
+};
+
+/*! \internal \brief Store digits tx/rx on the channel */
+static int remove_dtmf_store(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore = NULL;
+	struct dtmf_store_data *data;
+	SCOPED_CHANNELLOCK(chan_lock, chan);
+
+	datastore = ast_channel_datastore_find(chan, &dtmf_store_datastore, NULL);
+	if (!datastore) {
+		ast_log(AST_LOG_WARNING, "Cannot remove StoreDTMF from %s: StoreDTMF not currently enabled\n",
+		        ast_channel_name(chan));
+		return -1;
+	}
+	data = datastore->data;
+
+	if (ast_framehook_detach(chan, data->framehook_id)) {
+		ast_log(AST_LOG_WARNING, "Failed to remove StoreDTMF framehook from channel %s\n",
+		        ast_channel_name(chan));
+		return -1;
+	}
+
+	if (ast_channel_datastore_remove(chan, datastore)) {
+		ast_log(AST_LOG_WARNING, "Failed to remove StoreDTMF datastore from channel %s\n",
+		        ast_channel_name(chan));
+		return -1;
+	}
+	ast_datastore_free(datastore);
+
+	return 0;
+}
+
+/*! \brief Frame hook that is called to intercept digit/undigit */
+static struct ast_frame *dtmf_store_framehook(struct ast_channel *chan,
+	struct ast_frame *f, enum ast_framehook_event event, void *data)
+{
+	char currentdata[512];
+	char varnamesub[64];
+	char *varname = NULL;
+	struct dtmf_store_data *framedata = data;
+	int len;
+
+	if (!f || !framedata) {
+		return f;
+	}
+
+	if ((event != AST_FRAMEHOOK_EVENT_WRITE) && (event != AST_FRAMEHOOK_EVENT_READ)) {
+		return f;
+	}
+
+	if (f->frametype != AST_FRAME_DTMF_END) {
+		return f;
+	}
+
+	/* If this is DTMF then store the digits */
+	if (event == AST_FRAMEHOOK_EVENT_READ && framedata->rx_var) { /* coming from source */
+		varname = framedata->rx_var;
+	} else if (event == AST_FRAMEHOOK_EVENT_WRITE && framedata->tx_var) { /* going to source */
+		varname = framedata->tx_var;
+	}
+
+	if (!varname) {
+		return f;
+	}
+
+	sprintf(varnamesub, "${%s}", varname);
+	pbx_substitute_variables_helper(chan, varnamesub, currentdata, 511);
+	/* pbx_builtin_getvar_helper works for regular vars but not CDR vars */
+	if (ast_strlen_zero(currentdata)) { /* var doesn't exist yet */
+		ast_debug(3, "Creating new digit store: %s\n", varname);
+	}
+	len = strlen(currentdata);
+	if (framedata->maxdigits > 0 && len >= framedata->maxdigits) {
+		ast_debug(3, "Reached digit limit: %d\n", framedata->maxdigits);
+		remove_dtmf_store(chan); /* reached max digit count, stop now */
+		return f;
+	} else {
+		char newdata[len + 2]; /* one more char + terminator */
+		if (len > 0) {
+			ast_copy_string(newdata, currentdata, len + 2);
+		}
+		newdata[len] = (unsigned) f->subclass.integer;
+		newdata[len + 1] = '\0';
+		ast_debug(3, "Appending to digit store: now %s\n", newdata);
+		pbx_builtin_setvar_helper(chan, varname, newdata);
+	}
+	return f;
+}
+
+/*! \internal \brief Enable digit interception on the channel */
+static int dtmfstore_exec(struct ast_channel *chan, const char *appdata)
+{
+	struct ast_datastore *datastore;
+	struct dtmf_store_data *data;
+	static struct ast_framehook_interface digit_framehook_interface = {
+		.version = AST_FRAMEHOOK_INTERFACE_VERSION,
+		.event_cb = dtmf_store_framehook,
+		.disable_inheritance = 1,
+	};
+	char *parse = ast_strdupa(appdata);
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(direction);
+		AST_APP_ARG(varname);
+		AST_APP_ARG(maxdigits);
+	);
+	SCOPED_CHANNELLOCK(chan_lock, chan);
+	AST_STANDARD_APP_ARGS(args, parse);
+
+	if (ast_strlen_zero(appdata)) {
+		ast_log(AST_LOG_WARNING, "StoreDTMF requires an argument\n");
+		return -1;
+	}
+
+	if (!strcasecmp(args.direction, "remove")) {
+		return remove_dtmf_store(chan);
+	}
+
+	datastore = ast_channel_datastore_find(chan, &dtmf_store_datastore, NULL);
+	if (datastore) {
+		ast_log(AST_LOG_WARNING, "StoreDTMF already set on '%s'\n",
+		        ast_channel_name(chan));
+		return 0;
+	}
+
+	datastore = ast_datastore_alloc(&dtmf_store_datastore, NULL);
+	if (!datastore) {
+		return -1;
+	}
+
+	data = ast_calloc(1, sizeof(*data));
+	if (!data) {
+		ast_datastore_free(datastore);
+		return -1;
+	}
+
+	digit_framehook_interface.data = data;
+
+	data->rx_var = NULL;
+	data->tx_var = NULL;
+	data->maxdigits = 0;
+
+	if (!strcasecmp(args.direction, "tx")) {
+		data->tx_var = ast_strdup(args.varname);
+	} else if (!strcasecmp(args.direction, "rx")) {
+		data->rx_var = ast_strdup(args.varname);
+	} else {
+		ast_log(LOG_ERROR, "Direction must be either RX or TX\n");
+		return -1;
+	}
+
+	if (!ast_strlen_zero(args.maxdigits)) {
+		if (ast_str_to_int(args.maxdigits,&(data->maxdigits))) {
+			ast_log(LOG_ERROR, "Invalid integer: %s\n", args.maxdigits);
+			return -1;
+		}
+		if (data->maxdigits < 0) {
+			ast_log(LOG_ERROR, "Invalid natural number: %d\n", data->maxdigits);
+			return -1;
+		} else if (data->maxdigits == 0) {
+			ast_log(LOG_WARNING, "No maximum digit count set\n");
+		}
+	}
+
+	data->framehook_id = ast_framehook_attach(chan, &digit_framehook_interface);
+	if (data->framehook_id < 0) {
+		ast_log(AST_LOG_WARNING, "Failed to attach StoreDTMF framehook to '%s'\n",
+		        ast_channel_name(chan));
+		ast_datastore_free(datastore);
+		ast_free(data);
+		return -1;
+	}
+	datastore->data = data;
+
+	ast_channel_datastore_add(chan, datastore);
+
+	return 0;
+}
+
+static int unload_module(void)
+{
+	return ast_unregister_application(app);
+}
+
+static int load_module(void)
+{
+	return ast_register_application_xml(app, dtmfstore_exec);
+}
+
+AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Technology independent async DTMF storage");
diff --git a/doc/CHANGES-staging/app_dtmfstore.txt b/doc/CHANGES-staging/app_dtmfstore.txt
new file mode 100644
index 0000000..a82b543
--- /dev/null
+++ b/doc/CHANGES-staging/app_dtmfstore.txt
@@ -0,0 +1,6 @@
+Subject: app_dtmfstore
+
+New application which collects digits
+dialed and stores them into
+a specified variable.
+

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/16250
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: 19
Gerrit-Change-Id: I51aa93fc9507f7636ac44806c4420ce690423e6f
Gerrit-Change-Number: 16250
Gerrit-PatchSet: 1
Gerrit-Owner: N A <mail at interlinked.x10host.com>
Gerrit-Reviewer: Friendly Automation
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20210802/69c326bc/attachment-0001.html>


More information about the asterisk-code-review mailing list