summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorDavid M. Lee <dlee@digium.com>2013-03-22 14:06:46 +0000
committerDavid M. Lee <dlee@digium.com>2013-03-22 14:06:46 +0000
commitcf9324b25eb8a7dc5cef77f54e12758dfbcd6645 (patch)
tree67492a4753d03498d3b842939d1c4c9256efa013 /main
parent401f7c188038a327ed40a7bff30a28f03b69f8a1 (diff)
Move more channel events to Stasis; move res_json.c to main/json.c.
This patch started out simply as fixing the bouncing tests introduced in r382685, but required some other changes to give it a decent implementation. To fix the bouncing tests, the UserEvent and Newexten AMI events needed to be refactored to dispatch via Stasis. Dispatching directly to AMI resulted in those events sometimes getting ahead of the associated Newchannel events, which would understandably confuse anyone. I found that instead of creating a zillion different message types and structures associated with them, it would be preferable to define a message type that has a channel snapshot and a blob of structured data with a small bit of additional information. The JSON object model provides a very nice way of representing structured data, so I went with that. * Move JSON support from res_json.c to main/json.c * Made libjansson-dev a required dependency * Added an ast_channel_blob message type, which has a channel snapshot and JSON blob of data. * Changed UserEvent and Newexten events so that they are dispatched via ast_channel_blob messages on the channel's topic. * Got rid of the ast_channel_varset message; used ast_channel_blob instead. * Extracted the manager functions converting Stasis channel events to AMI events into manager_channel.c. (issue ASTERISK-21096) Review: https://reviewboard.asterisk.org/r/2381/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@383579 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'main')
-rw-r--r--main/channel.c86
-rw-r--r--main/json.c520
-rw-r--r--main/manager.c198
-rw-r--r--main/manager_channels.c409
-rw-r--r--main/pbx.c37
5 files changed, 1009 insertions, 241 deletions
diff --git a/main/channel.c b/main/channel.c
index 3f8319b34..5318e6667 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -155,7 +155,7 @@ static struct ao2_container *channels;
/*! \brief Message type for channel snapshot events */
static struct stasis_message_type *__channel_snapshot;
-static struct stasis_message_type *__channel_varset;
+static struct stasis_message_type *__channel_blob;
struct stasis_topic *__channel_topic_all;
@@ -243,37 +243,79 @@ static void publish_channel_state(struct ast_channel *chan)
stasis_publish(ast_channel_topic(chan), message);
}
-static void channel_varset_dtor(void *obj)
+static void channel_blob_dtor(void *obj)
{
- struct ast_channel_varset *event = obj;
+ struct ast_channel_blob *event = obj;
ao2_cleanup(event->snapshot);
- ast_free(event->variable);
- ast_free(event->value);
+ ast_json_unref(event->blob);
}
-void ast_channel_publish_varset(struct ast_channel *chan, const char *name, const char *value)
+struct stasis_message *ast_channel_blob_create(struct ast_channel *chan,
+ struct ast_json *blob)
{
- RAII_VAR(struct ast_channel_varset *, event, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_channel_blob *, obj, NULL, ao2_cleanup);
RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+ struct ast_json *type;
- event = ao2_alloc(sizeof(*event), channel_varset_dtor);
- if (!event) {
- return;
+ ast_assert(blob != NULL);
+
+ type = ast_json_object_get(blob, "type");
+ if (type == NULL) {
+ ast_log(LOG_ERROR, "Invalid ast_channel_blob; missing type field");
+ return NULL;
+ }
+
+ obj = ao2_alloc(sizeof(*obj), channel_blob_dtor);
+ if (!obj) {
+ return NULL;
}
if (chan) {
- event->snapshot = ast_channel_snapshot_create(chan);
- if (event->snapshot == NULL) {
- return;
+ obj->snapshot = ast_channel_snapshot_create(chan);
+ if (obj->snapshot == NULL) {
+ return NULL;
}
}
- event->variable = ast_strdup(name);
- event->value = ast_strdup(value);
- if (event->variable == NULL || event->value == NULL) {
+
+ obj->blob = ast_json_ref(blob);
+
+ msg = stasis_message_create(ast_channel_blob(), obj);
+ if (!msg) {
+ return NULL;
+ }
+
+ ao2_ref(msg, +1);
+ return msg;
+}
+
+const char *ast_channel_blob_type(struct ast_channel_blob *obj)
+{
+ if (obj == NULL) {
+ return NULL;
+ }
+
+ return ast_json_string_get(ast_json_object_get(obj->blob, "type"));
+}
+
+void ast_channel_publish_varset(struct ast_channel *chan, const char *name, const char *value)
+{
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+
+ ast_assert(name != NULL);
+ ast_assert(value != NULL);
+
+ blob = ast_json_pack("{s: s, s: s, s: s}",
+ "type", "varset",
+ "variable", name,
+ "value", value);
+ if (!blob) {
+ ast_log(LOG_ERROR, "Error creating message\n");
return;
}
- msg = stasis_message_create(ast_channel_varset(), event);
+ msg = ast_channel_blob_create(chan, ast_json_ref(blob));
+
if (!msg) {
return;
}
@@ -8633,8 +8675,8 @@ static void channels_shutdown(void)
{
ao2_cleanup(__channel_snapshot);
__channel_snapshot = NULL;
- ao2_cleanup(__channel_varset);
- __channel_varset = NULL;
+ ao2_cleanup(__channel_blob);
+ __channel_blob = NULL;
ao2_cleanup(__channel_topic_all);
__channel_topic_all = NULL;
__channel_topic_all_cached = stasis_caching_unsubscribe(__channel_topic_all_cached);
@@ -8666,7 +8708,7 @@ void ast_channels_init(void)
}
__channel_snapshot = stasis_message_type_create("ast_channel_snapshot");
- __channel_varset = stasis_message_type_create("ast_channel_varset");
+ __channel_blob = stasis_message_type_create("ast_channel_blob");
__channel_topic_all = stasis_topic_create("ast_channel_topic_all");
__channel_topic_all_cached = stasis_caching_topic_create(__channel_topic_all, channel_snapshot_get_id);
@@ -11305,9 +11347,9 @@ struct ast_channel_snapshot *ast_channel_snapshot_create(struct ast_channel *cha
return snapshot;
}
-struct stasis_message_type *ast_channel_varset(void)
+struct stasis_message_type *ast_channel_blob(void)
{
- return __channel_varset;
+ return __channel_blob;
}
struct stasis_message_type *ast_channel_snapshot(void)
diff --git a/main/json.c b/main/json.c
new file mode 100644
index 000000000..f1ebaef60
--- /dev/null
+++ b/main/json.c
@@ -0,0 +1,520 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@digium.com>
+ *
+ * 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 JSON abstraction layer.
+ *
+ * This is a very thin wrapper around the Jansson API. For more details on it, see its
+ * docs at http://www.digip.org/jansson/doc/2.4/apiref.html.
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+/*** MODULEINFO
+ <depend>jansson</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/json.h"
+#include "asterisk/module.h"
+#include "asterisk/utils.h"
+
+#include <jansson.h>
+
+/*!
+ * \brief Function wrapper around ast_malloc macro.
+ */
+static void *json_malloc(size_t size)
+{
+ return ast_malloc(size);
+}
+
+/*!
+ * \brief Function wrapper around ast_free macro.
+ */
+static void json_free(void *p)
+{
+ ast_free(p);
+}
+
+void ast_json_set_alloc_funcs(void *(*malloc_fn)(size_t), void (*free_fn)(void*))
+{
+ json_set_alloc_funcs(malloc_fn, free_fn);
+}
+
+void ast_json_reset_alloc_funcs(void)
+{
+ json_set_alloc_funcs(json_malloc, json_free);
+}
+
+struct ast_json *ast_json_ref(struct ast_json *json)
+{
+ json_incref((json_t *)json);
+ return json;
+}
+
+void ast_json_unref(struct ast_json *json)
+{
+ json_decref((json_t *)json);
+}
+
+enum ast_json_type ast_json_typeof(const struct ast_json *json)
+{
+ int r = json_typeof((json_t*)json);
+ switch(r) {
+ case JSON_OBJECT: return AST_JSON_OBJECT;
+ case JSON_ARRAY: return AST_JSON_ARRAY;
+ case JSON_STRING: return AST_JSON_STRING;
+ case JSON_INTEGER: return AST_JSON_INTEGER;
+ case JSON_REAL: return AST_JSON_REAL;
+ case JSON_TRUE: return AST_JSON_TRUE;
+ case JSON_FALSE: return AST_JSON_FALSE;
+ case JSON_NULL: return AST_JSON_NULL;
+ }
+ ast_assert(0); /* Unexpect return from json_typeof */
+ return r;
+}
+
+struct ast_json *ast_json_true(void)
+{
+ return (struct ast_json *)json_true();
+}
+
+struct ast_json *ast_json_false(void)
+{
+ return (struct ast_json *)json_false();
+}
+
+struct ast_json *ast_json_boolean(int value)
+{
+#if JANSSON_VERSION_HEX >= 0x020400
+ return (struct ast_json *)json_boolean(value);
+#else
+ return value ? ast_json_true() : ast_json_false();
+#endif
+}
+
+struct ast_json *ast_json_null(void)
+{
+ return (struct ast_json *)json_null();
+}
+
+int ast_json_is_true(const struct ast_json *json)
+{
+ return json_is_true((const json_t *)json);
+}
+
+int ast_json_is_false(const struct ast_json *json)
+{
+ return json_is_false((const json_t *)json);
+}
+
+int ast_json_is_null(const struct ast_json *json)
+{
+ return json_is_null((const json_t *)json);
+}
+
+struct ast_json *ast_json_string_create(const char *value)
+{
+ return (struct ast_json *)json_string(value);
+}
+
+const char *ast_json_string_get(const struct ast_json *string)
+{
+ return json_string_value((json_t *)string);
+}
+
+int ast_json_string_set(struct ast_json *string, const char *value)
+{
+ return json_string_set((json_t *)string, value);
+}
+
+struct ast_json *ast_json_stringf(const char *format, ...)
+{
+ struct ast_json *ret;
+ va_list args;
+ va_start(args, format);
+ ret = ast_json_vstringf(format, args);
+ va_end(args);
+ return ret;
+}
+
+struct ast_json *ast_json_vstringf(const char *format, va_list args)
+{
+ char *str = NULL;
+ json_t *ret = NULL;
+
+ if (format) {
+ int err = vasprintf(&str, format, args);
+ if (err > 0) {
+ ret = json_string(str);
+ free(str);
+ }
+ }
+ return (struct ast_json *)ret;
+}
+
+struct ast_json *ast_json_integer_create(intmax_t value)
+{
+ return (struct ast_json *)json_integer(value);
+}
+
+intmax_t ast_json_integer_get(const struct ast_json *integer)
+{
+ return json_integer_value((json_t *)integer);
+}
+
+int ast_json_integer_set(struct ast_json *integer, intmax_t value)
+{
+ return json_integer_set((json_t *)integer, value);
+}
+
+
+int ast_json_equal(const struct ast_json *lhs, const struct ast_json *rhs)
+{
+ return json_equal((json_t *)lhs, (json_t *)rhs);
+}
+
+struct ast_json *ast_json_array_create(void)
+{
+ return (struct ast_json *)json_array();
+}
+size_t ast_json_array_size(const struct ast_json *array)
+{
+ return json_array_size((json_t *)array);
+}
+struct ast_json *ast_json_array_get(const struct ast_json *array, size_t index)
+{
+ return (struct ast_json *)json_array_get((json_t *)array, index);
+}
+int ast_json_array_set(struct ast_json *array, size_t index, struct ast_json *value)
+{
+ return json_array_set_new((json_t *)array, index, (json_t *)value);
+}
+int ast_json_array_append(struct ast_json *array, struct ast_json *value)
+{
+ return json_array_append_new((json_t *)array, (json_t *)value);
+}
+int ast_json_array_insert(struct ast_json *array, size_t index, struct ast_json *value)
+{
+ return json_array_insert_new((json_t *)array, index, (json_t *)value);
+}
+int ast_json_array_remove(struct ast_json *array, size_t index)
+{
+ return json_array_remove((json_t *)array, index);
+}
+int ast_json_array_clear(struct ast_json *array)
+{
+ return json_array_clear((json_t *)array);
+}
+int ast_json_array_extend(struct ast_json *array, struct ast_json *tail)
+{
+ return json_array_extend((json_t *)array, (json_t *)tail);
+}
+
+struct ast_json *ast_json_object_create(void)
+{
+ return (struct ast_json *)json_object();
+}
+size_t ast_json_object_size(struct ast_json *object)
+{
+ return json_object_size((json_t *)object);
+}
+struct ast_json *ast_json_object_get(struct ast_json *object, const char *key)
+{
+ if (!key) {
+ return NULL;
+ }
+ return (struct ast_json *)json_object_get((json_t *)object, key);
+}
+int ast_json_object_set(struct ast_json *object, const char *key, struct ast_json *value)
+{
+ return json_object_set_new((json_t *)object, key, (json_t *)value);
+}
+int ast_json_object_del(struct ast_json *object, const char *key)
+{
+ return json_object_del((json_t *)object, key);
+}
+int ast_json_object_clear(struct ast_json *object)
+{
+ return json_object_clear((json_t *)object);
+}
+int ast_json_object_update(struct ast_json *object, struct ast_json *other)
+{
+ return json_object_update((json_t *)object, (json_t *)other);
+}
+int ast_json_object_update_existing(struct ast_json *object, struct ast_json *other)
+{
+#if JANSSON_VERSION_HEX >= 0x020300
+ return json_object_update_existing((json_t *)object, (json_t *)other);
+#else
+ struct ast_json_iter *iter = ast_json_object_iter(other);
+ int ret = 0;
+
+ if (object == NULL || other == NULL) {
+ return -1;
+ }
+
+ while (iter != NULL && ret == 0) {
+ const char *key = ast_json_object_iter_key(iter);
+ if (ast_json_object_get(object, key) != NULL) {
+ ret = ast_json_object_set(object, key, ast_json_object_iter_value(iter));
+ }
+ iter = ast_json_object_iter_next(other, iter);
+ }
+ return ret;
+#endif
+}
+int ast_json_object_update_missing(struct ast_json *object, struct ast_json *other)
+{
+#if JANSSON_VERSION_HEX >= 0x020300
+ return json_object_update_missing((json_t *)object, (json_t *)other);
+#else
+ struct ast_json_iter *iter = ast_json_object_iter(other);
+ int ret = 0;
+
+ if (object == NULL || other == NULL) {
+ return -1;
+ }
+
+ while (iter != NULL && ret == 0) {
+ const char *key = ast_json_object_iter_key(iter);
+ if (ast_json_object_get(object, key) == NULL) {
+ ret = ast_json_object_set(object, key, ast_json_object_iter_value(iter));
+ }
+ iter = ast_json_object_iter_next(other, iter);
+ }
+ return ret;
+#endif
+}
+
+struct ast_json_iter *ast_json_object_iter(struct ast_json *object)
+{
+ return json_object_iter((json_t *)object);
+}
+struct ast_json_iter *ast_json_object_iter_at(struct ast_json *object, const char *key)
+{
+ return json_object_iter_at((json_t *)object, key);
+}
+struct ast_json_iter *ast_json_object_iter_next(struct ast_json *object, struct ast_json_iter *iter)
+{
+ return json_object_iter_next((json_t *)object, iter);
+}
+const char *ast_json_object_iter_key(struct ast_json_iter *iter)
+{
+ return json_object_iter_key(iter);
+}
+struct ast_json *ast_json_object_iter_value(struct ast_json_iter *iter)
+{
+ return (struct ast_json *)json_object_iter_value(iter);
+}
+int ast_json_object_iter_set(struct ast_json *object, struct ast_json_iter *iter, struct ast_json *value)
+{
+ return json_object_iter_set_new((json_t *)object, iter, (json_t *)value);
+}
+
+/*!
+ * \brief Default flags for JSON encoding.
+ */
+static size_t dump_flags(void)
+{
+ /* There's a chance this could become a runtime flag */
+ int flags = JSON_COMPACT;
+#ifdef AST_DEVMODE
+ /* In dev mode, write readable JSON */
+ flags = JSON_INDENT(2) | JSON_PRESERVE_ORDER;
+#endif
+ return flags;
+}
+
+char *ast_json_dump_string(struct ast_json *root)
+{
+ return json_dumps((json_t *)root, dump_flags());
+}
+
+static int write_to_ast_str(const char *buffer, size_t size, void *data)
+{
+ struct ast_str **dst = data;
+ size_t str_size = ast_str_size(*dst);
+ size_t remaining = str_size - ast_str_strlen(*dst);
+ int ret;
+
+ /* While ast_str_append will grow the ast_str, it won't report
+ * allocation errors. Fortunately, it's not that hard.
+ */
+
+ /* Remaining needs to be big enough for buffer, plus null char */
+ while (remaining < size + 1) {
+ /* doubling the size of the buffer gives us 'amortized
+ * constant' time.
+ * See http://stackoverflow.com/a/249695/115478 for info.
+ */
+ str_size *= 2;
+ remaining = str_size - ast_str_strlen(*dst);
+ }
+
+ ret = ast_str_make_space(dst, str_size);
+ if (ret == -1) {
+ /* Could not alloc; fail */
+ return -1;
+ }
+
+ ast_str_append_substr(dst, -1, buffer, size);
+ return 0;
+}
+
+int ast_json_dump_str(struct ast_json *root, struct ast_str **dst)
+{
+ return json_dump_callback((json_t *)root, write_to_ast_str, dst, dump_flags());
+}
+
+
+int ast_json_dump_file(struct ast_json *root, FILE *output)
+{
+ if (!root || !output) {
+ return -1;
+ }
+ return json_dumpf((json_t *)root, output, dump_flags());
+}
+int ast_json_dump_new_file(struct ast_json *root, const char *path)
+{
+ if (!root || !path) {
+ return -1;
+ }
+ return json_dump_file((json_t *)root, path, dump_flags());
+}
+
+/*!
+ * \brief Copy Jansson error struct to ours.
+ */
+static void copy_error(struct ast_json_error *error, const json_error_t *jansson_error)
+{
+ if (error && jansson_error) {
+ error->line = jansson_error->line;
+ error->column = jansson_error->column;
+ error->position = jansson_error->position;
+ ast_copy_string(error->text, jansson_error->text, sizeof(error->text));
+ ast_copy_string(error->source, jansson_error->source, sizeof(error->source));
+ }
+
+}
+
+static void parse_error(struct ast_json_error *error, const char *text, const char *source)
+{
+ if (error != NULL) {
+ error->line = 0;
+ error->column = 0;
+ error->position = 0;
+ strncpy(error->text, text, sizeof(error->text));
+ strncpy(error->source, source, sizeof(error->text));
+ }
+}
+
+struct ast_json *ast_json_load_string(const char *input, struct ast_json_error *error)
+{
+ json_error_t jansson_error = {};
+ struct ast_json *r = NULL;
+ if (input != NULL) {
+ r = (struct ast_json *)json_loads(input, 0, &jansson_error);
+ copy_error(error, &jansson_error);
+ } else {
+ parse_error(error, "NULL input string", "<null>");
+ }
+ return r;
+}
+
+struct ast_json *ast_json_load_str(const struct ast_str *input, struct ast_json_error *error)
+{
+ return ast_json_load_string(ast_str_buffer(input), error);
+}
+
+struct ast_json *ast_json_load_buf(const char *buffer, size_t buflen, struct ast_json_error *error)
+{
+ json_error_t jansson_error = {};
+ struct ast_json *r = (struct ast_json *)json_loadb(buffer, buflen, 0, &jansson_error);
+ copy_error(error, &jansson_error);
+ return r;
+}
+struct ast_json *ast_json_load_file(FILE *input, struct ast_json_error *error)
+{
+ json_error_t jansson_error = {};
+ struct ast_json *r = NULL;
+ if (input != NULL) {
+ r = (struct ast_json *)json_loadf(input, 0, &jansson_error);
+ copy_error(error, &jansson_error);
+ } else {
+ parse_error(error, "NULL input file", "<null>");
+ }
+ return r;
+}
+struct ast_json *ast_json_load_new_file(const char *path, struct ast_json_error *error)
+{
+ json_error_t jansson_error = {};
+ struct ast_json *r = (struct ast_json *)json_load_file(path, 0, &jansson_error);
+ copy_error(error, &jansson_error);
+ return r;
+}
+
+struct ast_json *ast_json_pack(char const *format, ...)
+{
+ struct ast_json *ret;
+ va_list args;
+ va_start(args, format);
+ ret = ast_json_vpack(format, args);
+ va_end(args);
+ return ret;
+}
+struct ast_json *ast_json_vpack(char const *format, va_list ap)
+{
+ struct ast_json *r = NULL;
+ if (format) {
+ r = (struct ast_json *)json_vpack_ex(NULL, 0, format, ap);
+ }
+ return r;
+}
+
+struct ast_json *ast_json_copy(const struct ast_json *value)
+{
+ return (struct ast_json *)json_copy((json_t *)value);
+}
+struct ast_json *ast_json_deep_copy(const struct ast_json *value)
+{
+ return (struct ast_json *)json_deep_copy((json_t *)value);
+}
+
+static int unload_module(void)
+{
+ /* Nothing to do */
+ return 0;
+}
+
+static int load_module(void)
+{
+ /* Setup to use Asterisk custom allocators */
+ ast_json_reset_alloc_funcs();
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "JSON library",
+ .load = load_module,
+ .unload = unload_module);
diff --git a/main/manager.c b/main/manager.c
index 821fde8db..db19290f3 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -964,73 +964,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
manager.conf will be present upon starting a new session.</para>
</description>
</manager>
- <managerEvent language="en_US" name="Newchannel">
- <managerEventInstance class="EVENT_FLAG_CALL">
- <synopsis>Raised when a new channel is created.</synopsis>
- <syntax>
- <parameter name="Channel">
- </parameter>
- <parameter name="ChannelState">
- <para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
- </parameter>
- <parameter name="ChannelStateDesc">
- <enumlist>
- <enum name="Down"/>
- <enum name="Rsrvd"/>
- <enum name="OffHook"/>
- <enum name="Dialing"/>
- <enum name="Ring"/>
- <enum name="Ringing"/>
- <enum name="Up"/>
- <enum name="Busy"/>
- <enum name="Dialing Offhook"/>
- <enum name="Pre-ring"/>
- <enum name="Unknown"/>
- </enumlist>
- </parameter>
- <parameter name="CallerIDNum">
- </parameter>
- <parameter name="CallerIDName">
- </parameter>
- <parameter name="ConnectedLineNum">
- </parameter>
- <parameter name="ConnectedLineName">
- </parameter>
- <parameter name="AccountCode">
- </parameter>
- <parameter name="Context">
- </parameter>
- <parameter name="Exten">
- </parameter>
- <parameter name="Priority">
- </parameter>
- <parameter name="Uniqueid">
- </parameter>
- <parameter name="Cause">
- <para>A numeric cause code for why the channel was hung up.</para>
- </parameter>
- <parameter name="Cause-txt">
- <para>A description of why the channel was hung up.</para>
- </parameter>
- </syntax>
- </managerEventInstance>
- </managerEvent>
- <managerEvent language="en_US" name="Newstate">
- <managerEventInstance class="EVENT_FLAG_CALL">
- <synopsis>Raised when a channel's state changes.</synopsis>
- <syntax>
- <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
- </syntax>
- </managerEventInstance>
- </managerEvent>
- <managerEvent language="en_US" name="Hangup">
- <managerEventInstance class="EVENT_FLAG_CALL">
- <synopsis>Raised when a channel is hung up.</synopsis>
- <syntax>
- <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
- </syntax>
- </managerEventInstance>
- </managerEvent>
***/
/*! \addtogroup Group_AMI AMI functions
@@ -1128,8 +1061,6 @@ static const struct {
{{ "restart", "gracefully", NULL }},
};
-static struct stasis_subscription *channel_state_sub;
-
static void acl_change_event_cb(const struct ast_event *event, void *userdata);
static void acl_change_event_subscribe(void)
@@ -7446,127 +7377,6 @@ static void load_channelvars(struct ast_variable *var)
AST_RWLIST_UNLOCK(&channelvars);
}
-/*!
- * \brief Generate the AMI message body from a channel snapshot
- * \internal
- *
- * \param snapshot the channel snapshot for which to generate an AMI message body
- *
- * \retval NULL on error
- * \retval ast_str* on success (must be ast_freed by caller)
- */
-static struct ast_str *manager_build_channel_state_string(const struct ast_channel_snapshot *snapshot)
-{
- struct ast_str *out = ast_str_create(1024);
- int res = 0;
- if (!out) {
- return NULL;
- }
- res = ast_str_set(&out, 0,
- "Channel: %s\r\n"
- "ChannelState: %d\r\n"
- "ChannelStateDesc: %s\r\n"
- "CallerIDNum: %s\r\n"
- "CallerIDName: %s\r\n"
- "ConnectedLineNum: %s\r\n"
- "ConnectedLineName: %s\r\n"
- "AccountCode: %s\r\n"
- "Context: %s\r\n"
- "Exten: %s\r\n"
- "Priority: %d\r\n"
- "Uniqueid: %s\r\n"
- "Cause: %d\r\n"
- "Cause-txt: %s\r\n",
- snapshot->name,
- snapshot->state,
- ast_state2str(snapshot->state),
- snapshot->caller_number,
- snapshot->caller_name,
- snapshot->connected_number,
- snapshot->connected_name,
- snapshot->accountcode,
- snapshot->context,
- snapshot->exten,
- snapshot->priority,
- snapshot->uniqueid,
- snapshot->hangupcause,
- ast_cause2str(snapshot->hangupcause));
-
- if (!res) {
- return NULL;
- }
-
- return out;
-}
-
-static void channel_snapshot_update(struct ast_channel_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot)
-{
- int is_hungup;
- char *manager_event = NULL;
-
- if (!new_snapshot) {
- /* Ignore cache clearing events; we'll see the hangup first */
- return;
- }
-
- is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE) ? 1 : 0;
-
- if (!old_snapshot) {
- manager_event = "Newchannel";
- }
-
- if (old_snapshot && old_snapshot->state != new_snapshot->state) {
- manager_event = "Newstate";
- }
-
- if (old_snapshot && is_hungup) {
- manager_event = "Hangup";
- }
-
- if (manager_event) {
- RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
-
- channel_event_string = manager_build_channel_state_string(new_snapshot);
- if (channel_event_string) {
- manager_event(EVENT_FLAG_CALL, manager_event, "%s", ast_str_buffer(channel_event_string));
- }
- }
-}
-
-static void channel_varset(const char *channel_name, const char *uniqueid, const char *name, const char *value)
-{
- /*** DOCUMENTATION
- <managerEventInstance>
- <synopsis>Raised when a variable is set to a particular value.</synopsis>
- </managerEventInstance>
- ***/
- manager_event(EVENT_FLAG_DIALPLAN, "VarSet",
- "Channel: %s\r\n"
- "Variable: %s\r\n"
- "Value: %s\r\n"
- "Uniqueid: %s\r\n",
- channel_name, name, value, uniqueid);
-}
-
-static void channel_event_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
-{
- if (stasis_message_type(message) == stasis_cache_update()) {
- struct stasis_cache_update *update = stasis_message_data(message);
- if (ast_channel_snapshot() == update->type) {
- struct ast_channel_snapshot *old_snapshot =
- stasis_message_data(update->old_snapshot);
- struct ast_channel_snapshot *new_snapshot =
- stasis_message_data(update->new_snapshot);
- channel_snapshot_update(old_snapshot, new_snapshot);
- }
- } else if (stasis_message_type(message) == ast_channel_varset()) {
- struct ast_channel_varset *varset = stasis_message_data(message);
- const char *name = varset->snapshot ? varset->snapshot->name : "none";
- const char *uniqueid = varset->snapshot ? varset->snapshot->uniqueid : "none";
- channel_varset(name, uniqueid, varset->variable, varset->value);
- }
-}
-
/*! \internal \brief Free a user record. Should already be removed from the list */
static void manager_free_user(struct ast_manager_user *user)
{
@@ -7590,8 +7400,6 @@ static void manager_shutdown(void)
{
struct ast_manager_user *user;
- channel_state_sub = stasis_unsubscribe(channel_state_sub);
-
if (registered) {
ast_manager_unregister("Ping");
ast_manager_unregister("Events");
@@ -7683,10 +7491,8 @@ static int __init_manager(int reload, int by_external_config)
manager_enabled = 0;
- if (!channel_state_sub) {
- channel_state_sub = stasis_subscribe(
- stasis_caching_get_topic(ast_channel_topic_all_cached()),
- channel_event_cb, NULL);
+ if (manager_channels_init()) {
+ return -1;
}
if (!registered) {
diff --git a/main/manager_channels.c b/main/manager_channels.c
new file mode 100644
index 000000000..802b34187
--- /dev/null
+++ b/main/manager_channels.c
@@ -0,0 +1,409 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@digium.com>
+ *
+ * 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 The Asterisk Management Interface - AMI (channel event handling)
+ *
+ * \author David M. Lee, II <dlee@digium.com>
+ *
+ * AMI generated many per-channel and global-channel events by converting Stasis
+ * messages to AMI events. It makes sense to simply put them into a single file.
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/channel.h"
+#include "asterisk/manager.h"
+#include "asterisk/stasis_message_router.h"
+#include "asterisk/pbx.h"
+
+static struct stasis_message_router *channel_state_router;
+
+/*** DOCUMENTATION
+ <managerEvent language="en_US" name="Newchannel">
+ <managerEventInstance class="EVENT_FLAG_CALL">
+ <synopsis>Raised when a new channel is created.</synopsis>
+ <syntax>
+ <parameter name="Channel">
+ </parameter>
+ <parameter name="ChannelState">
+ <para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
+ </parameter>
+ <parameter name="ChannelStateDesc">
+ <enumlist>
+ <enum name="Down"/>
+ <enum name="Rsrvd"/>
+ <enum name="OffHook"/>
+ <enum name="Dialing"/>
+ <enum name="Ring"/>
+ <enum name="Ringing"/>
+ <enum name="Up"/>
+ <enum name="Busy"/>
+ <enum name="Dialing Offhook"/>
+ <enum name="Pre-ring"/>
+ <enum name="Unknown"/>
+ </enumlist>
+ </parameter>
+ <parameter name="CallerIDNum">
+ </parameter>
+ <parameter name="CallerIDName">
+ </parameter>
+ <parameter name="ConnectedLineNum">
+ </parameter>
+ <parameter name="ConnectedLineName">
+ </parameter>
+ <parameter name="AccountCode">
+ </parameter>
+ <parameter name="Context">
+ </parameter>
+ <parameter name="Exten">
+ </parameter>
+ <parameter name="Priority">
+ </parameter>
+ <parameter name="Uniqueid">
+ </parameter>
+ <parameter name="Cause">
+ <para>A numeric cause code for why the channel was hung up.</para>
+ </parameter>
+ <parameter name="Cause-txt">
+ <para>A description of why the channel was hung up.</para>
+ </parameter>
+ </syntax>
+ </managerEventInstance>
+ </managerEvent>
+ <managerEvent language="en_US" name="Newstate">
+ <managerEventInstance class="EVENT_FLAG_CALL">
+ <synopsis>Raised when a channel's state changes.</synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+ </syntax>
+ </managerEventInstance>
+ </managerEvent>
+ <managerEvent language="en_US" name="Hangup">
+ <managerEventInstance class="EVENT_FLAG_CALL">
+ <synopsis>Raised when a channel is hung up.</synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+ </syntax>
+ </managerEventInstance>
+ </managerEvent>
+ ***/
+
+/*!
+ * \brief Generate the AMI message body from a channel snapshot
+ * \internal
+ *
+ * \param snapshot the channel snapshot for which to generate an AMI message
+ * body
+ *
+ * \retval NULL on error
+ * \retval ast_str* on success (must be ast_freed by caller)
+ */
+static struct ast_str *manager_build_channel_state_string(
+ const struct ast_channel_snapshot *snapshot)
+{
+ struct ast_str *out = ast_str_create(1024);
+ int res = 0;
+ if (!out) {
+ return NULL;
+ }
+ res = ast_str_set(&out, 0,
+ "Channel: %s\r\n"
+ "ChannelState: %d\r\n"
+ "ChannelStateDesc: %s\r\n"
+ "CallerIDNum: %s\r\n"
+ "CallerIDName: %s\r\n"
+ "ConnectedLineNum: %s\r\n"
+ "ConnectedLineName: %s\r\n"
+ "AccountCode: %s\r\n"
+ "Context: %s\r\n"
+ "Exten: %s\r\n"
+ "Priority: %d\r\n"
+ "Uniqueid: %s\r\n"
+ "Cause: %d\r\n"
+ "Cause-txt: %s\r\n",
+ snapshot->name,
+ snapshot->state,
+ ast_state2str(snapshot->state),
+ snapshot->caller_number,
+ snapshot->caller_name,
+ snapshot->connected_number,
+ snapshot->connected_name,
+ snapshot->accountcode,
+ snapshot->context,
+ snapshot->exten,
+ snapshot->priority,
+ snapshot->uniqueid,
+ snapshot->hangupcause,
+ ast_cause2str(snapshot->hangupcause));
+
+ if (!res) {
+ return NULL;
+ }
+
+ return out;
+}
+
+static inline int cep_has_changed(
+ const struct ast_channel_snapshot *old_snapshot,
+ const struct ast_channel_snapshot *new_snapshot)
+{
+ ast_assert(old_snapshot != NULL);
+ ast_assert(new_snapshot != NULL);
+ return old_snapshot->priority != new_snapshot->priority ||
+ strcmp(old_snapshot->context, new_snapshot->context) != 0 ||
+ strcmp(old_snapshot->exten, new_snapshot->exten) != 0;
+}
+
+static void channel_snapshot_update(void *data, struct stasis_subscription *sub,
+ struct stasis_topic *topic,
+ struct stasis_message *message)
+{
+ RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
+ struct stasis_cache_update *update = stasis_message_data(message);
+ struct ast_channel_snapshot *old_snapshot;
+ struct ast_channel_snapshot *new_snapshot;
+ int is_hungup, was_hungup;
+ char *manager_event = NULL;
+ int new_exten;
+
+ if (ast_channel_snapshot() != update->type) {
+ return;
+ }
+
+ old_snapshot = stasis_message_data(update->old_snapshot);
+ new_snapshot = stasis_message_data(update->new_snapshot);
+
+ if (!new_snapshot) {
+ /* Ignore cache clearing events; we'll see the hangup first */
+ return;
+ }
+
+ was_hungup = (old_snapshot && ast_test_flag(&old_snapshot->flags, AST_FLAG_ZOMBIE)) ? 1 : 0;
+ is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE) ? 1 : 0;
+
+ if (!old_snapshot) {
+ manager_event = "Newchannel";
+ }
+
+ if (old_snapshot && old_snapshot->state != new_snapshot->state) {
+ manager_event = "Newstate";
+ }
+
+ if (!was_hungup && is_hungup) {
+ manager_event = "Hangup";
+ }
+
+ /* Detect Newexten transitions
+ * - if new snapshot has an application set AND
+ * - first snapshot OR
+ * - if the old snapshot has no application (first Newexten) OR
+ * - if the context/priority/exten changes
+ */
+ new_exten = !ast_strlen_zero(new_snapshot->appl) && (
+ !old_snapshot ||
+ ast_strlen_zero(old_snapshot->appl) ||
+ cep_has_changed(old_snapshot, new_snapshot));
+
+ if (manager_event || new_exten) {
+ channel_event_string =
+ manager_build_channel_state_string(new_snapshot);
+ }
+
+ if (!channel_event_string) {
+ return;
+ }
+
+ /* Channel state change events */
+ if (manager_event) {
+ manager_event(EVENT_FLAG_CALL, manager_event, "%s",
+ ast_str_buffer(channel_event_string));
+ }
+
+ if (new_exten) {
+ /* DEPRECATED: Extension field deprecated in 12; remove in 14 */
+ /*** DOCUMENTATION
+ <managerEventInstance>
+ <synopsis>Raised when a channel enters a new context, extension, priority.</synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+ <parameter name="Extension">
+ <para>Deprecated in 12, but kept for
+ backward compatability. Please use
+ 'Exten' instead.</para>
+ </parameter>
+ <parameter name="Application">
+ <para>The application about to be executed.</para>
+ </parameter>
+ <parameter name="AppData">
+ <para>The data to be passed to the application.</para>
+ </parameter>
+ </syntax>
+ </managerEventInstance>
+ ***/
+ manager_event(EVENT_FLAG_DIALPLAN, "Newexten",
+ "%s"
+ "Extension: %s\r\n"
+ "Application: %s\r\n"
+ "AppData: %s\r\n",
+ ast_str_buffer(channel_event_string),
+ new_snapshot->exten,
+ new_snapshot->appl,
+ new_snapshot->data);
+ }
+}
+
+static void channel_varset(struct ast_channel_blob *obj)
+{
+ RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
+ const char *variable = ast_json_string_get(ast_json_object_get(obj->blob, "variable"));
+ const char *value = ast_json_string_get(ast_json_object_get(obj->blob, "value"));
+
+ if (obj->snapshot) {
+ channel_event_string = manager_build_channel_state_string(obj->snapshot);
+ } else {
+ channel_event_string = ast_str_create(35);
+ ast_str_set(&channel_event_string, 0,
+ "Channel: none\r\n"
+ "Uniqueid: none\r\n");
+ }
+
+ if (!channel_event_string) {
+ return;
+ }
+
+ /*** DOCUMENTATION
+ <managerEventInstance>
+ <synopsis>Raised when a variable is set to a particular value.</synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+ <parameter name="Variable">
+ <para>The variable being set.</para>
+ </parameter>
+ <parameter name="Value">
+ <para>The new value of the variable.</para>
+ </parameter>
+ </syntax>
+ </managerEventInstance>
+ ***/
+ manager_event(EVENT_FLAG_DIALPLAN, "VarSet",
+ "%s"
+ "Variable: %s\r\n"
+ "Value: %s\r\n",
+ ast_str_buffer(channel_event_string),
+ variable, value);
+}
+
+static void channel_userevent(struct ast_channel_blob *obj)
+{
+ RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
+ const char *eventname;
+ const char *body;
+
+ eventname = ast_json_string_get(ast_json_object_get(obj->blob, "eventname"));
+ body = ast_json_string_get(ast_json_object_get(obj->blob, "body"));
+ channel_event_string = manager_build_channel_state_string(obj->snapshot);
+
+ if (!channel_event_string) {
+ return;
+ }
+
+ /*** DOCUMENTATION
+ <managerEventInstance>
+ <synopsis>A user defined event raised from the dialplan.</synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+ <parameter name="UserEvent">
+ <para>The event name, as specified in the dialplan.</para>
+ </parameter>
+ </syntax>
+ <see-also>
+ <ref type="application">UserEvent</ref>
+ </see-also>
+ </managerEventInstance>
+ ***/
+ manager_event(EVENT_FLAG_USER, "UserEvent",
+ "%s"
+ "UserEvent: %s\r\n"
+ "%s",
+ ast_str_buffer(channel_event_string), eventname, body);
+}
+
+/*!
+ * \brief Callback processing messages on the channel topic.
+ */
+static void channel_blob_cb(void *data, struct stasis_subscription *sub,
+ struct stasis_topic *topic,
+ struct stasis_message *message)
+{
+ struct ast_channel_blob *obj = stasis_message_data(message);
+
+ if (strcmp("varset", ast_channel_blob_type(obj)) == 0) {
+ channel_varset(obj);
+ } else if (strcmp("userevent", ast_channel_blob_type(obj)) == 0) {
+ channel_userevent(obj);
+ }
+}
+
+static void manager_channels_shutdown(void)
+{
+ stasis_message_router_unsubscribe(channel_state_router);
+ channel_state_router = NULL;
+}
+
+int manager_channels_init(void)
+{
+ int ret = 0;
+
+ if (channel_state_router) {
+ /* Already initialized */
+ return 0;
+ }
+
+ ast_register_atexit(manager_channels_shutdown);
+
+ channel_state_router = stasis_message_router_create(
+ stasis_caching_get_topic(ast_channel_topic_all_cached()));
+
+ if (!channel_state_router) {
+ return -1;
+ }
+
+ ret |= stasis_message_router_add(channel_state_router,
+ stasis_cache_update(),
+ channel_snapshot_update,
+ NULL);
+
+ ret |= stasis_message_router_add(channel_state_router,
+ ast_channel_blob(),
+ channel_blob_cb,
+ NULL);
+
+ /* If somehow we failed to add any routes, just shut down the whole
+ * things and fail it.
+ */
+ if (ret) {
+ manager_channels_shutdown();
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/main/pbx.c b/main/pbx.c
index 82bbb5257..5d5ef9539 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -4655,8 +4655,9 @@ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
int res;
struct pbx_find_info q = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
char passdata[EXT_DATA_SIZE];
-
int matching_action = (action == E_MATCH || action == E_CANMATCH || action == E_MATCHMORE);
+ RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
ast_rdlock_contexts();
if (found)
@@ -4700,28 +4701,18 @@ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
COLORIZE(COLOR_BRMAGENTA, 0, passdata),
"in new stack");
}
- /*** DOCUMENTATION
- <managerEventInstance>
- <synopsis>Raised when a channel enters a new context, extension, priority.</synopsis>
- <syntax>
- <parameter name="Application">
- <para>The application about to be executed.</para>
- </parameter>
- <parameter name="AppData">
- <para>The data to be passed to the application.</para>
- </parameter>
- </syntax>
- </managerEventInstance>
- ***/
- manager_event(EVENT_FLAG_DIALPLAN, "Newexten",
- "Channel: %s\r\n"
- "Context: %s\r\n"
- "Extension: %s\r\n"
- "Priority: %d\r\n"
- "Application: %s\r\n"
- "AppData: %s\r\n"
- "Uniqueid: %s\r\n",
- ast_channel_name(c), ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), app->name, passdata, ast_channel_uniqueid(c));
+ snapshot = ast_channel_snapshot_create(c);
+ if (snapshot) {
+ /* pbx_exec sets application name and data, but we don't want to log
+ * every exec. Just update the snapshot here instead.
+ */
+ ast_string_field_set(snapshot, appl, app->name);
+ ast_string_field_set(snapshot, data, passdata);
+ msg = stasis_message_create(ast_channel_snapshot(), snapshot);
+ if (msg) {
+ stasis_publish(ast_channel_topic(c), msg);
+ }
+ }
return pbx_exec(c, app, passdata); /* 0 on success, -1 on failure */
}
} else if (q.swo) { /* not found here, but in another switch */