diff --git a/CHANGES b/CHANGES index 248b110d42..8bcd533dfb 100644 --- a/CHANGES +++ b/CHANGES @@ -50,6 +50,9 @@ AMI (Asterisk Manager Interface) * The AMI event 'UserEvent' from app_userevent now contains the channel state fields. The channel state fields will come before the body fields. + * The deprecated use of | (pipe) as a separator in the channelvars setting in + manager.conf has been removed. + Channel Drivers ------------------ diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 8fef86d2ca..b60a92af85 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -4136,7 +4136,11 @@ struct ast_channel_snapshot { int priority; /*!< Dialplan: Current extension priority */ int amaflags; /*!< AMA flags for billing */ int hangupcause; /*!< Why is the channel hanged up. See causes.h */ + int caller_pres; /*!< Caller ID presentation. */ + struct ast_flags flags; /*!< channel flags of AST_FLAG_ type */ + + struct varshead *manager_vars; /*!< Variables to be appended to manager events */ }; /*! @@ -4151,6 +4155,27 @@ struct ast_channel_snapshot { */ struct ast_channel_snapshot *ast_channel_snapshot_create(struct ast_channel *chan); +/*! + * \since 12 + * \brief Sets the variables to be stored in the \a manager_vars field of all + * snapshots. + * \param varc Number of variable names. + * \param vars Array of variable names. + */ +void ast_channel_set_manager_vars(size_t varc, char **vars); + +/*! + * \since 12 + * \brief Gets the variables for a given channel, as specified by ast_channel_set_manager_vars(). + * + * The returned variable list is an AO2 object, so ao2_cleanup() to free it. + * + * \param chan Channel to get variables for. + * \return List of channel variables. + * \return \c NULL on error + */ +struct varshead *ast_channel_get_manager_vars(struct ast_channel *chan); + /*! * \since 12 * \brief Message type for \ref ast_channel_snapshot. @@ -4163,10 +4188,12 @@ struct stasis_message_type *ast_channel_snapshot(void); * \since 12 * \brief A topic which publishes the events for a particular channel. * - * \param chan Channel. + * If the given \a chan is \c NULL, ast_channel_topic_all() is returned. + * + * \param chan Channel, or \c NULL. * * \retval Topic for channel's events. - * \retval \c NULL if \a chan is \c NULL. + * \retval ast_channel_topic_all() if \a chan is \c NULL. */ struct stasis_topic *ast_channel_topic(struct ast_channel *chan); diff --git a/main/channel.c b/main/channel.c index 5318e6667d..56752f852d 100644 --- a/main/channel.c +++ b/main/channel.c @@ -243,6 +243,18 @@ static void publish_channel_state(struct ast_channel *chan) stasis_publish(ast_channel_topic(chan), message); } +static void publish_channel_blob(struct ast_channel *chan, struct ast_json *blob) +{ + RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup); + if (blob) { + message = ast_channel_blob_create(chan, blob); + } + if (message) { + stasis_publish(ast_channel_topic(chan), message); + } +} + + static void channel_blob_dtor(void *obj) { struct ast_channel_blob *event = obj; @@ -309,22 +321,7 @@ void ast_channel_publish_varset(struct ast_channel *chan, const char *name, cons "type", "varset", "variable", name, "value", value); - if (!blob) { - ast_log(LOG_ERROR, "Error creating message\n"); - return; - } - - msg = ast_channel_blob_create(chan, ast_json_ref(blob)); - - if (!msg) { - return; - } - - if (chan) { - stasis_publish(ast_channel_topic(chan), msg); - } else { - stasis_publish(ast_channel_topic_all(), msg); - } + publish_channel_blob(chan, blob); } @@ -1463,22 +1460,16 @@ int ast_queue_frame_head(struct ast_channel *chan, struct ast_frame *fin) /*! \brief Queue a hangup frame for channel */ int ast_queue_hangup(struct ast_channel *chan) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup); struct ast_frame f = { AST_FRAME_CONTROL, .subclass.integer = AST_CONTROL_HANGUP }; int res; /* Yeah, let's not change a lock-critical value without locking */ ast_channel_lock(chan); ast_channel_softhangup_internal_flag_add(chan, AST_SOFTHANGUP_DEV); - /*** DOCUMENTATION - - Raised when a hangup is requested with no set cause. - - ***/ - manager_event(EVENT_FLAG_CALL, "HangupRequest", - "Channel: %s\r\n" - "Uniqueid: %s\r\n", - ast_channel_name(chan), - ast_channel_uniqueid(chan)); + blob = ast_json_pack("{s: s}", "type", "hangup_request"); + publish_channel_blob(chan, blob); res = ast_queue_frame(chan, &f); ast_channel_unlock(chan); @@ -1488,6 +1479,8 @@ int ast_queue_hangup(struct ast_channel *chan) /*! \brief Queue a hangup frame for channel */ int ast_queue_hangup_with_cause(struct ast_channel *chan, int cause) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup); struct ast_frame f = { AST_FRAME_CONTROL, .subclass.integer = AST_CONTROL_HANGUP }; int res; @@ -1501,21 +1494,10 @@ int ast_queue_hangup_with_cause(struct ast_channel *chan, int cause) if (cause < 0) { f.data.uint32 = ast_channel_hangupcause(chan); } - /*** DOCUMENTATION - - Raised when a hangup is requested with a specific cause code. - - - - - ***/ - manager_event(EVENT_FLAG_CALL, "HangupRequest", - "Channel: %s\r\n" - "Uniqueid: %s\r\n" - "Cause: %d\r\n", - ast_channel_name(chan), - ast_channel_uniqueid(chan), - cause); + blob = ast_json_pack("{s: s, s: i}", + "type", "hangup_request", + "cause", cause); + publish_channel_blob(chan, blob); res = ast_queue_frame(chan, &f); ast_channel_unlock(chan); @@ -2818,25 +2800,16 @@ int ast_softhangup_nolock(struct ast_channel *chan, int cause) /*! \brief Softly hangup a channel, lock */ int ast_softhangup(struct ast_channel *chan, int cause) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); int res; ast_channel_lock(chan); res = ast_softhangup_nolock(chan, cause); - /*** DOCUMENTATION - - Raised when a soft hangup is requested with a specific cause code. - - - - - ***/ - manager_event(EVENT_FLAG_CALL, "SoftHangupRequest", - "Channel: %s\r\n" - "Uniqueid: %s\r\n" - "Cause: %d\r\n", - ast_channel_name(chan), - ast_channel_uniqueid(chan), - cause); + blob = ast_json_pack("{s: s, s: i, s: b}", + "type", "hangup_request", + "cause", cause, + "soft", 1); + publish_channel_blob(chan, blob); ast_channel_unlock(chan); return res; @@ -6836,39 +6809,6 @@ static void ast_set_owners_and_peers(struct ast_channel *chan1, } } -/*! - * \pre chan is locked - */ -static void report_new_callerid(struct ast_channel *chan) -{ - int pres; - - pres = ast_party_id_presentation(&ast_channel_caller(chan)->id); - /*** DOCUMENTATION - - Raised when a channel receives new Caller ID information. - - - A description of the Caller ID presentation. - - - - ***/ - ast_manager_event(chan, EVENT_FLAG_CALL, "NewCallerid", - "Channel: %s\r\n" - "CallerIDNum: %s\r\n" - "CallerIDName: %s\r\n" - "Uniqueid: %s\r\n" - "CID-CallingPres: %d (%s)\r\n", - ast_channel_name(chan), - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, ""), - S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, ""), - ast_channel_uniqueid(chan), - pres, - ast_describe_caller_presentation(pres) - ); -} - /*! * \internal * \brief Transfer COLP between target and transferee channels. @@ -7273,7 +7213,7 @@ void ast_do_masquerade(struct ast_channel *original) ast_channel_redirecting_set(original, ast_channel_redirecting(clonechan)); ast_channel_redirecting_set(clonechan, &exchange.redirecting); - report_new_callerid(original); + publish_channel_state(original); /* Restore original timing file descriptor */ ast_channel_set_fd(original, AST_TIMING_FD, ast_channel_timingfd(original)); @@ -7439,7 +7379,7 @@ void ast_set_callerid(struct ast_channel *chan, const char *cid_num, const char ast_cdr_setcid(ast_channel_cdr(chan), chan); } - report_new_callerid(chan); + publish_channel_state(chan); ast_channel_unlock(chan); } @@ -7458,26 +7398,14 @@ void ast_channel_set_caller(struct ast_channel *chan, const struct ast_party_cal void ast_channel_set_caller_event(struct ast_channel *chan, const struct ast_party_caller *caller, const struct ast_set_party_caller *update) { - const char *pre_set_number; - const char *pre_set_name; - if (ast_channel_caller(chan) == caller) { /* Don't set to self */ return; } ast_channel_lock(chan); - pre_set_number = - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL); - pre_set_name = S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL); ast_party_caller_set(ast_channel_caller(chan), caller, update); - if (S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL) - != pre_set_number - || S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL) - != pre_set_name) { - /* The caller id name or number changed. */ - report_new_callerid(chan); - } + publish_channel_state(chan); if (ast_channel_cdr(chan)) { ast_cdr_setcid(ast_channel_cdr(chan), chan); } @@ -8671,8 +8599,108 @@ static void prnt_channel_key(void *v_obj, void *where, ao2_prnt_fn *prnt) prnt(where, "%s", ast_channel_name(chan)); } +/*! + * \brief List of channel variables to append to all channel-related events. + */ +struct manager_channel_variable { + AST_LIST_ENTRY(manager_channel_variable) entry; + unsigned int isfunc:1; + char name[]; +}; + +static AST_RWLIST_HEAD_STATIC(channelvars, manager_channel_variable); + +static void free_channelvars(void) +{ + struct manager_channel_variable *var; + AST_RWLIST_WRLOCK(&channelvars); + while ((var = AST_RWLIST_REMOVE_HEAD(&channelvars, entry))) { + ast_free(var); + } + AST_RWLIST_UNLOCK(&channelvars); +} + +void ast_channel_set_manager_vars(size_t varc, char **vars) +{ + size_t i; + + free_channelvars(); + AST_RWLIST_WRLOCK(&channelvars); + for (i = 0; i < varc; ++i) { + const char *var = vars[i]; + struct manager_channel_variable *mcv; + if (!(mcv = ast_calloc(1, sizeof(*mcv) + strlen(var) + 1))) { + break; + } + strcpy(mcv->name, var); /* SAFE */ + if (strchr(var, '(')) { + mcv->isfunc = 1; + } + AST_RWLIST_INSERT_TAIL(&channelvars, mcv, entry); + } + AST_RWLIST_UNLOCK(&channelvars); +} + +/*! + * \brief Destructor for the return value from ast_channel_get_manager_vars(). + * \param obj AO2 object. + */ +static void varshead_dtor(void *obj) +{ + struct varshead *head = obj; + struct ast_var_t *var; + + while ((var = AST_RWLIST_REMOVE_HEAD(head, entries))) { + ast_var_delete(var); + } +} + +struct varshead *ast_channel_get_manager_vars(struct ast_channel *chan) +{ + RAII_VAR(struct varshead *, ret, NULL, ao2_cleanup); + RAII_VAR(struct ast_str *, tmp, NULL, ast_free); + struct manager_channel_variable *mcv; + + ret = ao2_alloc(sizeof(*ret), varshead_dtor); + tmp = ast_str_create(16); + + if (!ret || !tmp) { + return NULL; + } + + AST_RWLIST_RDLOCK(&channelvars); + AST_LIST_TRAVERSE(&channelvars, mcv, entry) { + const char *val = NULL; + struct ast_var_t *var; + + if (mcv->isfunc) { + if (ast_func_read2(chan, mcv->name, &tmp, 0) == 0) { + val = ast_str_buffer(tmp); + } else { + ast_log(LOG_ERROR, + "Error invoking function %s\n", mcv->name); + } + } else { + val = pbx_builtin_getvar_helper(chan, mcv->name); + } + + var = ast_var_assign(mcv->name, val ? val : ""); + if (!var) { + AST_RWLIST_UNLOCK(&channelvars); + return NULL; + } + + AST_RWLIST_INSERT_TAIL(ret, var, entries); + } + AST_RWLIST_UNLOCK(&channelvars); + + ao2_ref(ret, +1); + return ret; +} + static void channels_shutdown(void) { + free_channelvars(); ao2_cleanup(__channel_snapshot); __channel_snapshot = NULL; ao2_cleanup(__channel_blob); @@ -11298,6 +11326,7 @@ static void ast_channel_snapshot_dtor(void *obj) { struct ast_channel_snapshot *snapshot = obj; ast_string_field_free_memory(snapshot); + ao2_cleanup(snapshot->manager_vars); } struct ast_channel_snapshot *ast_channel_snapshot_create(struct ast_channel *chan) @@ -11342,6 +11371,9 @@ struct ast_channel_snapshot *ast_channel_snapshot_create(struct ast_channel *cha snapshot->amaflags = ast_channel_amaflags(chan); snapshot->hangupcause = ast_channel_hangupcause(chan); snapshot->flags = *ast_channel_flags(chan); + snapshot->caller_pres = ast_party_id_presentation(&ast_channel_caller(chan)->id); + + snapshot->manager_vars = ast_channel_get_manager_vars(chan); ao2_ref(snapshot, +1); return snapshot; diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c index 0867524d6f..d635b46a00 100644 --- a/main/channel_internal_api.c +++ b/main/channel_internal_api.c @@ -1385,7 +1385,7 @@ int ast_channel_internal_is_finalized(struct ast_channel *chan) struct stasis_topic *ast_channel_topic(struct ast_channel *chan) { - return chan->topic; + return chan ? chan->topic : ast_channel_topic_all(); } void ast_channel_internal_setup_topics(struct ast_channel *chan) diff --git a/main/manager.c b/main/manager.c index db19290f34..fdc85424b4 100644 --- a/main/manager.c +++ b/main/manager.c @@ -1042,6 +1042,8 @@ static struct ast_event_sub *acl_change_event_subscription; #define MGR_SHOW_TERMINAL_WIDTH 80 +#define MAX_VARS 128 + /*! \brief * Descriptor for a manager session, either on the AMI socket or over HTTP. * @@ -1167,14 +1169,6 @@ struct mansession { static struct ao2_container *sessions = NULL; -struct manager_channel_variable { - AST_LIST_ENTRY(manager_channel_variable) entry; - unsigned int isfunc:1; - char name[0]; /* allocate off the end the real size. */ -}; - -static AST_RWLIST_HEAD_STATIC(channelvars, manager_channel_variable); - /*! \brief user descriptor, as read from the config file. * * \note It is still missing some fields -- e.g. we can have multiple permit and deny @@ -1209,8 +1203,6 @@ static AST_RWLIST_HEAD_STATIC(manager_hooks, manager_custom_hook); /*! \brief A container of event documentation nodes */ AO2_GLOBAL_OBJ_STATIC(event_docs); -static void free_channelvars(void); - static enum add_filter_result manager_add_filter(const char *filter_pattern, struct ao2_container *whitefilters, struct ao2_container *blackfilters); /*! @@ -5650,30 +5642,16 @@ static int append_event(const char *str, int category) return 0; } -AST_THREADSTORAGE(manager_event_funcbuf); - static void append_channel_vars(struct ast_str **pbuf, struct ast_channel *chan) { - struct manager_channel_variable *var; + RAII_VAR(struct varshead *, vars, NULL, ao2_cleanup); + struct ast_var_t *var; - AST_RWLIST_RDLOCK(&channelvars); - AST_LIST_TRAVERSE(&channelvars, var, entry) { - const char *val; - struct ast_str *res; + vars = ast_channel_get_manager_vars(chan); - if (var->isfunc) { - res = ast_str_thread_get(&manager_event_funcbuf, 16); - if (res && ast_func_read2(chan, var->name, &res, 0) == 0) { - val = ast_str_buffer(res); - } else { - val = NULL; - } - } else { - val = pbx_builtin_getvar_helper(chan, var->name); - } - ast_str_append(pbuf, 0, "ChanVariable(%s): %s=%s\r\n", ast_channel_name(chan), var->name, val ? val : ""); + AST_LIST_TRAVERSE(vars, var, entries) { + ast_str_append(pbuf, 0, "ChanVariable(%s): %s=%s\r\n", ast_channel_name(chan), var->name, var->value); } - AST_RWLIST_UNLOCK(&channelvars); } /* XXX see if can be moved inside the function */ @@ -5700,7 +5678,7 @@ int __ast_manager_event_multichan(int category, const char *event, int chancount return -1; } - cat_str = authority_to_str(category, &auth); + cat_str = authority_to_str (category, &auth); ast_str_set(&buf, 0, "Event: %s\r\nPrivilege: %s\r\n", event, cat_str); @@ -7350,31 +7328,19 @@ static struct ast_cli_entry cli_manager[] = { */ static void load_channelvars(struct ast_variable *var) { - struct manager_channel_variable *mcv; - char *remaining = ast_strdupa(var->value); - char *next; + char *parse = NULL; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(vars)[MAX_VARS]; + ); ast_free(manager_channelvars); manager_channelvars = ast_strdup(var->value); - /* - * XXX TODO: To allow dialplan functions to have more than one - * parameter requires eliminating the '|' as a separator so we - * could use AST_STANDARD_APP_ARGS() to separate items. - */ - free_channelvars(); - AST_RWLIST_WRLOCK(&channelvars); - while ((next = strsep(&remaining, ",|"))) { - if (!(mcv = ast_calloc(1, sizeof(*mcv) + strlen(next) + 1))) { - break; - } - strcpy(mcv->name, next); /* SAFE */ - if (strchr(next, '(')) { - mcv->isfunc = 1; - } - AST_RWLIST_INSERT_TAIL(&channelvars, mcv, entry); - } - AST_RWLIST_UNLOCK(&channelvars); + /* parse the setting */ + parse = ast_strdupa(manager_channelvars); + AST_STANDARD_APP_ARGS(args, parse); + + ast_channel_set_manager_vars(args.argc, args.vars); } /*! \internal \brief Free a user record. Should already be removed from the list */ @@ -7595,8 +7561,6 @@ static int __init_manager(int reload, int by_external_config) } ami_tls_cfg.cipher = ast_strdup(""); - free_channelvars(); - for (var = ast_variable_browse(cfg, "general"); var; var = var->next) { val = var->value; @@ -7955,17 +7919,6 @@ static void acl_change_event_cb(const struct ast_event *event, void *userdata) __init_manager(1, 1); } -/* clear out every entry in the channelvar list */ -static void free_channelvars(void) -{ - struct manager_channel_variable *var; - AST_RWLIST_WRLOCK(&channelvars); - while ((var = AST_RWLIST_REMOVE_HEAD(&channelvars, entry))) { - ast_free(var); - } - AST_RWLIST_UNLOCK(&channelvars); -} - int init_manager(void) { return __init_manager(0, 0); diff --git a/main/manager_channels.c b/main/manager_channels.c index 802b341877..29aac4a884 100644 --- a/main/manager_channels.c +++ b/main/manager_channels.c @@ -30,6 +30,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") +#include "asterisk/callerid.h" #include "asterisk/channel.h" #include "asterisk/manager.h" #include "asterisk/stasis_message_router.h" @@ -80,6 +81,22 @@ static struct stasis_message_router *channel_state_router; + + + + + + Raised when a channel's state changes. + + + + + + + + Raised when a channel is hung up. + + A numeric cause code for why the channel was hung up. @@ -89,19 +106,51 @@ static struct stasis_message_router *channel_state_router; - + - Raised when a channel's state changes. + Raised when a hangup is requested. + - + - Raised when a channel is hung up. + Raised when a soft hangup is requested with a specific cause code. + + + + + + + Raised when a channel enters a new context, extension, priority. + + + + Deprecated in 12, but kept for + backward compatability. Please use + 'Exten' instead. + + + The application about to be executed. + + + The data to be passed to the application. + + + + + + + Raised when a channel receives new Caller ID information. + + + + A description of the Caller ID presentation. + @@ -137,9 +186,7 @@ static struct ast_str *manager_build_channel_state_string( "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", + "Uniqueid: %s\r\n", snapshot->name, snapshot->state, ast_state2str(snapshot->state), @@ -151,123 +198,276 @@ static struct ast_str *manager_build_channel_state_string( snapshot->context, snapshot->exten, snapshot->priority, - snapshot->uniqueid, - snapshot->hangupcause, - ast_cause2str(snapshot->hangupcause)); + snapshot->uniqueid); if (!res) { return NULL; } + if (snapshot->manager_vars) { + struct ast_var_t *var; + AST_LIST_TRAVERSE(snapshot->manager_vars, var, entries) { + ast_str_append(&out, 0, "ChanVariable(%s): %s=%s\r\n", + snapshot->name, + var->name, var->value); + } + } + return out; } -static inline int cep_has_changed( - const struct ast_channel_snapshot *old_snapshot, - const struct ast_channel_snapshot *new_snapshot) +/*! \brief Struct containing info for an AMI channel event to send out. */ +struct snapshot_manager_event { + /*! event_flags manager_event() flags parameter. */ + int event_flags; + /*! manager_event manager_event() category. */ + const char *manager_event; + AST_DECLARE_STRING_FIELDS( + /* extra fields to include in the event. */ + AST_STRING_FIELD(extra_fields); + ); +}; + +static void snapshot_manager_event_dtor(void *obj) { - 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; + struct snapshot_manager_event *ev = obj; + ast_string_field_free_memory(ev); } -static void channel_snapshot_update(void *data, struct stasis_subscription *sub, - struct stasis_topic *topic, - struct stasis_message *message) +/*! + * \brief Construct a \ref snapshot_manager_event. + * \param event_flags manager_event() flags parameter. + * \param manager_event manager_event() category. + * \param extra_fields_fmt Format string for extra fields to include. + * Or NO_EXTRA_FIELDS for no extra fields. + * \return New \ref snapshot_manager_event object. + * \return \c NULL on error. + */ +static struct snapshot_manager_event * +__attribute__((format(printf, 3, 4))) +snapshot_manager_event_create( + int event_flags, + const char *manager_event, + const char *extra_fields_fmt, + ...) { - 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; + RAII_VAR(struct snapshot_manager_event *, ev, NULL, ao2_cleanup); + va_list argp; - if (ast_channel_snapshot() != update->type) { - return; + ast_assert(extra_fields_fmt != NULL); + ast_assert(manager_event != NULL); + + ev = ao2_alloc(sizeof(*ev), snapshot_manager_event_dtor); + if (!ev) { + return NULL; } - old_snapshot = stasis_message_data(update->old_snapshot); - new_snapshot = stasis_message_data(update->new_snapshot); + if (ast_string_field_init(ev, 20)) { + return NULL; + } + + ev->manager_event = manager_event; + ev->event_flags = event_flags; + + va_start(argp, extra_fields_fmt); + ast_string_field_ptr_build_va(ev, &ev->extra_fields, extra_fields_fmt, + argp); + va_end(argp); + + ao2_ref(ev, +1); + return ev; +} + +/*! GCC warns about blank or NULL format strings. So, shenanigans! */ +#define NO_EXTRA_FIELDS "%s", "" + +/*! \brief Typedef for callbacks that get called on channel snapshot updates */ +typedef struct snapshot_manager_event *(*snapshot_monitor)( + struct ast_channel_snapshot *old_snapshot, + struct ast_channel_snapshot *new_snapshot); + +/*! \brief Handle channel state changes */ +static struct snapshot_manager_event *channel_state_change( + struct ast_channel_snapshot *old_snapshot, + struct ast_channel_snapshot *new_snapshot) +{ + int is_hungup, was_hungup; if (!new_snapshot) { /* Ignore cache clearing events; we'll see the hangup first */ - return; + return NULL; } - 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; + /* The Newchannel, Newstate and Hangup events are closely related, in + * in that they are mutually exclusive, basically different flavors + * of a new channel state event. + */ if (!old_snapshot) { - manager_event = "Newchannel"; + return snapshot_manager_event_create( + EVENT_FLAG_CALL, "Newchannel", NO_EXTRA_FIELDS); } - if (old_snapshot && old_snapshot->state != new_snapshot->state) { - manager_event = "Newstate"; - } + was_hungup = 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 (!was_hungup && is_hungup) { - manager_event = "Hangup"; + return snapshot_manager_event_create( + EVENT_FLAG_CALL, "Hangup", + "Cause: %d\r\n" + "Cause-txt: %s\r\n", + new_snapshot->hangupcause, + ast_cause2str(new_snapshot->hangupcause)); } - /* 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 + if (old_snapshot->state != new_snapshot->state) { + return snapshot_manager_event_create( + EVENT_FLAG_CALL, "Newstate", NO_EXTRA_FIELDS); + } + + /* No event */ + return NULL; +} + +/*! + * \brief Compares the context, exten and priority of two snapshots. + * \param old_snapshot Old snapshot + * \param new_snapshot New snapshot + * \return True (non-zero) if context, exten or priority are identical. + * \return False (zero) if context, exten and priority changed. + */ +static inline int cep_equal( + const struct ast_channel_snapshot *old_snapshot, + const struct ast_channel_snapshot *new_snapshot) +{ + ast_assert(old_snapshot != NULL); + ast_assert(new_snapshot != NULL); + + /* We actually get some snapshots with CEP set, but before the + * application is set. Since empty application is invalid, we treat + * setting the application from nothing as a CEP change. */ - 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 (ast_strlen_zero(old_snapshot->appl) && + !ast_strlen_zero(new_snapshot->appl)) { + return 0; } - if (!channel_event_string) { - return; + return old_snapshot->priority == new_snapshot->priority && + strcmp(old_snapshot->context, new_snapshot->context) == 0 && + strcmp(old_snapshot->exten, new_snapshot->exten) == 0; +} + +static struct snapshot_manager_event *channel_newexten( + struct ast_channel_snapshot *old_snapshot, + struct ast_channel_snapshot *new_snapshot) +{ + /* No Newexten event on cache clear */ + if (!new_snapshot) { + return NULL; + } + + /* Empty application is not valid for a Newexten event */ + if (ast_strlen_zero(new_snapshot->appl)) { + return NULL; + } + + if (old_snapshot && cep_equal(old_snapshot, new_snapshot)) { + return NULL; + } + + /* DEPRECATED: Extension field deprecated in 12; remove in 14 */ + return snapshot_manager_event_create( + EVENT_FLAG_CALL, "Newexten", + "Extension: %s\r\n" + "Application: %s\r\n" + "AppData: %s\r\n", + new_snapshot->exten, + new_snapshot->appl, + new_snapshot->data); +} + +/*! + * \brief Compares the callerid info of two snapshots. + * \param old_snapshot Old snapshot + * \param new_snapshot New snapshot + * \return True (non-zero) if callerid are identical. + * \return False (zero) if callerid changed. + */ +static inline int caller_id_equal( + 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 strcmp(old_snapshot->caller_number, new_snapshot->caller_number) == 0 && + strcmp(old_snapshot->caller_name, new_snapshot->caller_name) == 0; +} + +static struct snapshot_manager_event *channel_new_callerid( + struct ast_channel_snapshot *old_snapshot, + struct ast_channel_snapshot *new_snapshot) +{ + /* No NewCallerid event on cache clear or first event */ + if (!old_snapshot || !new_snapshot) { + return NULL; + } + + if (caller_id_equal(old_snapshot, new_snapshot)) { + return NULL; } - /* Channel state change events */ - if (manager_event) { - manager_event(EVENT_FLAG_CALL, manager_event, "%s", - ast_str_buffer(channel_event_string)); + return snapshot_manager_event_create( + EVENT_FLAG_CALL, "NewCallerid", + "CID-CallingPres: %d (%s)\r\n", + new_snapshot->caller_pres, + ast_describe_caller_presentation(new_snapshot->caller_pres)); +} + +snapshot_monitor monitors[] = { + channel_state_change, + channel_newexten, + channel_new_callerid +}; + +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; + struct ast_channel_snapshot *old_snapshot; + struct ast_channel_snapshot *new_snapshot; + size_t i; + + update = stasis_message_data(message); + + if (ast_channel_snapshot() != update->type) { + return; } - if (new_exten) { - /* DEPRECATED: Extension field deprecated in 12; remove in 14 */ - /*** DOCUMENTATION - - Raised when a channel enters a new context, extension, priority. - - - - Deprecated in 12, but kept for - backward compatability. Please use - 'Exten' instead. - - - The application about to be executed. - - - The data to be passed to the application. - - - - ***/ - 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); + old_snapshot = stasis_message_data(update->old_snapshot); + new_snapshot = stasis_message_data(update->new_snapshot); + + for (i = 0; i < ARRAY_LEN(monitors); ++i) { + RAII_VAR(struct snapshot_manager_event *, ev, NULL, ao2_cleanup); + ev = monitors[i](old_snapshot, new_snapshot); + + if (!ev) { + continue; + } + + /* If we haven't already, build the channel event string */ + if (!channel_event_string) { + channel_event_string = + manager_build_channel_state_string(new_snapshot); + if (!channel_event_string) { + return; + } + } + + manager_event(ev->event_flags, ev->manager_event, "%s%s", + ast_str_buffer(channel_event_string), + ev->extra_fields); } } @@ -347,6 +547,43 @@ static void channel_userevent(struct ast_channel_blob *obj) ast_str_buffer(channel_event_string), eventname, body); } +static void channel_hangup_request(struct ast_channel_blob *obj) +{ + RAII_VAR(struct ast_str *, extra, NULL, ast_free); + RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free); + struct ast_json *cause; + int is_soft; + char *manager_event = "HangupRequest"; + + extra = ast_str_create(20); + if (!extra) { + return; + } + + channel_event_string = manager_build_channel_state_string(obj->snapshot); + + if (!channel_event_string) { + return; + } + + cause = ast_json_object_get(obj->blob, "cause"); + if (cause) { + ast_str_append(&extra, 0, + "Cause: %jd\r\n", + ast_json_integer_get(cause)); + } + + is_soft = ast_json_is_true(ast_json_object_get(obj->blob, "soft")); + if (is_soft) { + manager_event = "SoftHangupRequest"; + } + + manager_event(EVENT_FLAG_CALL, manager_event, + "%s%s", + ast_str_buffer(channel_event_string), + ast_str_buffer(extra)); +} + /*! * \brief Callback processing messages on the channel topic. */ @@ -360,6 +597,8 @@ static void channel_blob_cb(void *data, struct stasis_subscription *sub, channel_varset(obj); } else if (strcmp("userevent", ast_channel_blob_type(obj)) == 0) { channel_userevent(obj); + } else if (strcmp("hangup_request", ast_channel_blob_type(obj)) == 0) { + channel_hangup_request(obj); } }