[asterisk-commits] mmichelson: trunk r370979 - in /trunk: channels/ channels/sip/include/ includ...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Thu Aug 9 09:52:23 CDT 2012


Author: mmichelson
Date: Thu Aug  9 09:52:16 2012
New Revision: 370979

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=370979
Log:
Extend extension state callbacks to have more information.

Quote from review board:

This patch extends the extension state callbacks so that monitoring channels
(as chan_sip) get more information of the devices which are responsible for
an extension state change. The additional information is needed by chan_sip
to present names/numbers of the caller and callee in an early-state SIP
notification. Users of extenstion state callback not interested in the
additional information are not affected by the changes.

Motivation: to present the involved party's name/number in an early-state
nofification (used by the notified device as a pickup offer) one after another
so that a user can see which call he will pick up in an undirected pickup.
Such a pickup offer to a user shall indicate the same call (number/name-A calls
number/name-B) as the call which would be picked up when an undirected pickup
is executed.


Users interested in additional state info must use the new functions
ast_extension_state_add_extended() resp.
ast_extension_state_add_destroy_extended() to register an extended state
callback. When the callback is registered this way, an extra member
device_state_info of struct ast_state_cb_info is passed to the callback in
addition to the aggregated extension state. This container holds an object for
every device of the monitored extension hint consisting of the device name, the
device state and a channel reference to the channel which (presumably) caused
the device state.

The information is used by chan_sip for early-state notifications. When the
state of a device changes and the new state contains AST_EVENT_RINGING, an
early-state notification is sent to the subscribed devices with the
caller/callee names/numbers of the oldest ringing channel of the monitored
extension. The notified user may then invoke a direct pickup, which will pickup
exactly this channel.

Users of the old non-extended callbacks will only be called when the aggregated
state did change (same behavior as before). Users of the extended callback will
also be called when the state is unchanged but does contain AST_EVENT_RINGING.
That could be the case if two channels are ringing at one device and one of
them hangs up, so the aggregated state does not change. This way the monitoring
channel can create a new early-state notification with the now ringing
party-ids.

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

This contribution comes from Guenther Kelleter


Modified:
    trunk/channels/chan_sip.c
    trunk/channels/sip/include/sip.h
    trunk/include/asterisk/pbx.h
    trunk/main/pbx.c

Modified: trunk/channels/chan_sip.c
URL: http://svnview.digium.com/svn/asterisk/trunk/channels/chan_sip.c?view=diff&rev=370979&r1=370978&r2=370979
==============================================================================
--- trunk/channels/chan_sip.c (original)
+++ trunk/channels/chan_sip.c Thu Aug  9 09:52:16 2012
@@ -1141,6 +1141,7 @@
 
 struct state_notify_data {
 	int state;
+	struct ao2_container *device_state_info;
 	int presence_state;
 	const char *presence_subtype;
 	const char *presence_message;
@@ -1438,7 +1439,7 @@
 static int do_magic_pickup(struct ast_channel *channel, const char *extension, const char *context);
 
 /*--- Device monitoring and Device/extension state/event handling */
-static int extensionstate_update(char *context, char *exten, struct state_notify_data *data, struct sip_pvt *p);
+static int extensionstate_update(const char *context, const char *exten, struct state_notify_data *data, struct sip_pvt *p, int force);
 static int cb_extensionstate(char *context, char *exten, struct ast_state_cb_info *info, void *data);
 static int sip_poke_noanswer(const void *data);
 static int sip_poke_peer(struct sip_peer *peer, int force);
@@ -6241,6 +6242,11 @@
 	p->peercaps = ast_format_cap_destroy(p->peercaps);
 	p->redircaps = ast_format_cap_destroy(p->redircaps);
 	p->prefcaps = ast_format_cap_destroy(p->prefcaps);
+
+	if (p->last_device_state_info) {
+		ao2_ref(p->last_device_state_info, -1);
+		p->last_device_state_info = NULL;
+	}
 
 	/* Lastly, kill the callid associated with the pvt */
 	if (p->logger_callid) {
@@ -13661,22 +13667,30 @@
 	return 0;
 }
 
-/*! \brief Find the channel that is causing the RINGING update */
-static int find_calling_channel(void *obj, void *arg, void *data, int flags)
-{
-	struct ast_channel *c = obj;
-	struct sip_pvt *p = data;
-	int res;
-
-	ast_channel_lock(c);
-
-	res = (ast_channel_pbx(c) &&
-			(!strcasecmp(ast_channel_macroexten(c), p->exten) || !strcasecmp(ast_channel_exten(c), p->exten)) &&
-			(sip_cfg.notifycid == IGNORE_CONTEXT || !strcasecmp(ast_channel_context(c), p->context)));
-
-	ast_channel_unlock(c);
-
-	return res ? CMP_MATCH | CMP_STOP : 0;
+/*! \internal \brief Find the channel that is causing the RINGING update, ref'd */
+static struct ast_channel *find_ringing_channel(struct ao2_container *device_state_info, struct sip_pvt *p)
+{
+	struct ao2_iterator citer;
+	struct ast_device_state_info *device_state;
+	struct ast_channel *c = NULL;
+	struct timeval tv = {0,};
+
+	/* iterate ringing devices and get the oldest of all causing channels */
+	citer = ao2_iterator_init(device_state_info, 0);
+	for (; (device_state = ao2_iterator_next(&citer)); ao2_ref(device_state, -1)) {
+		if (!device_state->causing_channel || (device_state->device_state != AST_DEVICE_RINGING &&
+		    device_state->device_state != AST_DEVICE_RINGINUSE)) {
+			continue;
+		}
+		ast_channel_lock(device_state->causing_channel);
+		if (ast_tvzero(tv) || ast_tvcmp(ast_channel_creationtime(device_state->causing_channel), tv) < 0) {
+			c = device_state->causing_channel;
+			tv = ast_channel_creationtime(c);
+		}
+		ast_channel_unlock(device_state->causing_channel);
+	}
+	ao2_iterator_destroy(&citer);
+	return c ? ast_channel_ref(c) : NULL;
 }
 
 /* XXX Candidate for moving into its own file */
@@ -13819,7 +13833,7 @@
 	case DIALOG_INFO_XML: /* SNOM subscribes in this format */
 		ast_str_append(tmp, 0, "<?xml version=\"1.0\"?>\n");
 		ast_str_append(tmp, 0, "<dialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"%u\" state=\"%s\" entity=\"%s\">\n", p->dialogver, full ? "full" : "partial", mto);
-		if ((data->state & AST_EXTENSION_RINGING) && sip_cfg.notifyringing) {
+		if (data->state > 0 && (data->state & AST_EXTENSION_RINGING) && sip_cfg.notifyringing) {
 			const char *local_display = exten;
 			char *local_target = ast_strdupa(mto);
 			const char *remote_display = exten;
@@ -13837,34 +13851,35 @@
 			   callee must be dialing the same extension that is being monitored.  Simply dialing
 			   the hint'd device is not sufficient. */
 			if (sip_cfg.notifycid) {
-				struct ast_channel *caller;
-
-				if ((caller = ast_channel_callback(find_calling_channel, NULL, p, 0))) {
+				struct ast_channel *callee;
+
+				callee = find_ringing_channel(data->device_state_info, p);
+				if (callee) {
 					char *cid_num;
 					char *connected_num;
 					int need;
 
-					ast_channel_lock(caller);
-					cid_num = S_COR(ast_channel_caller(caller)->id.number.valid,
-						ast_channel_caller(caller)->id.number.str, "");
+					ast_channel_lock(callee);
+					cid_num = S_COR(ast_channel_caller(callee)->id.number.valid,
+						ast_channel_caller(callee)->id.number.str, "");
 					need = strlen(cid_num) + strlen(p->fromdomain) + sizeof("sip:@");
+					local_target = ast_alloca(need);
+					snprintf(local_target, need, "sip:%s@%s", cid_num, p->fromdomain);
+
+					local_display = ast_strdupa(S_COR(ast_channel_caller(callee)->id.name.valid,
+						ast_channel_caller(callee)->id.name.str, ""));
+
+					connected_num = S_COR(ast_channel_connected(callee)->id.number.valid,
+						ast_channel_connected(callee)->id.number.str, "");
+					need = strlen(connected_num) + strlen(p->fromdomain) + sizeof("sip:@");
 					remote_target = ast_alloca(need);
-					snprintf(remote_target, need, "sip:%s@%s", cid_num, p->fromdomain);
-
-					remote_display = ast_strdupa(S_COR(ast_channel_caller(caller)->id.name.valid,
-						ast_channel_caller(caller)->id.name.str, ""));
-
-					connected_num = S_COR(ast_channel_connected(caller)->id.number.valid,
-						ast_channel_connected(caller)->id.number.str, "");
-					need = strlen(connected_num) + strlen(p->fromdomain) + sizeof("sip:@");
-					local_target = ast_alloca(need);
-					snprintf(local_target, need, "sip:%s@%s", connected_num, p->fromdomain);
-
-					local_display = ast_strdupa(S_COR(ast_channel_connected(caller)->id.name.valid,
-						ast_channel_connected(caller)->id.name.str, ""));
-
-					ast_channel_unlock(caller);
-					caller = ast_channel_unref(caller);
+					snprintf(remote_target, need, "sip:%s@%s", connected_num, p->fromdomain);
+
+					remote_display = ast_strdupa(S_COR(ast_channel_connected(callee)->id.name.valid,
+						ast_channel_connected(callee)->id.name.str, ""));
+
+					ast_channel_unlock(callee);
+					callee = ast_channel_unref(callee);
 				}
 
 				/* We create a fake call-id which the phone will send back in an INVITE
@@ -15800,7 +15815,7 @@
 /*! \brief Callback for the devicestate notification (SUBSCRIBE) support subsystem
 \note	If you add an "hint" priority to the extension in the dial plan,
 	you will get notifications on device state changes */
-static int extensionstate_update(char *context, char *exten, struct state_notify_data *data, struct sip_pvt *p)
+static int extensionstate_update(const char *context, const char *exten, struct state_notify_data *data, struct sip_pvt *p, int force)
 {
 	sip_pvt_lock(p);
 
@@ -15813,6 +15828,38 @@
 		append_history(p, "Subscribestatus", "%s", data->state == AST_EXTENSION_REMOVED ? "HintRemoved" : "Deactivated");
 		break;
 	default:	/* Tell user */
+		if (force) {
+			/* we must skip the next two checks for a queued state change or resubscribe */
+		} else if (p->laststate == data->state && (~data->state & AST_EXTENSION_RINGING)) {
+			/* don't notify unchanged state or unchanged early-state causing parties again */
+			sip_pvt_unlock(p);
+			return 0;
+		} else if (data->state & AST_EXTENSION_RINGING) {
+			/* check if another channel than last time is ringing now to be notified */
+			struct ast_channel *ringing = find_ringing_channel(data->device_state_info, p);
+			if (!ringing) {
+				/* there's something wrong, the device is ringing but there is no
+				 * ringing channel (yet, chan_sip is strange
+				 * the device is in AST_DEVICE_RINGING but the channel is in AST_STATE_DOWN)
+				 */
+				sip_pvt_unlock(p);
+				return 0;
+			}
+			if (!ast_tvcmp(ast_channel_creationtime(ringing), p->last_ringing_channel_time)) {
+				/* we assume here that no two channels have the exact same creation time */
+				ao2_ref(ringing, -1);
+				sip_pvt_unlock(p);
+				return 0;
+			}
+			p->last_ringing_channel_time = ast_channel_creationtime(ringing);
+			ao2_ref(ringing, -1);
+		}
+		/* ref before unref because the new could be the same as the old one. Don't risk destruction! */
+		if (data->device_state_info) {
+			ao2_ref(data->device_state_info, 1);
+		}
+		ao2_cleanup(p->last_device_state_info);
+		p->last_device_state_info = data->device_state_info;
 		p->laststate = data->state;
 		p->last_presence_state = data->presence_state;
 		ast_string_field_set(p, last_presence_subtype, S_OR(data->presence_subtype, ""));
@@ -15822,6 +15869,11 @@
 	if (p->subscribed != NONE) {	/* Only send state NOTIFY if we know the format */
 		if (!p->pendinginvite) {
 			transmit_state_notify(p, data, 1, FALSE);
+			if (p->last_device_state_info) {
+				/* We don't need the saved ref anymore, don't keep channels ref'd. */
+				ao2_ref(p->last_device_state_info, -1);
+				p->last_device_state_info = NULL;
+			}
 		} else {
 			/* We already have a NOTIFY sent that is not answered. Queue the state up.
 			   if many state changes happen meanwhile, we will only send a notification of the last one */
@@ -15844,6 +15896,7 @@
 	struct sip_pvt *p = data;
 	struct state_notify_data notify_data = {
 		.state = info->exten_state,
+		.device_state_info = info->device_state_info,
 		.presence_state = info->presence_state,
 		.presence_subtype = info->presence_subtype,
 		.presence_message = info->presence_message,
@@ -15854,7 +15907,7 @@
 		return 0;
 	}
 
-	return extensionstate_update(context, exten, &notify_data, p);
+	return extensionstate_update(context, exten, &notify_data, p, FALSE);
 }
 
 /*! \brief Send a fake 401 Unauthorized response when the administrator
@@ -22130,13 +22183,14 @@
 			if (ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE)) {
 				struct state_notify_data data = {
 					.state = p->laststate,
+					.device_state_info = p->last_device_state_info,
 					.presence_state = p->last_presence_state,
 					.presence_subtype = p->last_presence_subtype,
 					.presence_message = p->last_presence_message,
 				};
 				/* Ready to send the next state we have on queue */
 				ast_clear_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE);
-				extensionstate_update((char *)p->context, (char *)p->exten, &data, (void *) p);
+				extensionstate_update(p->context, p->exten, &data, p, TRUE);
 			}
 		}
 		break;
@@ -26863,6 +26917,7 @@
 			struct state_notify_data data = { 0, };
 			char *subtype = NULL;
 			char *message = NULL;
+			struct ao2_container *device_state_info = NULL;
 
 			if (p->expiry > 0 && !resubscribe) {
 				/* Add subscription for extension state from the PBX core */
@@ -26870,12 +26925,13 @@
 					ast_extension_state_del(p->stateid, cb_extensionstate);
 				}
 				dialog_ref(p, "copying dialog ptr into extension state struct");
-				p->stateid = ast_extension_state_add_destroy(p->context, p->exten, cb_extensionstate, cb_extensionstate_destroy, p);
+				p->stateid = ast_extension_state_add_destroy_extended(p->context, p->exten, cb_extensionstate, cb_extensionstate_destroy, p);
 				if (p->stateid == -1) {
 					dialog_unref(p, "copying dialog ptr into extension state struct failed");
 				}
 			}
-			if ((data.state = ast_extension_state(NULL, p->context, p->exten)) < 0) {
+			if ((data.state = ast_extension_state_extended(NULL, p->context, p->exten, &device_state_info)) < 0) {
+				ao2_cleanup(device_state_info);
 				if (p->expiry > 0) {
 					ast_log(LOG_NOTICE, "Got SUBSCRIBE for extension %s@%s from %s, but there is no hint for that extension.\n", p->exten, p->context, ast_sockaddr_stringify(&p->sa));
 				}
@@ -26893,12 +26949,31 @@
 			}
 			ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
 			transmit_response(p, "200 OK", req);
-			transmit_state_notify(p, &data, 1, FALSE);	/* Send first notification */
+			/* RFC 3265: A notification must be sent on every subscribe, so force it */
+			data.device_state_info = device_state_info;
+			if (data.state & AST_EXTENSION_RINGING) {
+				/* save last_ringing_channel_time if this state really contains a ringing channel
+				 * because extensionstate_update() doesn't do it if forced
+				 */
+				struct ast_channel *ringing = find_ringing_channel(data.device_state_info, p);
+				if (ringing) {
+					p->last_ringing_channel_time = ast_channel_creationtime(ringing);
+					ao2_ref(ringing, -1);
+				} else {
+					/* The device is ringing but there is no ringing channel (chan_sip:
+					 * the device can be AST_DEVICE_RINGING but the channel is AST_STATE_DOWN yet),
+					 * correct state for state_notify_build_xml not to search a ringing channel.
+					 */
+					data.state &= ~AST_EXTENSION_RINGING;
+				}
+			}
+			extensionstate_update(p->context, p->exten, &data, p, TRUE);
 			append_history(p, "Subscribestatus", "%s", ast_extension_state2str(data.state));
 			/* hide the 'complete' exten/context in the refer_to field for later display */
 			ast_string_field_build(p, subscribeuri, "%s@%s", p->exten, p->context);
 			/* Deleted the slow iteration of all sip dialogs to find old subscribes from this peer for exten at context */
 
+			ao2_cleanup(device_state_info);
 			ast_free(subtype);
 			ast_free(message);
 		}

Modified: trunk/channels/sip/include/sip.h
URL: http://svnview.digium.com/svn/asterisk/trunk/channels/sip/include/sip.h?view=diff&rev=370979&r1=370978&r2=370979
==============================================================================
--- trunk/channels/sip/include/sip.h (original)
+++ trunk/channels/sip/include/sip.h Thu Aug  9 09:52:16 2012
@@ -1155,6 +1155,8 @@
 	enum subscriptiontype subscribed;   /*!< SUBSCRIBE: Is this dialog a subscription?  */
 	int stateid;                        /*!< SUBSCRIBE: ID for devicestate subscriptions */
 	int laststate;                      /*!< SUBSCRIBE: Last known extension state */
+	struct ao2_container *last_device_state_info; /*!< SUBSCRIBE: last known extended extension state (take care of refs)*/
+	struct timeval last_ringing_channel_time; /*!< SUBSCRIBE: channel timestamp of the channel which caused the last early-state notification */
 	int last_presence_state;            /*!< SUBSCRIBE: Last known presence state */
 	uint32_t dialogver;                 /*!< SUBSCRIBE: Version for subscription dialog-info */
 

Modified: trunk/include/asterisk/pbx.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/pbx.h?view=diff&rev=370979&r1=370978&r2=370979
==============================================================================
--- trunk/include/asterisk/pbx.h (original)
+++ trunk/include/asterisk/pbx.h Thu Aug  9 09:52:16 2012
@@ -83,9 +83,16 @@
 	AST_HINT_UPDATE_PRESENCE = 2,
 };
 
+struct ast_device_state_info {
+	enum ast_device_state device_state;
+	struct ast_channel *causing_channel;
+	char device_name[1];
+};
+
 struct ast_state_cb_info {
 	enum ast_state_cb_update_reason reason;
 	enum ast_extension_states exten_state;
+	struct ao2_container *device_state_info; /* holds ast_device_state_info, must be referenced by callback if stored */
 	enum ast_presence_state presence_state;
 	const char *presence_subtype;
 	const char *presence_message;
@@ -483,6 +490,20 @@
 int ast_extension_state(struct ast_channel *c, const char *context, const char *exten);
 
 /*!
+ * \brief Uses hint and devicestate callback to get the extended state of an extension
+ * \since 11
+ *
+ * \param c this is not important
+ * \param context which context to look in
+ * \param exten which extension to get state
+ * \param[out] device_state_info ptr to an ao2_container with extended state info, must be unref'd after use.
+ *
+ * \return extension state as defined in the ast_extension_states enum
+ */
+int ast_extension_state_extended(struct ast_channel *c, const char *context, const char *exten,
+	struct ao2_container **device_state_info);
+
+/*!
  * \brief Uses hint and presence state callback to get the presence state of an extension
  *
  * \param c this is not important
@@ -531,6 +552,30 @@
 	ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data);
 
 /*!
+ * \brief Registers an extended state change callback with destructor.
+ * \since 11
+ *
+ * \param context which context to look in
+ * \param exten which extension to get state
+ * \param change_cb callback to call if state changed
+ * \param destroy_cb callback to call when registration destroyed.
+ * \param data to pass to callback
+ *
+ * \note The change_cb is called if the state of an extension is changed.
+ * The extended state is passed to the callback in the device_state_info
+ * member of ast_state_cb_info.
+ *
+ * \note The destroy_cb is called when the registration is
+ * deleted so the registerer can release any associated
+ * resources.
+ *
+ * \retval -1 on failure
+ * \retval ID on success
+ */
+int ast_extension_state_add_destroy_extended(const char *context, const char *exten,
+	ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data);
+
+/*!
  * \brief Registers a state change callback
  *
  * \param context which context to look in
@@ -544,6 +589,25 @@
  * \retval ID on success
  */
 int ast_extension_state_add(const char *context, const char *exten,
+	ast_state_cb_type change_cb, void *data);
+
+/*!
+ * \brief Registers an extended state change callback
+ * \since 11
+ *
+ * \param context which context to look in
+ * \param exten which extension to get state
+ * \param change_cb callback to call if state changed
+ * \param data to pass to callback
+ *
+ * \note The change_cb is called if the state of an extension is changed.
+ * The extended state is passed to the callback in the device_state_info
+ * member of ast_state_cb_info.
+ *
+ * \retval -1 on failure
+ * \retval ID on success
+ */
+int ast_extension_state_add_extended(const char *context, const char *exten,
 	ast_state_cb_type change_cb, void *data);
 
 /*!

Modified: trunk/main/pbx.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/pbx.c?view=diff&rev=370979&r1=370978&r2=370979
==============================================================================
--- trunk/main/pbx.c (original)
+++ trunk/main/pbx.c Thu Aug  9 09:52:16 2012
@@ -939,6 +939,8 @@
 	int id;
 	/*! Arbitrary data passed for callbacks. */
 	void *data;
+	/*! Flag if this callback is an extended callback containing detailed device status */
+	int extended;
 	/*! Callback when state changes. */
 	ast_state_cb_type change_cb;
 	/*! Callback when destroyed so any resources given by the registerer can be freed. */
@@ -1195,6 +1197,7 @@
 	const char *registrar, int lock_context);
 static struct ast_context *find_context_locked(const char *context);
 static struct ast_context *find_context(const char *context);
+static void get_device_state_causing_channels(struct ao2_container *c);
 
 /*!
  * \internal
@@ -4558,7 +4561,19 @@
 	return ast_str_buffer(hint_args);
 }
 
-static int ast_extension_state3(struct ast_str *hint_app)
+static void device_state_info_dt(void *obj)
+{
+	struct ast_device_state_info *info = obj;
+
+	ao2_cleanup(info->causing_channel);
+}
+
+static struct ao2_container *alloc_device_state_info(void)
+{
+	return ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 1, NULL, NULL);
+}
+
+static int ast_extension_state3(struct ast_str *hint_app, struct ao2_container *device_state_info)
 {
 	char *cur;
 	char *rest;
@@ -4569,14 +4584,28 @@
 
 	ast_devstate_aggregate_init(&agg);
 	while ((cur = strsep(&rest, "&"))) {
-		ast_devstate_aggregate_add(&agg, ast_device_state(cur));
+		enum ast_device_state state = ast_device_state(cur);
+
+		ast_devstate_aggregate_add(&agg, state);
+		if (device_state_info) {
+			struct ast_device_state_info *obj;
+
+			obj = ao2_alloc_options(sizeof(*obj) + strlen(cur), device_state_info_dt, AO2_ALLOC_OPT_LOCK_NOLOCK);
+			/* if failed we cannot add this device */
+			if (obj) {
+				obj->device_state = state;
+				strcpy(obj->device_name, cur);
+				ao2_link(device_state_info, obj);
+				ao2_ref(obj, -1);
+			}
+		}
 	}
 
 	return ast_devstate_to_extenstate(ast_devstate_aggregate_result(&agg));
 }
 
 /*! \brief Check state of extension by using hints */
-static int ast_extension_state2(struct ast_exten *e)
+static int ast_extension_state2(struct ast_exten *e, struct ao2_container *device_state_info)
 {
 	struct ast_str *hint_app = ast_str_thread_get(&extensionstate_buf, 32);
 
@@ -4585,7 +4614,7 @@
 	}
 
 	ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(e));
-	return ast_extension_state3(hint_app);
+	return ast_extension_state3(hint_app, device_state_info);
 }
 
 /*! \brief Return extension_state as string */
@@ -4600,8 +4629,9 @@
 	return "Unknown";
 }
 
-/*! \brief Check extension state for an extension by using hint */
-int ast_extension_state(struct ast_channel *c, const char *context, const char *exten)
+/*! \internal \brief Check extension state for an extension by using hint */
+static int internal_extension_state_extended(struct ast_channel *c, const char *context, const char *exten,
+	struct ao2_container *device_state_info)
 {
 	struct ast_exten *e;
 
@@ -4620,7 +4650,38 @@
 		}
 	}
 
-	return ast_extension_state2(e);  /* Check all devices in the hint */
+	return ast_extension_state2(e, device_state_info);  /* Check all devices in the hint */
+}
+
+/*! \brief Check extension state for an extension by using hint */
+int ast_extension_state(struct ast_channel *c, const char *context, const char *exten)
+{
+	return internal_extension_state_extended(c, context, exten, NULL);
+}
+
+/*! \brief Check extended extension state for an extension by using hint */
+int ast_extension_state_extended(struct ast_channel *c, const char *context, const char *exten,
+	struct ao2_container **device_state_info)
+{
+	struct ao2_container *container = NULL;
+	int ret;
+
+	if (device_state_info) {
+		container = alloc_device_state_info();
+	}
+
+	ret = internal_extension_state_extended(c, context, exten, container);
+	if (ret < 0 && container) {
+		ao2_ref(container, -1);
+		container = NULL;
+	}
+
+	if (device_state_info) {
+		get_device_state_causing_channels(container);
+		*device_state_info = container;
+	}
+
+	return ret;
 }
 
 static int extension_presence_state_helper(struct ast_exten *e, char **subtype, char **message)
@@ -4676,7 +4737,8 @@
 	const char *exten,
 	void *data,
 	enum ast_state_cb_update_reason reason,
-	struct ast_hint *hint)
+	struct ast_hint *hint,
+	struct ao2_container *device_state_info)
 {
 	int res = 0;
 	struct ast_state_cb_info info = { 0, };
@@ -4687,6 +4749,7 @@
 	if (hint) {
 		ao2_lock(hint);
 		info.exten_state = hint->laststate;
+		info.device_state_info = device_state_info;
 		info.presence_state = hint->last_presence_state;
 		if (!(ast_strlen_zero(hint->last_presence_subtype))) {
 			info.presence_subtype = ast_strdupa(hint->last_presence_subtype);
@@ -4798,7 +4861,8 @@
 				exten_name,
 				state_cb->data,
 				AST_HINT_UPDATE_PRESENCE,
-				hint);
+				hint,
+				NULL);
 		}
 		ao2_iterator_destroy(&cb_iter);
 
@@ -4810,7 +4874,8 @@
 				exten_name,
 				state_cb->data,
 				AST_HINT_UPDATE_PRESENCE,
-				hint);
+				hint,
+				NULL);
 		}
 		ao2_iterator_destroy(&cb_iter);
 	}
@@ -4824,6 +4889,87 @@
 	ao2_ref(pc, -1);
 
 	return res;
+}
+
+/*!
+ * /internal
+ * /brief Identify a channel for every device which is supposedly responsible for the device state.
+ *
+ * Especially when the device is ringing, the oldest ringing channel is chosen.
+ * For all other cases the first encountered channel in the specific state is chosen.
+ */
+static void get_device_state_causing_channels(struct ao2_container *c)
+{
+	struct ao2_iterator iter;
+	struct ast_device_state_info *info;
+	struct ast_channel *chan;
+
+	if (!c || !ao2_container_count(c)) {
+		return;
+	}
+	iter = ao2_iterator_init(c, 0);
+	for (; (info = ao2_iterator_next(&iter)); ao2_ref(info, -1)) {
+		enum ast_channel_state search_state = 0; /* prevent false uninit warning */
+		char match[AST_CHANNEL_NAME];
+		struct ast_channel_iterator *chan_iter;
+		struct timeval chantime = {0, }; /* prevent false uninit warning */
+
+		switch (info->device_state) {
+		case AST_DEVICE_RINGING:
+		case AST_DEVICE_RINGINUSE:
+			/* find ringing channel */
+			search_state = AST_STATE_RINGING;
+			break;
+		case AST_DEVICE_BUSY:
+			/* find busy channel */
+			search_state = AST_STATE_BUSY;
+			break;
+		case AST_DEVICE_ONHOLD:
+		case AST_DEVICE_INUSE:
+			/* find up channel */
+			search_state = AST_STATE_UP;
+			break;
+		case AST_DEVICE_UNKNOWN:
+		case AST_DEVICE_NOT_INUSE:
+		case AST_DEVICE_INVALID:
+		case AST_DEVICE_UNAVAILABLE:
+		case AST_DEVICE_TOTAL /* not a state */:
+			/* no channels are of interest */
+			continue;
+		}
+
+		/* iterate over all channels of the device */
+	        snprintf(match, sizeof(match), "%s-", info->device_name);
+		chan_iter = ast_channel_iterator_by_name_new(match, strlen(match));
+		for (; (chan = ast_channel_iterator_next(chan_iter)); ast_channel_unref(chan)) {
+			ast_channel_lock(chan);
+			/* this channel's state doesn't match */
+			if (search_state != ast_channel_state(chan)) {
+				ast_channel_unlock(chan);
+				continue;
+			}
+			/* any non-ringing channel will fit */
+			if (search_state != AST_STATE_RINGING) {
+				ast_channel_unlock(chan);
+				info->causing_channel = chan; /* is kept ref'd! */
+				break;
+			}
+			/* but we need the oldest ringing channel of the device to match with undirected pickup */
+			if (!info->causing_channel) {
+				chantime = ast_channel_creationtime(chan);
+				ast_channel_ref(chan); /* must ref it! */
+				info->causing_channel = chan;
+			} else if (ast_tvcmp(ast_channel_creationtime(chan), chantime) < 0) {
+				chantime = ast_channel_creationtime(chan);
+				ast_channel_unref(info->causing_channel);
+				ast_channel_ref(chan); /* must ref it! */
+				info->causing_channel = chan;
+			}
+			ast_channel_unlock(chan);
+		}
+		ast_channel_iterator_destroy(chan_iter);
+	}
+	ao2_iterator_destroy(&iter);
 }
 
 static int handle_statechange(void *datap)
@@ -4869,6 +5015,9 @@
 	for (; (device = ao2_iterator_next(dev_iter)); ao2_t_ref(device, -1, "Next device")) {
 		struct ast_state_cb *state_cb;
 		int state;
+		int same_state;
+		struct ao2_container *device_state_info;
+		int first_extended_cb_call = 1;
 
 		if (!device->hint) {
 			/* Should never happen. */
@@ -4902,8 +5051,13 @@
 		 * device state or notifying the watchers without causing a
 		 * deadlock.  (conlock, hints, and hint)
 		 */
-		state = ast_extension_state3(hint_app);
-		if (state == hint->laststate) {
+		/* Make a container so state3 can fill it if we wish.
+		 * If that failed we simply do not provide the extended state info.
+		 */
+		device_state_info = alloc_device_state_info();
+		state = ast_extension_state3(hint_app, device_state_info);
+		if ((same_state = state == hint->laststate) && (~state & AST_EXTENSION_RINGING)) {
+			ao2_cleanup(device_state_info);
 			continue;
 		}
 
@@ -4912,27 +5066,41 @@
 
 		/* For general callbacks */
 		cb_iter = ao2_iterator_init(statecbs, 0);
-		for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+		for (; !same_state && (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
 			execute_state_callback(state_cb->change_cb,
 				context_name,
 				exten_name,
 				state_cb->data,
 				AST_HINT_UPDATE_DEVICE,
-				hint);
+				hint,
+				NULL);
 		}
 		ao2_iterator_destroy(&cb_iter);
 
 		/* For extension callbacks */
+		/* extended callbacks are called when the state changed or when AST_EVENT_RINGING is
+		 * included. Normal callbacks are only called when the state changed.
+		 */
 		cb_iter = ao2_iterator_init(hint->callbacks, 0);
 		for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
-			execute_state_callback(state_cb->change_cb,
-				context_name,
-				exten_name,
-				state_cb->data,
-				AST_HINT_UPDATE_DEVICE,
-				hint);
+			if (state_cb->extended && first_extended_cb_call) {
+				/* Fill detailed device_state_info now that we know it is used by extd. callback */
+				first_extended_cb_call = 0;
+				get_device_state_causing_channels(device_state_info);
+			}
+			if (state_cb->extended || !same_state) {
+				execute_state_callback(state_cb->change_cb,
+					context_name,
+					exten_name,
+					state_cb->data,
+					AST_HINT_UPDATE_DEVICE,
+					hint,
+					state_cb->extended ? device_state_info : NULL);
+			}
 		}
 		ao2_iterator_destroy(&cb_iter);
+
+		ao2_cleanup(device_state_info);
 	}
 	ast_mutex_unlock(&context_merge_lock);
 
@@ -4959,9 +5127,9 @@
 	}
 }
 
-/*! \brief Add watcher for extension states with destructor */
-int ast_extension_state_add_destroy(const char *context, const char *exten,
-	ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
+/*! \internal \brief Add watcher for extension states with destructor */
+static int extension_state_add_destroy(const char *context, const char *exten,
+	ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data, int extended)
 {
 	struct ast_hint *hint;
 	struct ast_state_cb *state_cb;
@@ -4985,6 +5153,7 @@
 		state_cb->change_cb = change_cb;
 		state_cb->destroy_cb = destroy_cb;
 		state_cb->data = data;
+		state_cb->extended = extended;
 		ao2_link(statecbs, state_cb);
 
 		ao2_ref(state_cb, -1);
@@ -5037,6 +5206,7 @@
 	state_cb->change_cb = change_cb;	/* Pointer to callback routine */
 	state_cb->destroy_cb = destroy_cb;
 	state_cb->data = data;		/* Data for the callback */
+	state_cb->extended = extended;
 	ao2_link(hint->callbacks, state_cb);
 
 	ao2_ref(state_cb, -1);
@@ -5046,11 +5216,32 @@
 	return id;
 }
 
+/*! \brief Add watcher for extension states with destructor */
+int ast_extension_state_add_destroy(const char *context, const char *exten,
+	ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
+{
+	return extension_state_add_destroy(context, exten, change_cb, destroy_cb, data, 0);
+}
+
 /*! \brief Add watcher for extension states */
 int ast_extension_state_add(const char *context, const char *exten,
 	ast_state_cb_type change_cb, void *data)
 {
-	return ast_extension_state_add_destroy(context, exten, change_cb, NULL, data);
+	return extension_state_add_destroy(context, exten, change_cb, NULL, data, 0);
+}
+
+/*! \brief Add watcher for extended extension states with destructor */
+int ast_extension_state_add_destroy_extended(const char *context, const char *exten,
+	ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
+{
+	return extension_state_add_destroy(context, exten, change_cb, destroy_cb, data, 1);
+}
+
+/*! \brief Add watcher for extended extension states */
+int ast_extension_state_add_extended(const char *context, const char *exten,
+	ast_state_cb_type change_cb, void *data)
+{
+	return extension_state_add_destroy(context, exten, change_cb, NULL, data, 1);
 }
 
 /*! \brief Find Hint by callback id */
@@ -5144,7 +5335,8 @@
 				exten_name,
 				state_cb->data,
 				AST_HINT_UPDATE_DEVICE,
-				hint);
+				hint,
+				NULL);
 			ao2_ref(state_cb, -1);
 		}
 		ao2_ref(hint->callbacks, -1);
@@ -5218,7 +5410,7 @@
 		return -1;
 	}
 	hint_new->exten = e;
-	hint_new->laststate = ast_extension_state2(e);
+	hint_new->laststate = ast_extension_state2(e, NULL);
 	if ((presence_state = extension_presence_state_helper(e, &subtype, &message)) > 0) {
 		hint_new->last_presence_state = presence_state;
 		hint_new->last_presence_subtype = subtype;
@@ -8381,6 +8573,7 @@
 				saved_hint->exten,
 				thiscb->data,
 				AST_HINT_UPDATE_DEVICE,
+				NULL,
 				NULL);
 			/* Ref that we added when putting into saved_hint->callbacks */
 			ao2_ref(thiscb, -1);




More information about the asterisk-commits mailing list