res_stasis: Expose event for call forwarding and follow forwarded channel.

This change adds an event for when an originated call is redirected to
another target. This event contains the original channel and the newly
created channel. If a stasis subscription exists on the original originated
channel for a stasis application then a new subscription will also be
created on the stasis application to the redirected channel. This allows
the application to follow the call path completely.

(closes issue ASTERISK-22719)
Reported by: Joshua Colp

Review: https://reviewboard.asterisk.org/r/3054/


git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/12@403808 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Joshua Colp
2013-12-14 17:15:54 +00:00
parent b602348140
commit 6e7d64c79d
9 changed files with 303 additions and 8 deletions

View File

@@ -1001,7 +1001,7 @@ static void do_forward(struct chanlist *o, struct cause_args *num,
ast_channel_unlock(c); ast_channel_unlock(c);
ast_channel_lock_both(original, in); 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_call_forward(c));
ast_channel_unlock(in); ast_channel_unlock(in);
ast_channel_unlock(original); ast_channel_unlock(original);

View File

@@ -4666,7 +4666,7 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
ast_channel_unlock(qe->chan); ast_channel_unlock(qe->chan);
ast_channel_lock_both(qe->chan, original); 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_call_forward(original));
ast_channel_unlock(original); ast_channel_unlock(original);
ast_channel_unlock(qe->chan); ast_channel_unlock(qe->chan);

View File

@@ -518,12 +518,14 @@ void ast_channel_publish_dial(struct ast_channel *caller,
* *
* \param caller The channel performing the dial operation * \param caller The channel performing the dial operation
* \param peer The channel being dialed * \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 dialstring The information passed to the dialing application when beginning a dial
* \param dialstatus The current status of the dial operation * \param dialstatus The current status of the dial operation
* \param forward The call forward string provided by the dialed channel * \param forward The call forward string provided by the dialed channel
*/ */
void ast_channel_publish_dial_forward(struct ast_channel *caller, void ast_channel_publish_dial_forward(struct ast_channel *caller,
struct ast_channel *peer, struct ast_channel *peer,
struct ast_channel *forwarded,
const char *dialstring, const char *dialstring,
const char *dialstatus, const char *dialstatus,
const char *forward); const char *forward);

View File

@@ -422,14 +422,17 @@ static int handle_call_forward(struct ast_dial *dial, struct ast_dial_channel *c
channel->device = ast_strdup(device); channel->device = ast_strdup(device);
AST_LIST_UNLOCK(&dial->channels); AST_LIST_UNLOCK(&dial->channels);
/* Drop the original channel */ /* Drop the original channel */
ast_hangup(original);
channel->owner = NULL; channel->owner = NULL;
/* Finally give it a go... send it out into the world */ /* Finally give it a go... send it out into the world */
begin_dial_channel(channel, chan, chan ? 0 : 1); begin_dial_channel(channel, chan, chan ? 0 : 1);
ast_channel_publish_dial_forward(chan, original, channel->owner, NULL, "CANCEL",
ast_channel_call_forward(original));
ast_hangup(original);
return 0; return 0;
} }

View File

@@ -287,14 +287,21 @@ static void channel_blob_dtor(void *obj)
ast_json_unref(event->blob); 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, 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 ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
RAII_VAR(struct stasis_message *, msg, 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_json *, blob, NULL, ast_json_unref);
RAII_VAR(struct ast_channel_snapshot *, caller_snapshot, NULL, ao2_cleanup); 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 *, peer_snapshot, NULL, ao2_cleanup);
RAII_VAR(struct ast_channel_snapshot *, forwarded_snapshot, NULL, ao2_cleanup);
ast_assert(peer != NULL); ast_assert(peer != NULL);
blob = ast_json_pack("{s: s, s: s, s: s}", 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); 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); msg = stasis_message_create(ast_channel_dial_type(), payload);
if (!msg) { if (!msg) {
return; 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, void ast_channel_publish_dial(struct ast_channel *caller, struct ast_channel *peer,
const char *dialstring, const char *dialstatus) 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, 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); 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. * @{ \brief Define channel message types.
*/ */
STASIS_MESSAGE_TYPE_DEFN(ast_channel_snapshot_type); 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, STASIS_MESSAGE_TYPE_DEFN(ast_channel_varset_type,
.to_ami = varset_to_ami, .to_ami = varset_to_ami,
.to_json = varset_to_json, .to_json = varset_to_json,

View File

@@ -2879,6 +2879,137 @@ ari_validator ast_ari_validate_device_state_changed_fn(void)
return ast_ari_validate_device_state_changed; 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 ast_ari_validate_endpoint_state_change(struct ast_json *json)
{ {
int res = 1; int res = 1;
@@ -3023,6 +3154,9 @@ int ast_ari_validate_event(struct ast_json *json)
if (strcmp("DeviceStateChanged", discriminator) == 0) { if (strcmp("DeviceStateChanged", discriminator) == 0) {
return ast_ari_validate_device_state_changed(json); return ast_ari_validate_device_state_changed(json);
} else } else
if (strcmp("Dial", discriminator) == 0) {
return ast_ari_validate_dial(json);
} else
if (strcmp("EndpointStateChange", discriminator) == 0) { if (strcmp("EndpointStateChange", discriminator) == 0) {
return ast_ari_validate_endpoint_state_change(json); return ast_ari_validate_endpoint_state_change(json);
} else } else
@@ -3173,6 +3307,9 @@ int ast_ari_validate_message(struct ast_json *json)
if (strcmp("DeviceStateChanged", discriminator) == 0) { if (strcmp("DeviceStateChanged", discriminator) == 0) {
return ast_ari_validate_device_state_changed(json); return ast_ari_validate_device_state_changed(json);
} else } else
if (strcmp("Dial", discriminator) == 0) {
return ast_ari_validate_dial(json);
} else
if (strcmp("EndpointStateChange", discriminator) == 0) { if (strcmp("EndpointStateChange", discriminator) == 0) {
return ast_ari_validate_endpoint_state_change(json); return ast_ari_validate_endpoint_state_change(json);
} else } else

View File

@@ -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); 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. * \brief Validator for EndpointStateChange.
* *
@@ -1187,6 +1205,16 @@ ari_validator ast_ari_validate_application_fn(void);
* - application: string (required) * - application: string (required)
* - timestamp: Date * - timestamp: Date
* - device_state: DeviceState (required) * - 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 * EndpointStateChange
* - type: string (required) * - type: string (required)
* - application: string (required) * - application: string (required)

View File

@@ -265,6 +265,25 @@ static void app_dtor(void *obj)
app->data = NULL; 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, static void sub_default_handler(void *data, struct stasis_subscription *sub,
struct stasis_message *message) struct stasis_message *message)
{ {
@@ -275,6 +294,10 @@ static void sub_default_handler(void *data, struct stasis_subscription *sub,
ao2_cleanup(app); 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 */ /* By default, send any message that has a JSON representation */
json = stasis_message_to_json(message, stasis_app_get_sanitizer()); json = stasis_message_to_json(message, stasis_app_get_sanitizer());
if (!json) { if (!json) {

View File

@@ -98,6 +98,7 @@
"ChannelHangupRequest", "ChannelHangupRequest",
"ChannelVarset", "ChannelVarset",
"EndpointStateChange", "EndpointStateChange",
"Dial",
"StasisEnd", "StasisEnd",
"StasisStart" "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": { "StasisEnd": {
"id": "StasisEnd", "id": "StasisEnd",
"description": "Notification that a channel has left a Stasis application.", "description": "Notification that a channel has left a Stasis application.",