[asterisk-commits] kharwell: branch kharwell/pimp_sip_state r387543 - /team/kharwell/pimp_sip_st...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Thu May 2 17:59:15 CDT 2013


Author: kharwell
Date: Thu May  2 17:59:14 2013
New Revision: 387543

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=387543
Log:
compiling now yay. hopefully the bulk of the code is in, but haven't tested yet.

Modified:
    team/kharwell/pimp_sip_state/res/res_sip_exten_state.c

Modified: team/kharwell/pimp_sip_state/res/res_sip_exten_state.c
URL: http://svnview.digium.com/svn/asterisk/team/kharwell/pimp_sip_state/res/res_sip_exten_state.c?view=diff&rev=387543&r1=387542&r2=387543
==============================================================================
--- team/kharwell/pimp_sip_state/res/res_sip_exten_state.c (original)
+++ team/kharwell/pimp_sip_state/res/res_sip_exten_state.c Thu May  2 17:59:14 2013
@@ -36,33 +36,36 @@
 #include "asterisk/astobj2.h"
 #include "asterisk/sorcery.h"
 #include "asterisk/app.h"
+#include "asterisk/pbx.h"
 
 /*!
  * \internal
  * \brief Extension state notifier.
  */
 struct exten_state_notifier {
-	/*! The name of the notifier */
-	const char *name;
 	/*! The name of the event this notifier registers for */
 	const char *event_name;
 	/*! The type of body this notifier accepts */
 	const char *accept;
 
 	/*!
-	 * \brief Called before sending the notify request.
+	 * \brief Create the body text of a NOTIFY request.
 	 *
-	 * Allows the notifier to set data on the outgoing request.
+	 * Implementors use this to create body information within the given
+	 * ast_str.  That information is then added to the NOTIFY request.
 	 */
-	void (*set_tdata)(pjsip_tx_data *tdata);
+	void (*create_body)(struct ao2_container *info, struct ast_str *body_text);
+
+	/*! Next item in the list */
+	AST_LIST_ENTRY(exten_state_notifier) next;
 };
 
-AST_RWLIST_HEAD_STATIC(notifiers, exten_state_notifer);
+AST_RWLIST_HEAD_STATIC(notifiers, exten_state_notifier);
 
 static int register_exten_state_notifier(struct exten_state_notifier *obj)
 {
 	SCOPED_LOCK(lock, &notifiers, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
-	AST_LIST_INSERT_HEAD(&notifiers, obj);
+	AST_LIST_INSERT_TAIL(&notifiers, obj, next);
 	ast_module_ref(ast_module_info->self);
 	return 0;
 }
@@ -71,7 +74,7 @@
 {
 	struct exten_state_notifier *i;
 	SCOPED_LOCK(lock, &notifiers, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
-	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&notifers, i, next) {
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&notifiers, i, next) {
 		if (i == obj) {
 			AST_RWLIST_REMOVE_CURRENT(next);
 			ast_module_unref(ast_module_info->self);
@@ -81,12 +84,16 @@
 	AST_RWLIST_TRAVERSE_SAFE_END;
 }
 
+/*!
+ * \internal
+ * \brief Find a notifier based on the given accept type.
+ */
 static struct exten_state_notifier *notifier_by_accept(const char *accept)
 {
 	struct exten_state_notifier *res = NULL;
 	struct exten_state_notifier *i;
 	SCOPED_LOCK(lock, &notifiers, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
-	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&notifers, i, next) {
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&notifiers, i, next) {
 		if (!strcmp(i->accept, accept)) {
 			res = i;
 			break;
@@ -94,6 +101,35 @@
 	}
 	AST_RWLIST_TRAVERSE_SAFE_END;
 	return res;
+}
+
+/*!
+ * \internal
+ * \brief Find a notifier based on a given message accept type.
+ *
+ * Attempts to locate accept headers in the given request data. If found
+ * tries to find a notifier for the accept type.
+ */
+static struct exten_state_notifier *notifier_by_req(pjsip_rx_data *rdata)
+{
+	int i;
+	char type[50];
+	pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)
+		pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL);
+	struct exten_state_notifier *res;
+
+	if (!hdr) {
+		ast_log(LOG_WARNING, "No Accept header found in subscribe\n");
+		return NULL;
+	}
+
+	for (i = 0; i < hdr->count; ++i) {
+		ast_copy_pj_str(type, &hdr->values[i], sizeof(type));
+		if ((res = notifier_by_accept(type))) {
+			return res;
+		}
+	}
+	return NULL;
 }
 
 static int sub_handler_hash(const void *obj, int flags)
@@ -127,11 +163,19 @@
 	struct ast_sip_subscription *sip_sub;
 	/*! The notify data creator */
 	struct exten_state_notifier *notifier;
+	/*! Context in which subscription looks for updates */
+	const char *context;
+	/*! Extension within the context to receive updates from */
+	char exten[AST_MAX_EXTENSION];
+	/*! Last state information set during state change callback */
+	struct ao2_container *state_info;
+	/*! State when sending an serialized notify */
+	pjsip_evsub_state state;
 };
 
 static void state_sub_destructor(void *obj)
 {
-	struct exten_state_sub *sub = obj;
+	struct state_sub *sub = obj;
 	ao2_cleanup(sub->sip_sub);
 }
 
@@ -142,12 +186,12 @@
  * Attempts to locate an event header in the given request data. If found
  * tries to find a subscription handler for the event type.
  */
-static struct sub_handler *sub_handler_by_req(pjsip_rx_data *rdata)
+static struct ast_sip_subscription_handler *sub_handler_by_req(pjsip_rx_data *rdata)
 {
 	char type[25];
-	pjsip_str_t event_name = { "Event", 5 }
+	pj_str_t event_name = { "Event", 5 };
 	pjsip_event_hdr *hdr = (pjsip_event_hdr*)
-		pjsip_msg_find_hdr_by_name(rdata->msg, &event_name, NULL);
+		pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &event_name, NULL);
 
 	if (!hdr) {
 		ast_log(LOG_WARNING, "No Event header found in subscribe\n");
@@ -156,57 +200,28 @@
 
 	ast_copy_pj_str(type, &hdr->event_type, sizeof(type));
 
-	return (struct sub_handler*)ao2_find(sub_handlers, 
-					     type, OBJ_KEY | OBJ_NOLOCK);
-}
-
-/*!
- * \internal
- * \brief Find a notifier for based on a given message accept type.
- *
- * Attempts to locate accept headers in the given request data. If found
- * tries to find a notifier for the accept type.
- */
-static struct exten_state_notifer *notifier_by_req(pjsip_rx_data *rdata)
-{
-	int i, j;
-	char type[50];
-	pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)
-		pjsip_msg_find_hdr(rdata->msg, PJSIP_H_ACCEPT, NULL);
-	struct exten_state_notifer *res;
-
-	if (!hdr) {
-		ast_log(LOG_WARNING, "No Accept header found in subscribe\n");
-		return NULL;
-	}
-
-	for (i = 0; i < hdr->count; ++i) {
-		ast_copy_pj_str(type, &hdr->values[i], sizeof(type));
-		if ((res = notifier_by_accept(type))) {
-			return res;
-		}
-	}
-	return NULL;
-}
-
-/*!
- * \internal
- * \brief Allocates an exten_state_sub object.
+	return (struct ast_sip_subscription_handler*)
+		ao2_find(sub_handlers, type, OBJ_KEY | OBJ_NOLOCK);
+}
+
+/*!
+ * \internal
+ * \brief Allocates an state_sub object.
  *
  * Creates the underlying SIP subscription for the given request. First makes
  * sure that there are registered handler and notifier objects available.
  */
-static struct exten_state_sub *state_sub_alloc(struct ast_sip_endpoint *endpoint,
+static struct state_sub *state_sub_alloc(struct ast_sip_endpoint *endpoint,
 		enum ast_sip_subscription_role role, pjsip_rx_data *rdata)
 {
-	struct sub_handler *handler;
-	struct exten_state_sub *sub;
+	struct ast_sip_subscription_handler *handler;
+	struct state_sub *sub;
 
 	if (!(sub = ao2_alloc(sizeof(*sub), state_sub_destructor))) {
 		return NULL;
 	}
 
-	if (!(sub_handler = sub_handler_by_req(rdata))) {
+	if (!(handler = sub_handler_by_req(rdata))) {
 		ast_log(LOG_WARNING, "Handler not found for subscription event\n");
 		ao2_cleanup(sub);
 		return NULL;
@@ -218,9 +233,9 @@
 		return NULL;
 	}
 
-	if (!(sub->sip_sub = ast_sip_create_subscription(sub_handler, role, endpoint, rdata))) {
+	if (!(sub->sip_sub = ast_sip_create_subscription(handler, role, endpoint, rdata))) {
 		ast_log(LOG_WARNING, "Unable to create SIP subscription for endpoint %s\n",
-			ast_sorcery_object_get_id(endpoint)->id);
+			ast_sorcery_object_get_id(endpoint));
 		ao2_cleanup(sub);
 		return NULL;
 	}
@@ -228,149 +243,229 @@
 	return sub;
 }
 
-static void send_notify(struct state_sub *sub, pjsip_evsub_state state, const char *reason)
-{
-	pjsip_media_type body_type;
+/*!
+ * \internal
+ * \brief Create and send a NOTIFY request to the subscriber.
+ */
+static void create_send_notify(struct state_sub *sub, pjsip_evsub_state state,
+			       const char *reason, struct ao2_container *info)
+{
+	RAII_VAR(struct ast_str *, body_text, ast_str_create(512), ast_free_ptr);
+	pj_str_t reason_str;
+	const pj_str_t *reason_str_ptr = NULL;
+	pjsip_tx_data *tdata;
+	struct ast_sip_body body;
 	char type[50];
-	char *subtype;
-
-	ast_copy_string(type, sub->notifier->accept, sizeof(type));
-	if ((subtype = strchr(type, '/'))) {
-		*subtype++ = '\0';
-		body_type.subtype = pj_str_t(subtype);
-	}
-
-	body_type.type = pj_str_t(type);
-
-	RAII_VAR(struct ast_str *, body, ast_str_create(64), ast_free_ptr);
-	pjsip_tx_data *tdata;
-	pj_str_t reason_str;
-	pj_str_t pj_body;
-
-	ao2_callback(sub->stasis_subs, OBJ_NODATA, get_message_count, &counter);
+
+	if (!(body.subtype = strchr(sub->notifier->accept, '/'))) {
+		ast_log(LOG_WARNING, "Subtype not specified for notifier\n");
+		return;
+	}
+
+	ast_copy_string(type, sub->notifier->accept,
+			body.subtype - sub->notifier->accept);
+	body.type = type;
+	++body.subtype;	
+
+	if (!sub->notifier->create_body) {
+		ast_log(LOG_WARNING, "Body handler not specified for notifier\n");
+		return;
+	}
+
+	sub->notifier->create_body(info, body_text);
+	body.body_text = ast_str_buffer(body_text);
 
 	if (reason) {
 		pj_cstr(&reason_str, reason);
 		reason_str_ptr = &reason_str;
 	}
-	ast_str_append(&body, 0, "Messages-Waiting: %s\r\n", counter.new_msgs ? "yes" : "no");
-	ast_str_append(&body, 0, "Voice-Message: %d/%d (0/0)\r\n", counter.new_msgs, counter.old_msgs);
-	pj_cstr(&pj_body, ast_str_buffer(body));
 
 	if (pjsip_evsub_notify(ast_sip_subscription_get_evsub(sub->sip_sub), state,
-			       NULL, reason ? pj_str(reason) : NULL, &tdata) != PJ_SUCCESS) {
-		ast_log(LOG_WARNING, "Unable to create NOTIFY request to %s.\n", sub->id);
-		return;
-	}
-
-	if (sub->notifier->set_data) {
-		sub->notifier->set_data(tdata);
+			       NULL, reason_str_ptr, &tdata) != PJ_SUCCESS) {
+		ast_log(LOG_WARNING, "Unable to create NOTIFY request\n");
+		return;
+	}
+
+	if (ast_sip_add_body(tdata, &body)) {
+		ast_log(LOG_WARNING, "Unable to add body to NOTIFY request\n");
+		pjsip_tx_data_dec_ref(tdata);
+		return;
 	}
 
 	if (ast_sip_subscription_send_request(sub->sip_sub, tdata) != PJ_SUCCESS) {
-		ast_log(LOG_WARNING, "Unable to send MWI NOTIFY request to %s\n", sub->id);
+		ast_log(LOG_WARNING, "Unable to send NOTIFY request\n");
 		pjsip_tx_data_dec_ref(tdata);
 	}
 }
 
 /*!
  * \internal
+ * \brief Get device state information and send notification to the subscriber.
+ */
+static void send_notify(struct state_sub *ssub, pjsip_evsub_state state, const char *reason)
+{
+	RAII_VAR(struct ao2_container*, info, NULL, ao2_cleanup);
+
+	if (ast_extension_state_extended(NULL, ssub->context, ssub->exten, &info)) {
+		ast_log(LOG_WARNING, "Unable to get device info for extension %s\n", ssub->exten);
+		return;
+	}
+
+	create_send_notify(ssub, state, reason, info);
+}
+
+static int serialized_notify(void *data)
+{
+	struct state_sub *ssub = data;
+	create_send_notify(ssub, ssub->state, NULL, ssub->state_info);
+	ao2_ref(ssub, -1);
+	return 0;
+}
+
+/*!
+ * \internal
  * \brief Callback for exten/device state changes.
  *
- * Upon state change, unless it has been deactivated or removed then send the
- * appropriate notification to the subscriber.
+ * Upon state change, send the appropriate notification to the subscriber.
  */
 static int state_changed(char *context, char *exten, 
 			 struct ast_state_cb_info *info, void *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,
-	};
-
-	switch (notify_data->state) {
-	case AST_EXTENSION_DEACTIVATED:
-	case AST_EXTENSION_REMOVED:
-		ast_verb(2, "Watcher for hint %s %s\n", exten, data->state
+	struct state_sub *ssub = data;
+
+	if ((info->exten_state == AST_EXTENSION_DEACTIVATED) ||
+	    (info->exten_state == AST_EXTENSION_REMOVED)) {
+		ssub->state = PJSIP_EVSUB_STATE_TERMINATED;
+		ast_log(LOG_NOTICE, "Watcher for hint %s %s\n", exten, info->exten_state
 			 == AST_EXTENSION_REMOVED ? "removed" : "deactivated");
-		break;
-	default:
-
-	}
-
-	return extensionstate_update(context, exten, &notify_data, p, FALSE);
-}
-
-/*!
- * \internal
- * \brief Callback for exten/device state destruction.
- */
-static void state_destroyed(int id, void *data)
-{
-	struct state_sub *sub = data;
-	ao2_cleanup(sub);
+	}
+
+	ao2_ref(ssub, +1);
+	ssub->state = PJSIP_EVSUB_STATE_ACTIVE;
+	ssub->state_info = info->device_state_info;
+	ast_sip_push_task(ast_sip_subscription_get_serializer(ssub->sip_sub),
+			  serialized_notify, ssub);
+	return 0;
+}
+
+static struct ast_datastore_info ds_info = { };
+static const char ds_name[] = "exten state datastore";
+
+/*!
+ * \internal
+ * \brief Add a datastore for exten state_sub.
+ *
+ * Adds the state_sub wrapper object to a datastore so it can be retrieved
+ * later based upon its association with the ast_sip_subscription.
+ */
+static int add_datastore(struct state_sub *sub)
+{
+	RAII_VAR(struct ast_datastore *, datastore, 
+		 ast_sip_subscription_alloc_datastore(&ds_info, ds_name), ao2_cleanup);
+
+	if (!datastore) {
+		return -1;
+	}
+
+	datastore->data = sub;
+	ast_sip_subscription_add_datastore(sub->sip_sub, datastore);
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Get the state_sub object associated with the given
+ * ast_sip_subscription in the datastore.
+ */
+static struct state_sub *from_datastore(struct ast_sip_subscription *sub)
+{
+	RAII_VAR(struct ast_datastore *, datastore,
+		 ast_sip_subscription_get_datastore(sub, ds_name), ao2_cleanup);
+
+	return datastore ? datastore->data : NULL;
 }
 
 static void subscription_shutdown(struct ast_sip_subscription *sub)
 {
-	/* nothing to do? */
+	struct state_sub *ssub = from_datastore(sub);
+
+	if (ssub) {
+		ao2_cleanup(ssub);
+	}
 }
 
 static struct ast_sip_subscription *new_subscribe(struct ast_sip_endpoint *endpoint,
 						  pjsip_rx_data *rdata)
 {
-	RAII_VAR(struct exten_subscription *, sub, NULL, ao2_cleanup);
 	pjsip_uri *uri = rdata->msg_info.msg->line.req.uri;
-	pjsip_sip_uri sip_uri = pjsip_uri_get_uri(ruri);
-	char exten[AST_MAX_EXTENSION];
-	char hint[AST_MAX_EXTENSION];
+	pjsip_sip_uri *sip_uri = pjsip_uri_get_uri(uri);
+	struct state_sub *ssub;
 
 	if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri)) {
 		ast_log(LOG_WARNING, "Attempt to SUBSCRIBE to a non-SIP URI\n");
 		return NULL;
 	}
 
-	ast_copy_pj_str(exten, &sip_uri->user, sizeof(exten));
-
-	if (!ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, endpoint->context, exten)) {
-		ast_log(LOG_WARNING, "Hint does not exists for extension %s\n", exten);
-		return NULL:
-	}
-
-	if (!(sub = state_sub_alloc(endpoint, AST_SIP_NOTIFIER, rdata))) {
-		return NULL;
-	}
-
-	if (ast_extension_state_add_destroy(endpoint->context, exten,
-					    state_changed, state_destroyed, sub)) {
-		ast_log(LOG_WARNING, "Unable to subscribe to hint in context % extension %s\n",
-			endpoint->context, exten);
-		ao2_cleanup(sub);
-		return NULL;
-	}
-
-	pjsip_evsub_accept(ast_sip_subscription_get_evsub(sub->sip_sub),
+	if (!(ssub = state_sub_alloc(endpoint, AST_SIP_NOTIFIER, rdata))) {
+		return NULL;
+	}
+
+	ssub->context = endpoint->context;
+	ast_copy_pj_str(ssub->exten, &sip_uri->user, sizeof(ssub->exten));
+
+	if (ast_extension_state_add(ssub->context, ssub->exten, state_changed, ssub)) {
+		ast_log(LOG_WARNING, "Unable to subscribe extension %s updates\n",
+			ssub->exten);
+		ao2_cleanup(ssub);
+		return NULL;
+	}
+
+	if (add_datastore(ssub)) {
+		ast_log(LOG_WARNING, "Unable to add to subscription datastore.\n");
+		ao2_cleanup(ssub);
+		return NULL;
+	}
+
+	pjsip_evsub_accept(ast_sip_subscription_get_evsub(ssub->sip_sub),
 			   rdata, 200, NULL);
-	send_notify(sub, PJSIP_EVSUB_STATE_ACTIVE, NULL);
-
-	return sub->sip_sub;
+
+	send_notify(ssub, PJSIP_EVSUB_STATE_ACTIVE, NULL);
+	return ssub->sip_sub;
 }
 
 static void resubscribe(struct ast_sip_subscription *sub, pjsip_rx_data *rdata,
 			struct ast_sip_subscription_response_data *response_data)
 {
+	struct state_sub *ssub = from_datastore(sub);
+
+	if (ssub) {
+		send_notify(ssub, PJSIP_EVSUB_STATE_ACTIVE, NULL);
+	}
 }
 
 static void subscription_timeout(struct ast_sip_subscription *sub)
 {
+	struct state_sub *ssub = from_datastore(sub);
+
+	if (!ssub) {
+		return;
+	}
+
+	ast_log(LOG_NOTICE, "Subscription for has timed out.\n");
+	send_notify(ssub, PJSIP_EVSUB_STATE_TERMINATED, "timeout");
 }
 
 static void subscription_terminated(struct ast_sip_subscription *sub,
 				    pjsip_rx_data *rdata)
 {
+	struct state_sub *ssub = from_datastore(sub);
+
+	if (!ssub) {
+		return;
+	}
+
+	ast_log(LOG_NOTICE, "Subscription has been terminated\n");
+	send_notify(ssub, PJSIP_EVSUB_STATE_TERMINATED, NULL);
 }
 
 static void notify_response(struct ast_sip_subscription *sub, pjsip_rx_data *rdata)
@@ -384,7 +479,7 @@
 	/* nothing to do? */
 }
 
-static int refresh_subcription(struct ast_sip_subscription *sub)
+static int refresh_subscription(struct ast_sip_subscription *sub)
 {
 	/* nothing to do? */
 	return 0;
@@ -394,7 +489,7 @@
  * \internal
  * \brief Checks to see if the given value is in the given accept array.
  */
-static int has_accept(const char accept[], const char *value)
+static int has_accept(const char *accept[], const char *value)
 {
 	int i;
 	/* search all items or until we find a null/empty element */
@@ -413,7 +508,7 @@
  * \brief Add given accept to 'dest' if 'dest' does not already contain
  *        the accept type.
  */
-static void add_accept(char* dest[], const char* accept)
+static void add_accept(const char* dest[], const char* accept)
 {
 	int i = 0;
 
@@ -480,13 +575,13 @@
  * If a handler already exists for an event_name then if the accept value
  * has not been added to that handler it will be.
  */
-static void create_sub_handlers()
+static void create_sub_handlers(void)
 {
 	struct exten_state_notifier *i;
 	SCOPED_LOCK(lock, &notifiers, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
-	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&notifers, i, next) {
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&notifiers, i, next) {
 		struct ast_sip_subscription_handler *handler =
-			ao2_find(sub_handlers, n->event_name, OBJ_NOLOCK | OBJ_KEY);
+			ao2_find(sub_handlers, i->event_name, OBJ_NOLOCK | OBJ_KEY);
 
 		if (handler) {
 			/* if handler exists just assign those accept values to
@@ -497,7 +592,7 @@
 
 		/* if handler does not exist yet, create it and add to 
 		   container - note accept values assigned in create */
-		if ((handler = create_sub_handler(event_name))) {
+		if ((handler = create_sub_handler(i->event_name, i->accept))) {
 			ao2_link(sub_handlers, handler);
 		}
 	}
@@ -528,9 +623,10 @@
  * \internal
  * \brief Unregister a subscription handler with the pub/sub framework.
  */
-static void unregister_sub_handlers(void *obj, void *arg, int flags)
+static int unregister_sub_handlers(void *obj, void *arg, int flags)
 {
 	ast_sip_unregister_subscription_handler(obj);
+	return 0;
 }
 
 static int load_module(void)
@@ -545,7 +641,7 @@
 	/* register notifiers - for now they have to be specifically added here
 	   because of a limitation in pjsip. if pjsip ever gets modified these
 	   can be moved to their respective module files */
-	/* register_exten_state_notifier(obj) */
+	register_exten_state_notifier(NULL);
 
 	/* create the subscription handlers based upon the notifiers */
 	create_sub_handlers();
@@ -564,7 +660,7 @@
 		     unregister_sub_handlers, NULL);
 
 	/* unregister notifiers here, again due to pjsip limitation */
-	/* unregister_exten_state_notifier(obj) */
+	unregister_exten_state_notifier(NULL);
 
 	ao2_cleanup(sub_handlers);
 




More information about the asterisk-commits mailing list