ARI: Add the ability to intercept hold and raise an event

For some applications - such as SLA - a phone pressing hold should not behave
in the fashion that the Asterisk core would like it to. Instead, the hold
action has some application specific behaviour associated with it - such as
disconnecting the channel that initiated the hold; only playing MoH to channels
in the bridge if the channels are of a particular type, etc.

One way of accomplishing this is to use a framehook to intercept the
hold/unhold frames, raise an event, and eat the frame. Tasty. This patch
accomplishes that using a new dialplan function, HOLD_INTERCEPT.

In addition, some general cleanup of raising hold/unhold Stasis messages was
done, including removing some RAII_VAR usage.

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

ASTERISK-24922 #close


git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/13@434216 65c4cc65-6c06-0410-ace0-fbb531ad65f3
changes/43/43/1
Matthew Jordan 10 years ago
parent 488f093e97
commit ab803ec342

@ -20,6 +20,22 @@ chan_pjsip
more information. Defaults to 'no' as setting it to 'yes' can result in
many unnecessary messages being sent to the caller.
res_ari_channels
------------------
* Two new events, 'ChannelHold' and 'ChannelUnhold', have been added to the
events data model. These events are raised when a channel indicates a hold
or unhold, respectively.
func_holdintercept
------------------
* A new dialplan function, HOLD_INTERCEPT, has been added. This function, when
placed on a channel, intercepts hold/unhold indications signalled by the
channel and prevents them from moving on to other channels in a bridge with
the hold initiator. Instead, AMI or ARI events are raised indicating that
the channel wanted to place someone on hold. This allows external
applications to implement their own custom hold/unhold logic.
------------------------------------------------------------------------------
--- Functionality changes from Asterisk 13.2.0 to Asterisk 13.3.0 ------------
------------------------------------------------------------------------------

@ -979,7 +979,8 @@ int ast_bridge_channel_write_control_data(struct ast_bridge_channel *bridge_chan
int ast_bridge_channel_write_hold(struct ast_bridge_channel *bridge_channel, const char *moh_class)
{
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
struct ast_json *blob;
int res;
size_t datalen;
if (!ast_strlen_zero(moh_class)) {
@ -990,12 +991,16 @@ int ast_bridge_channel_write_hold(struct ast_bridge_channel *bridge_channel, con
} else {
moh_class = NULL;
datalen = 0;
blob = NULL;
}
ast_channel_publish_cached_blob(bridge_channel->chan, ast_channel_hold_type(), blob);
return ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
res = ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
moh_class, datalen);
ast_json_unref(blob);
return res;
}
int ast_bridge_channel_write_unhold(struct ast_bridge_channel *bridge_channel)

@ -1193,8 +1193,8 @@ int ast_queue_hangup_with_cause(struct ast_channel *chan, int cause)
int ast_queue_hold(struct ast_channel *chan, const char *musicclass)
{
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
struct ast_frame f = { AST_FRAME_CONTROL, .subclass.integer = AST_CONTROL_HOLD };
struct ast_json *blob = NULL;
int res;
if (!ast_strlen_zero(musicclass)) {
@ -1209,6 +1209,8 @@ int ast_queue_hold(struct ast_channel *chan, const char *musicclass)
res = ast_queue_frame(chan, &f);
ast_json_unref(blob);
return res;
}

@ -1061,20 +1061,22 @@ static void channel_hold_cb(void *data, struct stasis_subscription *sub,
struct stasis_message *message)
{
struct ast_channel_blob *obj = stasis_message_data(message);
const char *musicclass;
RAII_VAR(struct ast_str *, musicclass_string, NULL, ast_free);
RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
struct ast_str *musicclass_string = ast_str_create(32);
struct ast_str *channel_event_string;
if (!(musicclass_string = ast_str_create(32))) {
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)) {
@ -1087,13 +1089,16 @@ static void channel_hold_cb(void *data, struct stasis_subscription *sub,
"%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);
RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
struct ast_str *channel_event_string;
channel_event_string = ast_manager_build_channel_state_string(obj->snapshot);
if (!channel_event_string) {
@ -1103,6 +1108,8 @@ static void channel_unhold_cb(void *data, struct stasis_subscription *sub,
manager_event(EVENT_FLAG_CALL, "Unhold",
"%s",
ast_str_buffer(channel_event_string));
ast_free(channel_event_string);
}
static void manager_channels_shutdown(void)

@ -1136,6 +1136,47 @@ static struct ast_json *talking_stop_to_json(struct stasis_message *message,
return channel_blob_to_json(message, "ChannelTalkingFinished", sanitize);
}
static struct ast_json *hold_to_json(struct stasis_message *message,
const struct stasis_message_sanitizer *sanitize)
{
struct ast_channel_blob *channel_blob = stasis_message_data(message);
struct ast_json *blob = channel_blob->blob;
struct ast_channel_snapshot *snapshot = channel_blob->snapshot;
const char *musicclass = ast_json_string_get(ast_json_object_get(blob, "musicclass"));
const struct timeval *tv = stasis_message_timestamp(message);
struct ast_json *json_channel;
json_channel = ast_channel_snapshot_to_json(snapshot, sanitize);
if (!json_channel) {
return NULL;
}
return ast_json_pack("{s: s, s: o, s: s, s: o}",
"type", "ChannelHold",
"timestamp", ast_json_timeval(*tv, NULL),
"musicclass", S_OR(musicclass, "N/A"),
"channel", json_channel);
}
static struct ast_json *unhold_to_json(struct stasis_message *message,
const struct stasis_message_sanitizer *sanitize)
{
struct ast_channel_blob *channel_blob = stasis_message_data(message);
struct ast_channel_snapshot *snapshot = channel_blob->snapshot;
const struct timeval *tv = stasis_message_timestamp(message);
struct ast_json *json_channel;
json_channel = ast_channel_snapshot_to_json(snapshot, sanitize);
if (!json_channel) {
return NULL;
}
return ast_json_pack("{s: s, s: o, s: o}",
"type", "ChannelUnhold",
"timestamp", ast_json_timeval(*tv, NULL),
"channel", json_channel);
}
/*!
* @{ \brief Define channel message types.
*/
@ -1154,8 +1195,12 @@ STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_begin_type);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_dtmf_end_type,
.to_json = dtmf_end_to_json,
);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_hold_type);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_unhold_type);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_hold_type,
.to_json = hold_to_json,
);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_unhold_type,
.to_json = unhold_to_json,
);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_chanspy_start_type);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_chanspy_stop_type);
STASIS_MESSAGE_TYPE_DEFN(ast_channel_fax_type);

@ -3197,6 +3197,85 @@ ari_validator ast_ari_validate_channel_hangup_request_fn(void)
return ast_ari_validate_channel_hangup_request;
}
int ast_ari_validate_channel_hold(struct ast_json *json)
{
int res = 1;
struct ast_json_iter *iter;
int has_type = 0;
int has_application = 0;
int has_channel = 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 ChannelHold 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 ChannelHold 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 ChannelHold field timestamp failed validation\n");
res = 0;
}
} else
if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_channel = 1;
prop_is_valid = ast_ari_validate_channel(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ChannelHold field channel failed validation\n");
res = 0;
}
} else
{
ast_log(LOG_ERROR,
"ARI ChannelHold has undocumented field %s\n",
ast_json_object_iter_key(iter));
res = 0;
}
}
if (!has_type) {
ast_log(LOG_ERROR, "ARI ChannelHold missing required field type\n");
res = 0;
}
if (!has_application) {
ast_log(LOG_ERROR, "ARI ChannelHold missing required field application\n");
res = 0;
}
if (!has_channel) {
ast_log(LOG_ERROR, "ARI ChannelHold missing required field channel\n");
res = 0;
}
return res;
}
ari_validator ast_ari_validate_channel_hold_fn(void)
{
return ast_ari_validate_channel_hold;
}
int ast_ari_validate_channel_left_bridge(struct ast_json *json)
{
int res = 1;
@ -3545,6 +3624,85 @@ ari_validator ast_ari_validate_channel_talking_started_fn(void)
return ast_ari_validate_channel_talking_started;
}
int ast_ari_validate_channel_unhold(struct ast_json *json)
{
int res = 1;
struct ast_json_iter *iter;
int has_type = 0;
int has_application = 0;
int has_channel = 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 ChannelUnhold 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 ChannelUnhold 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 ChannelUnhold field timestamp failed validation\n");
res = 0;
}
} else
if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_channel = 1;
prop_is_valid = ast_ari_validate_channel(
ast_json_object_iter_value(iter));
if (!prop_is_valid) {
ast_log(LOG_ERROR, "ARI ChannelUnhold field channel failed validation\n");
res = 0;
}
} else
{
ast_log(LOG_ERROR,
"ARI ChannelUnhold has undocumented field %s\n",
ast_json_object_iter_key(iter));
res = 0;
}
}
if (!has_type) {
ast_log(LOG_ERROR, "ARI ChannelUnhold missing required field type\n");
res = 0;
}
if (!has_application) {
ast_log(LOG_ERROR, "ARI ChannelUnhold missing required field application\n");
res = 0;
}
if (!has_channel) {
ast_log(LOG_ERROR, "ARI ChannelUnhold missing required field channel\n");
res = 0;
}
return res;
}
ari_validator ast_ari_validate_channel_unhold_fn(void)
{
return ast_ari_validate_channel_unhold;
}
int ast_ari_validate_channel_userevent(struct ast_json *json)
{
int res = 1;
@ -4119,6 +4277,9 @@ int ast_ari_validate_event(struct ast_json *json)
if (strcmp("ChannelHangupRequest", discriminator) == 0) {
return ast_ari_validate_channel_hangup_request(json);
} else
if (strcmp("ChannelHold", discriminator) == 0) {
return ast_ari_validate_channel_hold(json);
} else
if (strcmp("ChannelLeftBridge", discriminator) == 0) {
return ast_ari_validate_channel_left_bridge(json);
} else
@ -4131,6 +4292,9 @@ int ast_ari_validate_event(struct ast_json *json)
if (strcmp("ChannelTalkingStarted", discriminator) == 0) {
return ast_ari_validate_channel_talking_started(json);
} else
if (strcmp("ChannelUnhold", discriminator) == 0) {
return ast_ari_validate_channel_unhold(json);
} else
if (strcmp("ChannelUserevent", discriminator) == 0) {
return ast_ari_validate_channel_userevent(json);
} else
@ -4290,6 +4454,9 @@ int ast_ari_validate_message(struct ast_json *json)
if (strcmp("ChannelHangupRequest", discriminator) == 0) {
return ast_ari_validate_channel_hangup_request(json);
} else
if (strcmp("ChannelHold", discriminator) == 0) {
return ast_ari_validate_channel_hold(json);
} else
if (strcmp("ChannelLeftBridge", discriminator) == 0) {
return ast_ari_validate_channel_left_bridge(json);
} else
@ -4302,6 +4469,9 @@ int ast_ari_validate_message(struct ast_json *json)
if (strcmp("ChannelTalkingStarted", discriminator) == 0) {
return ast_ari_validate_channel_talking_started(json);
} else
if (strcmp("ChannelUnhold", discriminator) == 0) {
return ast_ari_validate_channel_unhold(json);
} else
if (strcmp("ChannelUserevent", discriminator) == 0) {
return ast_ari_validate_channel_userevent(json);
} else

@ -808,6 +808,24 @@ int ast_ari_validate_channel_hangup_request(struct ast_json *json);
*/
ari_validator ast_ari_validate_channel_hangup_request_fn(void);
/*!
* \brief Validator for ChannelHold.
*
* A channel initiated a media hold.
*
* \param json JSON object to validate.
* \returns True (non-zero) if valid.
* \returns False (zero) if invalid.
*/
int ast_ari_validate_channel_hold(struct ast_json *json);
/*!
* \brief Function pointer to ast_ari_validate_channel_hold().
*
* See \ref ast_ari_model_validators.h for more details.
*/
ari_validator ast_ari_validate_channel_hold_fn(void);
/*!
* \brief Validator for ChannelLeftBridge.
*
@ -880,6 +898,24 @@ int ast_ari_validate_channel_talking_started(struct ast_json *json);
*/
ari_validator ast_ari_validate_channel_talking_started_fn(void);
/*!
* \brief Validator for ChannelUnhold.
*
* A channel initiated a media unhold.
*
* \param json JSON object to validate.
* \returns True (non-zero) if valid.
* \returns False (zero) if invalid.
*/
int ast_ari_validate_channel_unhold(struct ast_json *json);
/*!
* \brief Function pointer to ast_ari_validate_channel_unhold().
*
* See \ref ast_ari_model_validators.h for more details.
*/
ari_validator ast_ari_validate_channel_unhold_fn(void);
/*!
* \brief Validator for ChannelUserevent.
*
@ -1393,6 +1429,11 @@ ari_validator ast_ari_validate_application_fn(void);
* - cause: int
* - channel: Channel (required)
* - soft: boolean
* ChannelHold
* - type: string (required)
* - application: string (required)
* - timestamp: Date
* - channel: Channel (required)
* ChannelLeftBridge
* - type: string (required)
* - application: string (required)
@ -1415,6 +1456,11 @@ ari_validator ast_ari_validate_application_fn(void);
* - application: string (required)
* - timestamp: Date
* - channel: Channel (required)
* ChannelUnhold
* - type: string (required)
* - application: string (required)
* - timestamp: Date
* - channel: Channel (required)
* ChannelUserevent
* - type: string (required)
* - application: string (required)

@ -161,6 +161,8 @@
"ChannelVarset",
"ChannelTalkingStarted",
"ChannelTalkingFinished",
"ChannelHold",
"ChannelUnhold",
"EndpointStateChange",
"Dial",
"StasisEnd",
@ -598,6 +600,28 @@
}
}
},
"ChannelHold": {
"id": "ChannelHold",
"description": "A channel initiated a media hold.",
"properties": {
"channel": {
"required": true,
"type": "Channel",
"description": "The channel that initiated the hold event."
}
}
},
"ChannelUnhold": {
"id": "ChannelUnhold",
"description": "A channel initiated a media unhold.",
"properties": {
"channel": {
"required": true,
"type": "Channel",
"description": "The channel that initiated the unhold event."
}
}
},
"ChannelTalkingStarted": {
"id": "ChannelTalkingStarted",
"description": "Talking was detected on the channel.",

Loading…
Cancel
Save