/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2013, Digium, Inc. * * David M. Lee, II * * 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 * * 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" #include "asterisk/callerid.h" #include "asterisk/channel.h" #include "asterisk/manager.h" #include "asterisk/stasis_message_router.h" #include "asterisk/pbx.h" #include "asterisk/stasis_channels.h" /*** DOCUMENTATION Raised when a new channel is created. Newstate Hangup Raised when a channel's state changes. Newchannel Hangup Raised when a channel is hung up. A numeric cause code for why the channel was hung up. A description of why the channel was hung up. Newchannel SoftHangupRequest HangupRequest Newstate Raised when a hangup is requested. SoftHangupRequest Hangup Raised when a soft hangup is requested with a specific cause code. HangupRequest Hangup 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. CALLERID Raised when a channel's connected line information is changed. CONNECTEDLINE Raised when a Channel's AccountCode is changed. The channel's previous account code CHANNEL Raised when a dial action has started. The non-technology specific device being dialed. Dial Originate Originate DialEnd Raised when dial status has changed. The new state of the outbound dial attempt. The outbound channel is ringing. The call to the outbound channel is proceeding. Progress has been received on the outbound channel. If the call was forwarded, where the call was forwarded to. Raised when a dial action has completed. The result of the dial operation. The call was aborted. The caller answered. The caller was busy. The caller cancelled the call. The requested channel is unavailable. The called party is congested. The dial completed, but the caller elected to continue in the dialplan. The dial completed, but the caller jumped to a dialplan location. If known, the location the caller is jumping to will be appended to the result following a ":". The called party failed to answer. If the call was forwarded, where the call was forwarded to. Dial Originate Originate DialBegin Raised when a channel goes on hold. The suggested MusicClass, if provided. Unhold Raised when a channel goes off hold. Hold Raised when one channel begins spying on another channel. ChanSpyStop ChanSpy Raised when a channel has stopped spying. ChanSpyStart ChanSpy Raised when a hangup handler is about to be called. Hangup handler parameter string passed to the Gosub application. CHANNEL Raised when a hangup handler is removed from the handler stack by the CHANNEL() function. HangupHandlerPush CHANNEL Raised when a hangup handler is added to the handler stack by the CHANNEL() function. HangupHandlerPop CHANNEL Raised periodically during a fax transmission. A text message describing the current status of the fax Raised when a receive fax operation has completed. The value of the LOCALSTATIONID channel variable The value of the REMOTESTATIONID channel variable The number of pages that have been transferred The negotiated resolution The negotiated transfer rate The files being affected by the fax operation Raised when a send fax operation has completed. Raised when music on hold has started on a channel. The class of music being played on the channel MusicOnHoldStop StartMusicOnHold MusicOnHold Raised when music on hold has stopped on a channel. MusicOnHoldStart StopMusicOnHold Raised when monitoring has started on a channel. MonitorStop Monitor Monitor Raised when monitoring has stopped on a channel. MonitorStart StopMonitor StopMonitor ***/ /*! \brief The \ref stasis subscription returned by the forwarding of the channel topic * to the manager topic */ static struct stasis_forward *topic_forwarder; struct ast_str *ast_manager_build_channel_state_string_prefix( const struct ast_channel_snapshot *snapshot, const char *prefix) { struct ast_str *out; char *caller_name; char *connected_name; int res; if (snapshot->tech_properties & AST_CHAN_TP_INTERNAL) { return NULL; } out = ast_str_create(1024); if (!out) { return NULL; } caller_name = ast_escape_c_alloc(snapshot->caller_name); connected_name = ast_escape_c_alloc(snapshot->connected_name); res = ast_str_set(&out, 0, "%sChannel: %s\r\n" "%sChannelState: %u\r\n" "%sChannelStateDesc: %s\r\n" "%sCallerIDNum: %s\r\n" "%sCallerIDName: %s\r\n" "%sConnectedLineNum: %s\r\n" "%sConnectedLineName: %s\r\n" "%sLanguage: %s\r\n" "%sAccountCode: %s\r\n" "%sContext: %s\r\n" "%sExten: %s\r\n" "%sPriority: %d\r\n" "%sUniqueid: %s\r\n" "%sLinkedid: %s\r\n", prefix, snapshot->name, prefix, snapshot->state, prefix, ast_state2str(snapshot->state), prefix, S_OR(snapshot->caller_number, ""), prefix, S_OR(caller_name, ""), prefix, S_OR(snapshot->connected_number, ""), prefix, S_OR(connected_name, ""), prefix, snapshot->language, prefix, snapshot->accountcode, prefix, snapshot->context, prefix, snapshot->exten, prefix, snapshot->priority, prefix, snapshot->uniqueid, prefix, snapshot->linkedid); ast_free(caller_name); ast_free(connected_name); if (!res) { ast_free(out); return NULL; } if (snapshot->manager_vars) { struct ast_var_t *var; char *val; AST_LIST_TRAVERSE(snapshot->manager_vars, var, entries) { val = ast_escape_c_alloc(var->value); ast_str_append(&out, 0, "%sChanVariable: %s=%s\r\n", prefix, var->name, S_OR(val, "")); ast_free(val); } } return out; } struct ast_str *ast_manager_build_channel_state_string( const struct ast_channel_snapshot *snapshot) { return ast_manager_build_channel_state_string_prefix(snapshot, ""); } /*! \brief Typedef for callbacks that get called on channel snapshot updates */ typedef struct ast_manager_event_blob *(*channel_snapshot_monitor)( struct ast_channel_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot); /*! \brief Handle channel state changes */ static struct ast_manager_event_blob *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 NULL; } /* 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) { return ast_manager_event_blob_create( EVENT_FLAG_CALL, "Newchannel", NO_EXTRA_FIELDS); } was_hungup = ast_test_flag(&old_snapshot->flags, AST_FLAG_DEAD) ? 1 : 0; is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_DEAD) ? 1 : 0; if (!was_hungup && is_hungup) { return ast_manager_event_blob_create( EVENT_FLAG_CALL, "Hangup", "Cause: %d\r\n" "Cause-txt: %s\r\n", new_snapshot->hangupcause, ast_cause2str(new_snapshot->hangupcause)); } if (old_snapshot->state != new_snapshot->state) { return ast_manager_event_blob_create( EVENT_FLAG_CALL, "Newstate", NO_EXTRA_FIELDS); } /* No event */ return NULL; } static struct ast_manager_event_blob *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; } /* Ignore any updates if we're hungup */ if (ast_test_flag(&new_snapshot->flags, AST_FLAG_DEAD)) { return NULL; } /* Ignore updates if the CEP is unchanged */ if (old_snapshot && ast_channel_snapshot_cep_equal(old_snapshot, new_snapshot)) { return NULL; } /* DEPRECATED: Extension field deprecated in 12; remove in 14 */ return ast_manager_event_blob_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); } static struct ast_manager_event_blob *channel_new_callerid( struct ast_channel_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot) { struct ast_manager_event_blob *res; char *callerid; /* No NewCallerid event on cache clear or first event */ if (!old_snapshot || !new_snapshot) { return NULL; } if (ast_channel_snapshot_caller_id_equal(old_snapshot, new_snapshot)) { return NULL; } if (!(callerid = ast_escape_c_alloc( ast_describe_caller_presentation(new_snapshot->caller_pres)))) { return NULL; } res = ast_manager_event_blob_create( EVENT_FLAG_CALL, "NewCallerid", "CID-CallingPres: %d (%s)\r\n", new_snapshot->caller_pres, callerid); ast_free(callerid); return res; } static struct ast_manager_event_blob *channel_new_connected_line( struct ast_channel_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot) { /* No NewConnectedLine event on cache clear or first event */ if (!old_snapshot || !new_snapshot) { return NULL; } if (ast_channel_snapshot_connected_line_equal(old_snapshot, new_snapshot)) { return NULL; } return ast_manager_event_blob_create( EVENT_FLAG_CALL, "NewConnectedLine", "%s", ""); } static struct ast_manager_event_blob *channel_new_accountcode( struct ast_channel_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot) { if (!old_snapshot || !new_snapshot) { return NULL; } if (!strcmp(old_snapshot->accountcode, new_snapshot->accountcode)) { return NULL; } return ast_manager_event_blob_create( EVENT_FLAG_CALL, "NewAccountCode", "OldAccountCode: %s\r\n", old_snapshot->accountcode); } channel_snapshot_monitor channel_monitors[] = { channel_state_change, channel_newexten, channel_new_callerid, channel_new_accountcode, channel_new_connected_line, }; static void channel_snapshot_update(void *data, struct stasis_subscription *sub, 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); ast_assert(ast_channel_snapshot_type() == update->type); old_snapshot = stasis_message_data(update->old_snapshot); new_snapshot = stasis_message_data(update->new_snapshot); for (i = 0; i < ARRAY_LEN(channel_monitors); ++i) { RAII_VAR(struct ast_manager_event_blob *, ev, NULL, ao2_cleanup); ev = channel_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 = ast_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); } } static void publish_basic_channel_event(const char *event, int class, struct ast_channel_snapshot *snapshot) { RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free); channel_event_string = ast_manager_build_channel_state_string(snapshot); if (!channel_event_string) { return; } manager_event(class, event, "%s", ast_str_buffer(channel_event_string)); } static void channel_hangup_request_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct ast_channel_blob *obj = stasis_message_data(message); struct ast_str *extra; struct ast_str *channel_event_string; struct ast_json *cause; int is_soft; char *manager_event = "HangupRequest"; if (!obj->snapshot) { /* No snapshot? Likely an earlier allocation failure creating it. */ return; } extra = ast_str_create(20); if (!extra) { return; } channel_event_string = ast_manager_build_channel_state_string(obj->snapshot); if (!channel_event_string) { ast_free(extra); 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)); ast_free(channel_event_string); ast_free(extra); } static void channel_chanspy_stop_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { RAII_VAR(struct ast_str *, spyer_channel_string, NULL, ast_free); struct ast_channel_snapshot *spyer; struct ast_multi_channel_blob *payload = stasis_message_data(message); spyer = ast_multi_channel_blob_get_channel(payload, "spyer_channel"); if (!spyer) { ast_log(AST_LOG_WARNING, "Received ChanSpy Start event with no spyer channel!\n"); return; } spyer_channel_string = ast_manager_build_channel_state_string_prefix(spyer, "Spyer"); if (!spyer_channel_string) { return; } manager_event(EVENT_FLAG_CALL, "ChanSpyStop", "%s", ast_str_buffer(spyer_channel_string)); } static void channel_chanspy_start_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { RAII_VAR(struct ast_str *, spyer_channel_string, NULL, ast_free); RAII_VAR(struct ast_str *, spyee_channel_string, NULL, ast_free); struct ast_channel_snapshot *spyer; struct ast_channel_snapshot *spyee; struct ast_multi_channel_blob *payload = stasis_message_data(message); spyer = ast_multi_channel_blob_get_channel(payload, "spyer_channel"); if (!spyer) { ast_log(AST_LOG_WARNING, "Received ChanSpy Start event with no spyer channel!\n"); return; } spyee = ast_multi_channel_blob_get_channel(payload, "spyee_channel"); if (!spyee) { ast_log(AST_LOG_WARNING, "Received ChanSpy Start event with no spyee channel!\n"); return; } spyer_channel_string = ast_manager_build_channel_state_string_prefix(spyer, "Spyer"); if (!spyer_channel_string) { return; } spyee_channel_string = ast_manager_build_channel_state_string_prefix(spyee, "Spyee"); if (!spyee_channel_string) { return; } manager_event(EVENT_FLAG_CALL, "ChanSpyStart", "%s%s", ast_str_buffer(spyer_channel_string), ast_str_buffer(spyee_channel_string)); } static void channel_dtmf_begin_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct ast_channel_blob *obj = stasis_message_data(message); RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free); const char *digit = ast_json_string_get(ast_json_object_get(obj->blob, "digit")); const char *direction = ast_json_string_get(ast_json_object_get(obj->blob, "direction")); channel_event_string = ast_manager_build_channel_state_string(obj->snapshot); if (!channel_event_string) { return; } /*** DOCUMENTATION Raised when a DTMF digit has started on a channel. DTMF digit received or transmitted (0-9, A-E, # or * DTMFEnd ***/ manager_event(EVENT_FLAG_DTMF, "DTMFBegin", "%s" "Digit: %s\r\n" "Direction: %s\r\n", ast_str_buffer(channel_event_string), digit, direction); } static void channel_dtmf_end_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct ast_channel_blob *obj = stasis_message_data(message); RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free); const char *digit = ast_json_string_get(ast_json_object_get(obj->blob, "digit")); const char *direction = ast_json_string_get(ast_json_object_get(obj->blob, "direction")); long duration_ms = ast_json_integer_get(ast_json_object_get(obj->blob, "duration_ms")); channel_event_string = ast_manager_build_channel_state_string(obj->snapshot); if (!channel_event_string) { return; } /*** DOCUMENTATION Raised when a DTMF digit has ended on a channel. DTMF digit received or transmitted (0-9, A-E, # or * Duration (in milliseconds) DTMF was sent/received DTMFBegin ***/ manager_event(EVENT_FLAG_DTMF, "DTMFEnd", "%s" "Digit: %s\r\n" "DurationMs: %ld\r\n" "Direction: %s\r\n", ast_str_buffer(channel_event_string), digit, duration_ms, direction); } static void channel_hangup_handler_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free); struct ast_channel_blob *payload = stasis_message_data(message); const char *action = ast_json_string_get(ast_json_object_get(payload->blob, "type")); const char *handler = ast_json_string_get(ast_json_object_get(payload->blob, "handler")); const char *event; channel_event_string = ast_manager_build_channel_state_string(payload->snapshot); if (!channel_event_string) { return; } if (!strcmp(action, "type")) { event = "HangupHandlerRun"; } else if (!strcmp(action, "type")) { event = "HangupHandlerPop"; } else if (!strcmp(action, "type")) { event = "HangupHandlerPush"; } else { return; } manager_event(EVENT_FLAG_DIALPLAN, event, "%s" "Handler: %s\r\n", ast_str_buffer(channel_event_string), handler); } static void channel_fax_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free); RAII_VAR(struct ast_str *, event_buffer, ast_str_create(256), ast_free); struct ast_channel_blob *payload = stasis_message_data(message); const char *type = ast_json_string_get(ast_json_object_get(payload->blob, "type")); struct ast_json *operation = ast_json_object_get(payload->blob, "operation"); struct ast_json *status = ast_json_object_get(payload->blob, "status"); struct ast_json *local_station_id = ast_json_object_get(payload->blob, "local_station_id"); struct ast_json *remote_station_id = ast_json_object_get(payload->blob, "remote_station_id"); struct ast_json *fax_pages = ast_json_object_get(payload->blob, "fax_pages"); struct ast_json *fax_resolution = ast_json_object_get(payload->blob, "fax_resolution"); struct ast_json *fax_bitrate = ast_json_object_get(payload->blob, "fax_bitrate"); struct ast_json *filenames = ast_json_object_get(payload->blob, "filenames"); const char *event; size_t array_len; size_t i; if (!event_buffer) { return; } channel_event_string = ast_manager_build_channel_state_string(payload->snapshot); if (!channel_event_string) { return; } if (!strcmp(type, "status")) { event = "FAXStatus"; } else if (!strcmp(type, "receive")) { event = "ReceiveFAX"; } else if (!strcmp(type, "send")) { event = "SendFAX"; } else { return; } if (operation) { ast_str_append(&event_buffer, 0, "Operation: %s\r\n", ast_json_string_get(operation)); } if (status) { ast_str_append(&event_buffer, 0, "Status: %s\r\n", ast_json_string_get(status)); } if (local_station_id) { ast_str_append(&event_buffer, 0, "LocalStationID: %s\r\n", ast_json_string_get(local_station_id)); } if (remote_station_id) { ast_str_append(&event_buffer, 0, "RemoteStationID: %s\r\n", ast_json_string_get(remote_station_id)); } if (fax_pages) { ast_str_append(&event_buffer, 0, "PagesTransferred: %s\r\n", ast_json_string_get(fax_pages)); } if (fax_resolution) { ast_str_append(&event_buffer, 0, "Resolution: %s\r\n", ast_json_string_get(fax_resolution)); } if (fax_bitrate) { ast_str_append(&event_buffer, 0, "TransferRate: %s\r\n", ast_json_string_get(fax_bitrate)); } if (filenames) { array_len = ast_json_array_size(filenames); for (i = 0; i < array_len; i++) { ast_str_append(&event_buffer, 0, "FileName: %s\r\n", ast_json_string_get(ast_json_array_get(filenames, i))); } } manager_event(EVENT_FLAG_CALL, event, "%s" "%s", ast_str_buffer(channel_event_string), ast_str_buffer(event_buffer)); } static void channel_moh_start_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct ast_channel_blob *payload = stasis_message_data(message); struct ast_json *blob = payload->blob; RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free); channel_event_string = ast_manager_build_channel_state_string(payload->snapshot); if (!channel_event_string) { return; } manager_event(EVENT_FLAG_CALL, "MusicOnHoldStart", "%s" "Class: %s\r\n", ast_str_buffer(channel_event_string), ast_json_string_get(ast_json_object_get(blob, "class"))); } static void channel_moh_stop_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct ast_channel_blob *payload = stasis_message_data(message); publish_basic_channel_event("MusicOnHoldStop", EVENT_FLAG_CALL, payload->snapshot); } static void channel_monitor_start_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct ast_channel_blob *payload = stasis_message_data(message); publish_basic_channel_event("MonitorStart", EVENT_FLAG_CALL, payload->snapshot); } static void channel_monitor_stop_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct ast_channel_blob *payload = stasis_message_data(message); publish_basic_channel_event("MonitorStop", EVENT_FLAG_CALL, payload->snapshot); } static int dial_status_end(const char *dialstatus) { return (strcmp(dialstatus, "RINGING") && strcmp(dialstatus, "PROCEEDING") && strcmp(dialstatus, "PROGRESS")); } /*! * \brief Callback processing messages for channel dialing */ static void channel_dial_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct ast_multi_channel_blob *obj = stasis_message_data(message); const char *dialstatus; const char *dialstring; const char *forward; struct ast_channel_snapshot *caller; struct ast_channel_snapshot *peer; RAII_VAR(struct ast_str *, caller_event_string, NULL, ast_free); RAII_VAR(struct ast_str *, peer_event_string, NULL, ast_free); caller = ast_multi_channel_blob_get_channel(obj, "caller"); peer = ast_multi_channel_blob_get_channel(obj, "peer"); /* Peer is required - otherwise, who are we dialing? */ ast_assert(peer != NULL); peer_event_string = ast_manager_build_channel_state_string_prefix(peer, "Dest"); if (!peer_event_string) { return; } if (caller && !(caller_event_string = ast_manager_build_channel_state_string(caller))) { return; } dialstatus = ast_json_string_get(ast_json_object_get(ast_multi_channel_blob_get_json(obj), "dialstatus")); dialstring = ast_json_string_get(ast_json_object_get(ast_multi_channel_blob_get_json(obj), "dialstring")); forward = ast_json_string_get(ast_json_object_get(ast_multi_channel_blob_get_json(obj), "forward")); if (ast_strlen_zero(dialstatus)) { manager_event(EVENT_FLAG_CALL, "DialBegin", "%s" "%s" "DialString: %s\r\n", caller_event_string ? ast_str_buffer(caller_event_string) : "", ast_str_buffer(peer_event_string), S_OR(dialstring, "unknown")); } else { int forwarded = !ast_strlen_zero(forward); manager_event(EVENT_FLAG_CALL, dial_status_end(dialstatus) ? "DialEnd" : "DialState", "%s" "%s" "%s%s%s" "DialStatus: %s\r\n", caller_event_string ? ast_str_buffer(caller_event_string) : "", ast_str_buffer(peer_event_string), forwarded ? "Forward: " : "", S_OR(forward, ""), forwarded ? "\r\n" : "", S_OR(dialstatus, "unknown")); } } static void channel_hold_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct ast_channel_blob *obj = stasis_message_data(message); struct ast_str *musicclass_string = ast_str_create(32); struct ast_str *channel_event_string; if (!musicclass_string) { return; } channel_event_string = ast_manager_build_channel_state_string(obj->snapshot); if (!channel_event_string) { ast_free(musicclass_string); return; } if (obj->blob) { const char *musicclass; musicclass = ast_json_string_get(ast_json_object_get(obj->blob, "musicclass")); if (!ast_strlen_zero(musicclass)) { ast_str_set(&musicclass_string, 0, "MusicClass: %s\r\n", musicclass); } } manager_event(EVENT_FLAG_CALL, "Hold", "%s" "%s", ast_str_buffer(channel_event_string), ast_str_buffer(musicclass_string)); ast_free(musicclass_string); ast_free(channel_event_string); } static void channel_unhold_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { struct ast_channel_blob *obj = stasis_message_data(message); struct ast_str *channel_event_string; channel_event_string = ast_manager_build_channel_state_string(obj->snapshot); if (!channel_event_string) { return; } manager_event(EVENT_FLAG_CALL, "Unhold", "%s", ast_str_buffer(channel_event_string)); ast_free(channel_event_string); } static void manager_channels_shutdown(void) { stasis_forward_cancel(topic_forwarder); topic_forwarder = NULL; } int manager_channels_init(void) { int ret = 0; struct stasis_topic *manager_topic; struct stasis_topic *channel_topic; struct stasis_message_router *message_router; manager_topic = ast_manager_get_topic(); if (!manager_topic) { return -1; } message_router = ast_manager_get_message_router(); if (!message_router) { return -1; } channel_topic = ast_channel_topic_all_cached(); if (!channel_topic) { return -1; } topic_forwarder = stasis_forward_all(channel_topic, manager_topic); if (!topic_forwarder) { return -1; } ast_register_cleanup(manager_channels_shutdown); ret |= stasis_message_router_add_cache_update(message_router, ast_channel_snapshot_type(), channel_snapshot_update, NULL); ret |= stasis_message_router_add(message_router, ast_channel_dtmf_begin_type(), channel_dtmf_begin_cb, NULL); ret |= stasis_message_router_add(message_router, ast_channel_dtmf_end_type(), channel_dtmf_end_cb, NULL); ret |= stasis_message_router_add(message_router, ast_channel_hangup_request_type(), channel_hangup_request_cb, NULL); ret |= stasis_message_router_add(message_router, ast_channel_dial_type(), channel_dial_cb, NULL); ret |= stasis_message_router_add(message_router, ast_channel_hold_type(), channel_hold_cb, NULL); ret |= stasis_message_router_add(message_router, ast_channel_unhold_type(), channel_unhold_cb, NULL); ret |= stasis_message_router_add(message_router, ast_channel_fax_type(), channel_fax_cb, NULL); ret |= stasis_message_router_add(message_router, ast_channel_chanspy_start_type(), channel_chanspy_start_cb, NULL); ret |= stasis_message_router_add(message_router, ast_channel_chanspy_stop_type(), channel_chanspy_stop_cb, NULL); ret |= stasis_message_router_add(message_router, ast_channel_hangup_handler_type(), channel_hangup_handler_cb, NULL); ret |= stasis_message_router_add(message_router, ast_channel_moh_start_type(), channel_moh_start_cb, NULL); ret |= stasis_message_router_add(message_router, ast_channel_moh_stop_type(), channel_moh_stop_cb, NULL); ret |= stasis_message_router_add(message_router, ast_channel_monitor_start_type(), channel_monitor_start_cb, NULL); ret |= stasis_message_router_add(message_router, ast_channel_monitor_stop_type(), channel_monitor_stop_cb, NULL); /* If somehow we failed to add any routes, just shut down the whole * thing and fail it. */ if (ret) { manager_channels_shutdown(); return -1; } return 0; }