diff --git a/apps/app_dial.c b/apps/app_dial.c index c9bee19b0ba4bd6cd40e587dda5e7ddd44261d95..fdbe05692d3303d4f0df87f4a44b162143847b54 100644 --- a/apps/app_dial.c +++ b/apps/app_dial.c @@ -1007,7 +1007,7 @@ static void do_forward(struct chanlist *o, struct cause_args *num, ast_channel_unlock(c); ast_channel_lock_both(original, in); - ast_channel_publish_dial_forward(in, original, NULL, "CANCEL", + ast_channel_publish_dial_forward(in, original, c, NULL, "CANCEL", ast_channel_call_forward(c)); ast_channel_unlock(in); ast_channel_unlock(original); diff --git a/apps/app_queue.c b/apps/app_queue.c index 906dff15fc65ad600966cfcc96d2836ee2bc0138..d9f0f85d3f500ca7afa50957fe5922f11c14c661 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -4666,7 +4666,7 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte ast_channel_unlock(qe->chan); ast_channel_lock_both(qe->chan, original); - ast_channel_publish_dial_forward(qe->chan, original, NULL, "CANCEL", + ast_channel_publish_dial_forward(qe->chan, original, o->chan, NULL, "CANCEL", ast_channel_call_forward(original)); ast_channel_unlock(original); ast_channel_unlock(qe->chan); diff --git a/include/asterisk/stasis_channels.h b/include/asterisk/stasis_channels.h index 519a4b676e17c05715f1d0d1a7abcfead47c6fbe..8c27803dfd8a18c6b5b95ec965039cc13fcd69aa 100644 --- a/include/asterisk/stasis_channels.h +++ b/include/asterisk/stasis_channels.h @@ -518,12 +518,14 @@ void ast_channel_publish_dial(struct ast_channel *caller, * * \param caller The channel performing the dial operation * \param peer The channel being dialed + * \param forwarded The channel created as a result of the call forwarding * \param dialstring The information passed to the dialing application when beginning a dial * \param dialstatus The current status of the dial operation * \param forward The call forward string provided by the dialed channel */ void ast_channel_publish_dial_forward(struct ast_channel *caller, struct ast_channel *peer, + struct ast_channel *forwarded, const char *dialstring, const char *dialstatus, const char *forward); diff --git a/main/dial.c b/main/dial.c index 134386735af3c1b3a5b69133e7f6c28726028e51..ca0b9c8d1724b45818c0d912db648c5224fc7cbf 100644 --- a/main/dial.c +++ b/main/dial.c @@ -465,14 +465,17 @@ static int handle_call_forward(struct ast_dial *dial, struct ast_dial_channel *c channel->device = ast_strdup(device); AST_LIST_UNLOCK(&dial->channels); - /* Drop the original channel */ - ast_hangup(original); channel->owner = NULL; /* Finally give it a go... send it out into the world */ begin_dial_channel(channel, chan, chan ? 0 : 1, predial_string); + ast_channel_publish_dial_forward(chan, original, channel->owner, NULL, "CANCEL", + ast_channel_call_forward(original)); + + ast_hangup(original); + return 0; } diff --git a/main/stasis_channels.c b/main/stasis_channels.c index 38aac982e328d75a6fefd0127a7ba76583deb9c0..8a39bdfcb27e0afcd9e851860a06f3c33459fe4f 100644 --- a/main/stasis_channels.c +++ b/main/stasis_channels.c @@ -287,14 +287,21 @@ static void channel_blob_dtor(void *obj) ast_json_unref(event->blob); } +/*! \brief Dummy callback for receiving events */ +static void dummy_event_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) +{ +} + void ast_channel_publish_dial_forward(struct ast_channel *caller, struct ast_channel *peer, - const char *dialstring, const char *dialstatus, const char *forward) + struct ast_channel *forwarded, const char *dialstring, const char *dialstatus, + const char *forward) { RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup); RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); RAII_VAR(struct ast_channel_snapshot *, caller_snapshot, NULL, ao2_cleanup); RAII_VAR(struct ast_channel_snapshot *, peer_snapshot, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel_snapshot *, forwarded_snapshot, NULL, ao2_cleanup); ast_assert(peer != NULL); blob = ast_json_pack("{s: s, s: s, s: s}", @@ -323,18 +330,33 @@ void ast_channel_publish_dial_forward(struct ast_channel *caller, struct ast_cha } ast_multi_channel_blob_add_channel(payload, "peer", peer_snapshot); + if (forwarded) { + forwarded_snapshot = ast_channel_snapshot_create(forwarded); + if (!forwarded_snapshot) { + return; + } + ast_multi_channel_blob_add_channel(payload, "forwarded", forwarded_snapshot); + } + msg = stasis_message_create(ast_channel_dial_type(), payload); if (!msg) { return; } - publish_message_for_channel_topics(msg, caller); + if (forwarded) { + struct stasis_subscription *subscription = stasis_subscribe(ast_channel_topic(peer), dummy_event_cb, NULL); + + stasis_publish(ast_channel_topic(peer), msg); + stasis_unsubscribe_and_join(subscription); + } else { + publish_message_for_channel_topics(msg, caller); + } } void ast_channel_publish_dial(struct ast_channel *caller, struct ast_channel *peer, const char *dialstring, const char *dialstatus) { - ast_channel_publish_dial_forward(caller, peer, dialstring, dialstatus, NULL); + ast_channel_publish_dial_forward(caller, peer, NULL, dialstring, dialstatus, NULL); } static struct stasis_message *create_channel_blob_message(struct ast_channel_snapshot *snapshot, @@ -931,11 +953,54 @@ static struct ast_json *hangup_request_to_json( return channel_blob_to_json(message, "ChannelHangupRequest", sanitize); } +static struct ast_json *dial_to_json( + struct stasis_message *message, + const struct stasis_message_sanitizer *sanitize) +{ + struct ast_multi_channel_blob *payload = stasis_message_data(message); + struct ast_json *blob = ast_multi_channel_blob_get_json(payload); + struct ast_json *caller_json = ast_channel_snapshot_to_json(ast_multi_channel_blob_get_channel(payload, "caller"), sanitize); + struct ast_json *peer_json = ast_channel_snapshot_to_json(ast_multi_channel_blob_get_channel(payload, "peer"), sanitize); + struct ast_json *forwarded_json = ast_channel_snapshot_to_json(ast_multi_channel_blob_get_channel(payload, "forwarded"), sanitize); + struct ast_json *json; + const struct timeval *tv = stasis_message_timestamp(message); + int res = 0; + + json = ast_json_pack("{s: s, s: o, s: O, s: O, s: O}", + "type", "Dial", + "timestamp", ast_json_timeval(*tv, NULL), + "dialstatus", ast_json_object_get(blob, "dialstatus"), + "forward", ast_json_object_get(blob, "forward"), + "dialstring", ast_json_object_get(blob, "dialstring")); + if (!json) { + return NULL; + } + + if (caller_json) { + res |= ast_json_object_set(json, "caller", caller_json); + } + if (peer_json) { + res |= ast_json_object_set(json, "peer", peer_json); + } + if (forwarded_json) { + res |= ast_json_object_set(json, "forwarded", forwarded_json); + } + + if (res) { + ast_json_unref(json); + return NULL; + } + + return json; +} + /*! * @{ \brief Define channel message types. */ STASIS_MESSAGE_TYPE_DEFN(ast_channel_snapshot_type); -STASIS_MESSAGE_TYPE_DEFN(ast_channel_dial_type); +STASIS_MESSAGE_TYPE_DEFN(ast_channel_dial_type, + .to_json = dial_to_json, + ); STASIS_MESSAGE_TYPE_DEFN(ast_channel_varset_type, .to_ami = varset_to_ami, .to_json = varset_to_json, diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c index 7ddef278dfd877f438807e3b2c751b4b26771cf2..d99240bd8681e8cdab281e98089766721a159e2e 100644 --- a/res/ari/ari_model_validators.c +++ b/res/ari/ari_model_validators.c @@ -2879,6 +2879,137 @@ ari_validator ast_ari_validate_device_state_changed_fn(void) return ast_ari_validate_device_state_changed; } +int ast_ari_validate_dial(struct ast_json *json) +{ + int res = 1; + struct ast_json_iter *iter; + int has_type = 0; + int has_application = 0; + int has_dialstatus = 0; + int has_peer = 0; + + for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) { + if (strcmp("type", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_type = 1; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI Dial field type failed validation\n"); + res = 0; + } + } else + if (strcmp("application", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_application = 1; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI Dial field application failed validation\n"); + res = 0; + } + } else + if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + prop_is_valid = ast_ari_validate_date( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI Dial field timestamp failed validation\n"); + res = 0; + } + } else + if (strcmp("caller", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + prop_is_valid = ast_ari_validate_channel( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI Dial field caller failed validation\n"); + res = 0; + } + } else + if (strcmp("dialstatus", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_dialstatus = 1; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI Dial field dialstatus failed validation\n"); + res = 0; + } + } else + if (strcmp("dialstring", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI Dial field dialstring failed validation\n"); + res = 0; + } + } else + if (strcmp("forward", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI Dial field forward failed validation\n"); + res = 0; + } + } else + if (strcmp("forwarded", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + prop_is_valid = ast_ari_validate_channel( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI Dial field forwarded failed validation\n"); + res = 0; + } + } else + if (strcmp("peer", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_peer = 1; + prop_is_valid = ast_ari_validate_channel( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI Dial field peer failed validation\n"); + res = 0; + } + } else + { + ast_log(LOG_ERROR, + "ARI Dial has undocumented field %s\n", + ast_json_object_iter_key(iter)); + res = 0; + } + } + + if (!has_type) { + ast_log(LOG_ERROR, "ARI Dial missing required field type\n"); + res = 0; + } + + if (!has_application) { + ast_log(LOG_ERROR, "ARI Dial missing required field application\n"); + res = 0; + } + + if (!has_dialstatus) { + ast_log(LOG_ERROR, "ARI Dial missing required field dialstatus\n"); + res = 0; + } + + if (!has_peer) { + ast_log(LOG_ERROR, "ARI Dial missing required field peer\n"); + res = 0; + } + + return res; +} + +ari_validator ast_ari_validate_dial_fn(void) +{ + return ast_ari_validate_dial; +} + int ast_ari_validate_endpoint_state_change(struct ast_json *json) { int res = 1; @@ -3023,6 +3154,9 @@ int ast_ari_validate_event(struct ast_json *json) if (strcmp("DeviceStateChanged", discriminator) == 0) { return ast_ari_validate_device_state_changed(json); } else + if (strcmp("Dial", discriminator) == 0) { + return ast_ari_validate_dial(json); + } else if (strcmp("EndpointStateChange", discriminator) == 0) { return ast_ari_validate_endpoint_state_change(json); } else @@ -3173,6 +3307,9 @@ int ast_ari_validate_message(struct ast_json *json) if (strcmp("DeviceStateChanged", discriminator) == 0) { return ast_ari_validate_device_state_changed(json); } else + if (strcmp("Dial", discriminator) == 0) { + return ast_ari_validate_dial(json); + } else if (strcmp("EndpointStateChange", discriminator) == 0) { return ast_ari_validate_endpoint_state_change(json); } else diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h index 1f9420cb0a9ee75c8205eedef1fd758e3a743891..22ab43be8990274ebde7895b581db03eeb14a1f4 100644 --- a/res/ari/ari_model_validators.h +++ b/res/ari/ari_model_validators.h @@ -790,6 +790,24 @@ int ast_ari_validate_device_state_changed(struct ast_json *json); */ ari_validator ast_ari_validate_device_state_changed_fn(void); +/*! + * \brief Validator for Dial. + * + * Dialing state has changed. + * + * \param json JSON object to validate. + * \returns True (non-zero) if valid. + * \returns False (zero) if invalid. + */ +int ast_ari_validate_dial(struct ast_json *json); + +/*! + * \brief Function pointer to ast_ari_validate_dial(). + * + * See \ref ast_ari_model_validators.h for more details. + */ +ari_validator ast_ari_validate_dial_fn(void); + /*! * \brief Validator for EndpointStateChange. * @@ -1187,6 +1205,16 @@ ari_validator ast_ari_validate_application_fn(void); * - application: string (required) * - timestamp: Date * - device_state: DeviceState (required) + * Dial + * - type: string (required) + * - application: string (required) + * - timestamp: Date + * - caller: Channel + * - dialstatus: string (required) + * - dialstring: string + * - forward: string + * - forwarded: Channel + * - peer: Channel (required) * EndpointStateChange * - type: string (required) * - application: string (required) diff --git a/res/stasis/app.c b/res/stasis/app.c index 8ad41e565bae337b109a375b305c7bd201b1c4b0..8e9872aec35ae9e03dcf931c09caa50931234f69 100644 --- a/res/stasis/app.c +++ b/res/stasis/app.c @@ -265,6 +265,25 @@ static void app_dtor(void *obj) app->data = NULL; } +static void call_forwarded_handler(struct stasis_app *app, struct stasis_message *message) +{ + struct ast_multi_channel_blob *payload = stasis_message_data(message); + struct ast_channel_snapshot *snapshot = ast_multi_channel_blob_get_channel(payload, "forwarded"); + struct ast_channel *chan; + + if (!snapshot) { + return; + } + + chan = ast_channel_get_by_name(snapshot->uniqueid); + if (!chan) { + return; + } + + app_subscribe_channel(app, chan); + ast_channel_unref(chan); +} + static void sub_default_handler(void *data, struct stasis_subscription *sub, struct stasis_message *message) { @@ -275,6 +294,10 @@ static void sub_default_handler(void *data, struct stasis_subscription *sub, ao2_cleanup(app); } + if (stasis_message_type(message) == ast_channel_dial_type()) { + call_forwarded_handler(app, message); + } + /* By default, send any message that has a JSON representation */ json = stasis_message_to_json(message, stasis_app_get_sanitizer()); if (!json) { diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json index 5195a5bbbde53677e467fa0fa5544b0d8f3e0454..a0c5408fc91327776aec1691b11d82fdd5eb2b8f 100644 --- a/rest-api/api-docs/events.json +++ b/rest-api/api-docs/events.json @@ -98,6 +98,7 @@ "ChannelHangupRequest", "ChannelVarset", "EndpointStateChange", + "Dial", "StasisEnd", "StasisStart" ] @@ -411,6 +412,42 @@ } } }, + "Dial": { + "id": "Dial", + "description": "Dialing state has changed.", + "properties": { + "caller": { + "required": false, + "type": "Channel", + "description": "The calling channel." + }, + "peer": { + "required": true, + "type": "Channel", + "description": "The dialed channel." + }, + "forward": { + "required": false, + "type": "string", + "description": "Forwarding target requested by the original dialed channel." + }, + "forwarded": { + "required": false, + "type": "Channel", + "description": "Channel that the caller has been forwarded to." + }, + "dialstring": { + "required": false, + "type": "string", + "description": "The dial string for calling the peer channel." + }, + "dialstatus": { + "required": true, + "type": "string", + "description": "Current status of the dialing attempt to the peer." + } + } + }, "StasisEnd": { "id": "StasisEnd", "description": "Notification that a channel has left a Stasis application.",