[asterisk-commits] file: branch file/chan_jingle2 r367117 - in /team/file/chan_jingle2: channels...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Fri May 18 18:45:16 CDT 2012


Author: file
Date: Fri May 18 18:45:04 2012
New Revision: 367117

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=367117
Log:
Incorporate changes based on review feedback.

Modified:
    team/file/chan_jingle2/channels/chan_jingle2.c
    team/file/chan_jingle2/configs/jingle2.conf.sample

Modified: team/file/chan_jingle2/channels/chan_jingle2.c
URL: http://svnview.digium.com/svn/asterisk/team/file/chan_jingle2/channels/chan_jingle2.c?view=diff&rev=367117&r1=367116&r2=367117
==============================================================================
--- team/file/chan_jingle2/channels/chan_jingle2.c (original)
+++ team/file/chan_jingle2/channels/chan_jingle2.c Fri May 18 18:45:04 2012
@@ -31,8 +31,8 @@
 	<depend>iksemel</depend>
 	<depend>res_jabber</depend>
 	<use type="external">openssl</use>
-	<defaultenabled>no</defaultenabled>
-	<support_level>extended</support_level>
+	<defaultenabled>yes</defaultenabled>
+	<support_level>core</support_level>
  ***/
 
 #include "asterisk.h"
@@ -70,33 +70,14 @@
 #include "asterisk/abstract_jb.h"
 #include "asterisk/jabber.h"
 
-/*! \brief Default jitterbuffer configuration */
-static struct ast_jb_conf default_jbconf =
-{
-	.flags = 0,
-	.max_size = -1,
-	.resync_threshold = -1,
-	.impl = "",
-	.target_extra = -1,
-};
-
-/*! \brief Global jitterbuffer configuration */
-static struct ast_jb_conf global_jbconf;
-
 /*! \brief Default filename for the configuration file */
 #define JINGLE_CONFIG "jingle2.conf"
 
-/*! \brief Maximum size for a username */
-#define MAX_SIZE_USERNAME 100
-
-/*! \brief Maximum size for a session identifier */
-#define MAX_SIZE_SID 100
-
-/*! \brief Maximum number of ICE candidates we will offer */
-#define MAX_ICE_CANDIDATES 10
-
-/*! \brief Maximum number of payloads we will offer */
-#define MAX_PAYLOADS 30
+/*! \brief Default maximum number of ICE candidates we will offer */
+#define DEFAULT_MAX_ICE_CANDIDATES 10
+
+/*! \brief Default maximum number of payloads we will offer */
+#define DEFAULT_MAX_PAYLOADS 30
 
 /*! \brief Number of buckets for endpoints */
 #define ENDPOINT_BUCKETS 37
@@ -144,9 +125,13 @@
 
 /*! \brief Endpoint which contains configuration information and active sessions */
 struct jingle_endpoint {
-	char name[MAX_SIZE_USERNAME];           /*!< Name of the endpoint */
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(name);         /*!< Name of the endpoint */
+		);
 	struct aji_client *connection;          /*!< Connection to use for traffic */
 	iksrule *rule;                          /*!< Action matching rule */
+	unsigned int maxicecandidates;          /*!< Maximum number of ICE candidates we will offer */
+	unsigned int maxpayloads;               /*!< Maximum number of payloads we will offer */
 	struct ast_codec_pref prefs;            /*!< Codec preferences */
 	char context[AST_MAX_CONTEXT];          /*!< Context to place incoming calls into */
 	char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Account code */
@@ -159,22 +144,27 @@
 	enum jingle_transport transport;        /*!< Default transport to use on outgoing sessions */
 	struct ao2_container *sessions;         /*!< Active sessions to or from the endpoint */
 	unsigned int destroy:1;                 /*!< Bit to indicate that the endpoint should be destroyed */
+	unsigned int destroyed:1;               /*!< Bit to indicate this endpoint is effectively destroyed */
 };
 
 /*! \brief Session which contains information about an active session */
 struct jingle_session {
-	char sid[MAX_SIZE_SID];               /*!< Session identifier */
-	struct jingle_endpoint *endpoint;     /*!< Endpoint we belong to */
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(sid);        /*!< Session identifier */
+		AST_STRING_FIELD(audio_name); /*!< Name of the audio content */
+		AST_STRING_FIELD(video_name); /*!< Name of the video content */
+		);
+	struct jingle_endpoint *endpoint;     /*!< Endpoint we are associated with */
 	struct aji_client *connection;        /*!< Connection to use for traffic */
 	enum jingle_transport transport;      /*!< Transport type to use for this session */
+	unsigned int maxicecandidates;        /*!< Maximum number of ICE candidates we will offer */
+	unsigned int maxpayloads;             /*!< Maximum number of payloads we will offer */
 	char remote_original[AJI_MAX_JIDLEN]; /*!< Identifier of the original remote party (remote may have changed due to redirect) */
 	char remote[AJI_MAX_JIDLEN];          /*!< Identifier of the remote party */
 	iksrule *rule;                        /*!< Session matching rule */
 	struct ast_codec_pref prefs;          /*!< Codec preferences */
 	struct ast_channel *owner;            /*!< Master Channel */
-	char audio_content_name[100];         /*!< Name attribute for audio content */
 	struct ast_rtp_instance *rtp;         /*!< RTP audio session */
-	char video_content_name[100];         /*!< Name attribute for video content */
 	struct ast_rtp_instance *vrtp;        /*!< RTP video session */
 	struct ast_format_cap *cap;           /*!< Local codec capabilities */
 	struct ast_format_cap *jointcap;      /*!< Joint codec capabilities */
@@ -289,22 +279,26 @@
 	ast_format_cap_destroy(endpoint->cap);
 
 	ao2_ref(endpoint->sessions, -1);
+
+	ast_string_field_free_memory(endpoint);
 }
 
 /*! \brief Hashing function for Jingle endpoints */
 static int jingle_endpoint_hash(const void *obj, const int flags)
 {
 	const struct jingle_endpoint *endpoint = obj;
-
-	return ast_str_hash(endpoint->name);
+	const char *name = obj;
+
+	return ast_str_hash(flags & OBJ_KEY ? name : endpoint->name);
 }
 
 /*! \brief Comparator function for Jingle endpoints */
 static int jingle_endpoint_cmp(void *obj, void *arg, int flags)
 {
 	struct jingle_endpoint *endpoint1 = obj, *endpoint2 = arg;
-
-	return !strcmp(endpoint1->name, endpoint2->name) ? CMP_MATCH | CMP_STOP : 0;
+	const char *name = arg;
+
+	return !strcmp(endpoint1->name, flags & OBJ_KEY ? name : endpoint2->name) ? CMP_MATCH | CMP_STOP : 0;
 }
 
 /*! \brief Destructor for Jingle sessions */
@@ -331,22 +325,26 @@
 	ast_format_cap_destroy(session->cap);
 	ast_format_cap_destroy(session->jointcap);
 	ast_format_cap_destroy(session->peercap);
+
+	ast_string_field_free_memory(session);
 }
 
 /*! \brief Hashing function for Jingle sessions */
 static int jingle_session_hash(const void *obj, const int flags)
 {
 	const struct jingle_session *session = obj;
-
-	return ast_str_hash(session->sid);
+	const char *sid = obj;
+
+	return ast_str_hash(flags & OBJ_KEY ? sid : session->sid);
 }
 
 /*! \brief Comparator function for Jingle sessions */
 static int jingle_session_cmp(void *obj, void *arg, int flags)
 {
 	struct jingle_session *session1 = obj, *session2 = arg;
-
-	return !strcmp(session1->sid, session2->sid) ? CMP_MATCH | CMP_STOP : 0;
+	const char *sid = arg;
+
+	return !strcmp(session1->sid, flags & OBJ_KEY ? sid : session2->sid) ? CMP_MATCH | CMP_STOP : 0;
 }
 
 /*! \brief Function called by RTP engine to get local RTP peer */
@@ -361,10 +359,6 @@
 
 	ao2_ref(session->rtp, +1);
 	*instance = session->rtp;
-
-	if (ast_test_flag(&global_jbconf, AST_JB_FORCED)) {
-		res = AST_RTP_GLUE_RESULT_FORBID;
-	}
 
 	return res;
 }
@@ -431,21 +425,33 @@
 		return NULL;
 	}
 
+	if (ast_string_field_init(session, 512)) {
+		ao2_ref(session, -1);
+		return NULL;
+	}
+
 	if (!ast_strlen_zero(from)) {
 		ast_copy_string(session->remote_original, from, sizeof(session->remote_original));
 		ast_copy_string(session->remote, from, sizeof(session->remote));
 	}
 
 	if (ast_strlen_zero(sid)) {
-		snprintf(session->sid, sizeof(session->sid), "%08lx%08lx", ast_random(), ast_random());
+		ast_string_field_build(session, sid, "%08lx%08lx", ast_random(), ast_random());
 		session->outgoing = 1;
-		ast_copy_string(session->audio_content_name, "audio", sizeof(session->audio_content_name));
-		ast_copy_string(session->video_content_name, "video", sizeof(session->video_content_name));
+		ast_string_field_set(session, audio_name, "audio");
+		ast_string_field_set(session, video_name, "video");
 	} else {
-		ast_copy_string(session->sid, sid, sizeof(session->sid));
+		ast_string_field_set(session, sid, sid);
 	}
 
 	ao2_lock(endpoint);
+
+	/* If the endpoint is effectively destroyed it is not acceptable to create a session on it */
+	if (endpoint->destroyed) {
+		ao2_unlock(endpoint);
+		ao2_ref(session, -1);
+		return NULL;
+	}
 
 	ao2_ref(endpoint, +1);
 	session->endpoint = endpoint;
@@ -479,6 +485,9 @@
 
 	memcpy(&session->prefs, &endpoint->prefs, sizeof(session->prefs));
 
+	session->maxicecandidates = endpoint->maxicecandidates;
+	session->maxpayloads = endpoint->maxpayloads;
+
 	ao2_unlock(endpoint);
 
 	return session;
@@ -564,6 +573,7 @@
 	iks *response;
 
 	if (!(response = iks_new("iq"))) {
+		ast_log(LOG_ERROR, "Unable to allocate an IKS response stanza\n");
 		return;
 	}
 
@@ -585,6 +595,7 @@
 	if (!(response = iks_new("iq")) ||
 	    !(error = iks_new("error")) ||
 	    !(reason = iks_new(reasonstr))) {
+		ast_log(LOG_ERROR, "Unable to allocate IKS error response stanzas\n");
 		goto end;
 	}
 
@@ -611,7 +622,7 @@
 }
 
 /*! \brief Internal helper function which adds ICE-UDP candidates to a transport node */
-static int jingle_add_ice_udp_candidates_to_transport(struct ast_rtp_instance *rtp, iks *transport, iks **candidates)
+static int jingle_add_ice_udp_candidates_to_transport(struct ast_rtp_instance *rtp, iks *transport, iks **candidates, unsigned int maximum)
 {
 	struct ast_rtp_engine_ice *ice;
 	struct ao2_container *local_candidates;
@@ -620,6 +631,7 @@
 	int i = 0, res = 0;
 
 	if (!(ice = ast_rtp_instance_get_ice(rtp)) || !(local_candidates = ice->get_local_candidates(rtp))) {
+		ast_log(LOG_ERROR, "Unable to add ICE-UDP candidates as ICE support not available or no candidates available\n");
 		return -1;
 	}
 
@@ -629,12 +641,13 @@
 
 	it = ao2_iterator_init(local_candidates, 0);
 
-	while ((candidate = ao2_iterator_next(&it)) && (i < MAX_ICE_CANDIDATES)) {
+	while ((candidate = ao2_iterator_next(&it)) && (i < maximum)) {
 		iks *local_candidate;
 		char tmp[30];
 
 		if (!(local_candidate = iks_new("candidate"))) {
 			res = -1;
+			ast_log(LOG_ERROR, "Unable to allocate IKS candidate stanza for ICE-UDP transport\n");
 			goto end;
 		}
 
@@ -671,7 +684,7 @@
 }
 
 /*! \brief Internal helper function which adds Google candidates to a transport node */
-static int jingle_add_google_candidates_to_transport(struct ast_rtp_instance *rtp, iks *transport, iks **candidates, unsigned int video, enum jingle_transport transport_type)
+static int jingle_add_google_candidates_to_transport(struct ast_rtp_instance *rtp, iks *transport, iks **candidates, unsigned int video, enum jingle_transport transport_type, unsigned int maximum)
 {
 	struct ast_rtp_engine_ice *ice;
 	struct ao2_container *local_candidates;
@@ -680,6 +693,7 @@
 	int i = 0, res = 0;
 
 	if (!(ice = ast_rtp_instance_get_ice(rtp)) || !(local_candidates = ice->get_local_candidates(rtp))) {
+		ast_log(LOG_ERROR, "Unable to add Google ICE candidates as ICE support not available or no candidates available\n");
 		return -1;
 	}
 
@@ -689,13 +703,14 @@
 
 	it = ao2_iterator_init(local_candidates, 0);
 
-	while ((candidate = ao2_iterator_next(&it)) && (i < MAX_ICE_CANDIDATES)) {
+	while ((candidate = ao2_iterator_next(&it)) && (i < maximum)) {
 		iks *local_candidate;
 		/* In Google land a username is 16 bytes, explicitly */
 		char ufrag[17] = "";
 
 		if (!(local_candidate = iks_new("candidate"))) {
 			res = -1;
+			ast_log(LOG_ERROR, "Unable to allocate IKS candidate stanza for Google ICE transport\n");
 			goto end;
 		}
 
@@ -751,6 +766,7 @@
 
 	if (!(iq = iks_new("iq")) || !(jingle = iks_new(session->transport == JINGLE_TRANSPORT_GOOGLE_V1 ? "session" : "jingle")) ||
 	    !(reason = iks_new("reason")) || !(text = iks_new(reasontext))) {
+		ast_log(LOG_ERROR, "Failed to allocate stanzas for session-terminate message on session '%s'\n", session->sid);
 		goto end;
 	}
 
@@ -794,6 +810,7 @@
 	}
 
 	if (!(iq = iks_new("iq")) || !(jingle = iks_new("jingle")) || !(text = iks_new(info))) {
+		ast_log(LOG_ERROR, "Failed to allocate stanzas for session-info message on session '%s'\n", session->sid);
 		goto end;
 	}
 
@@ -816,28 +833,82 @@
 	iks_delete(iq);
 }
 
-/*! \brief Helper function which gets the session lock and ultimately channel lock if present */
-static void jingle_get_owner_lock(struct jingle_session *session)
-{
-	while (session->owner && ast_channel_trylock(session->owner)) {
-		ao2_unlock(session);
-		usleep(1);
-		ao2_lock(session);
-	}
+/*! \internal
+ *
+ * \brief Locks both pvt and pvt owner if owner is present.
+ *
+ * \note This function gives a ref to pvt->owner if it is present and locked.
+ *       This reference must be decremented after pvt->owner is unlocked.
+ *
+ * \note This function will never give you up,
+ * \note This function will never let you down.
+ * \note This function will run around and desert you.
+ *
+ * \pre pvt is not locked
+ * \post pvt is locked
+ * \post pvt->owner is locked and its reference count is increased (if pvt->owner is not NULL)
+ *
+ * \returns a pointer to the locked and reffed pvt->owner channel if it exists.
+ */
+static struct ast_channel *jingle_session_lock_full(struct jingle_session *pvt)
+{
+	struct ast_channel *chan;
+
+	/* Locking is simple when it is done right.  If you see a deadlock resulting
+	 * in this function, it is not this function's fault, Your problem exists elsewhere.
+	 * This function is perfect... seriously. */
+	for (;;) {
+		/* First, get the channel and grab a reference to it */
+		ao2_lock(pvt);
+		chan = pvt->owner;
+		if (chan) {
+			/* The channel can not go away while we hold the pvt lock.
+			 * Give the channel a ref so it will not go away after we let
+			 * the pvt lock go. */
+			ast_channel_ref(chan);
+		} else {
+			/* no channel, return pvt locked */
+			return NULL;
+		}
+
+		/* We had to hold the pvt lock while getting a ref to the owner channel
+		 * but now we have to let this lock go in order to preserve proper
+		 * locking order when grabbing the channel lock */
+		ao2_unlock(pvt);
+
+		/* Look, no deadlock avoidance, hooray! */
+		ast_channel_lock(chan);
+		ao2_lock(pvt);
+		if (pvt->owner == chan) {
+			/* done */
+			break;
+		}
+
+		/* If the owner changed while everything was unlocked, no problem,
+		 * just start over and everthing will work.  This is rare, do not be
+		 * confused by this loop and think this it is an expensive operation.
+		 * The majority of the calls to this function will never involve multiple
+		 * executions of this loop. */
+		ast_channel_unlock(chan);
+		ast_channel_unref(chan);
+		ao2_unlock(pvt);
+	}
+
+	/* If owner exists, it is locked and reffed */
+	return pvt->owner;
 }
 
 /*! \brief Helper function which queues a hangup frame with cause code */
 static void jingle_queue_hangup_with_cause(struct jingle_session *session, int cause)
 {
-	ao2_lock(session);
-
-	jingle_get_owner_lock(session);
-
-	if (session->owner) {
-		ast_queue_hangup_with_cause(session->owner, cause);
-		ast_channel_unlock(session->owner);
-	}
-
+	struct ast_channel *chan;
+
+	if ((chan = jingle_session_lock_full(session))) {
+		ast_debug(3, "Hanging up channel '%s' with cause '%d'\n", ast_channel_name(chan), cause);
+		ast_queue_hangup_with_cause(chan, cause);
+		ast_channel_unlock(chan);
+		ast_channel_unref(chan);
+	}
 	ao2_unlock(session);
 }
 
@@ -845,15 +916,19 @@
 static void jingle_send_transport_info(struct jingle_session *session, const char *from)
 {
 	iks *iq, *jingle = NULL, *audio = NULL, *audio_transport = NULL, *video = NULL, *video_transport = NULL;
-	iks *audio_candidates[MAX_ICE_CANDIDATES] = { 0, }, *video_candidates[MAX_ICE_CANDIDATES] = { 0, };
+	iks *audio_candidates[session->maxicecandidates], *video_candidates[session->maxicecandidates];
 	int i, res = 0;
 
 	if (!(iq = iks_new("iq")) ||
 	    !(jingle = iks_new(session->transport == JINGLE_TRANSPORT_GOOGLE_V1 ? "session" : "jingle"))) {
 		iks_delete(iq);
 		jingle_queue_hangup_with_cause(session, AST_CAUSE_SWITCH_CONGESTION);
+		ast_log(LOG_ERROR, "Failed to allocate stanzas for transport-info message, hanging up session '%s'\n", session->sid);
 		return;
 	}
+
+	memset(audio_candidates, 0, sizeof(audio_candidates));
+	memset(video_candidates, 0, sizeof(video_candidates));
 
 	iks_insert_attrib(iq, "from", session->connection->jid->full);
 	iks_insert_attrib(iq, "to", from);
@@ -876,17 +951,18 @@
 	if (session->rtp) {
 		if (session->transport == JINGLE_TRANSPORT_GOOGLE_V1) {
 			/* V1 protocol has the candidates directly in the session */
-			res = jingle_add_google_candidates_to_transport(session->rtp, jingle, audio_candidates, 0, session->transport);
+			res = jingle_add_google_candidates_to_transport(session->rtp, jingle, audio_candidates, 0, session->transport, session->maxicecandidates);
 		} else if ((audio = iks_new("content")) && (audio_transport = iks_new("transport"))) {
 			iks_insert_attrib(audio, "creator", session->outgoing ? "initiator" : "responder");
-			iks_insert_attrib(audio, "name", session->audio_content_name);
+			iks_insert_attrib(audio, "name", session->audio_name);
 			iks_insert_node(jingle, audio);
 			iks_insert_node(audio, audio_transport);
 
 			if (session->transport == JINGLE_TRANSPORT_ICE_UDP) {
-				res = jingle_add_ice_udp_candidates_to_transport(session->rtp, audio_transport, audio_candidates);
+				res = jingle_add_ice_udp_candidates_to_transport(session->rtp, audio_transport, audio_candidates, session->maxicecandidates);
 			} else if (session->transport == JINGLE_TRANSPORT_GOOGLE_V2) {
-				res = jingle_add_google_candidates_to_transport(session->rtp, audio_transport, audio_candidates, 0, session->transport);
+				res = jingle_add_google_candidates_to_transport(session->rtp, audio_transport, audio_candidates, 0, session->transport,
+										session->maxicecandidates);
 			}
 		} else {
 			res = -1;
@@ -896,14 +972,15 @@
 	if ((session->transport != JINGLE_TRANSPORT_GOOGLE_V1) && !res && session->vrtp) {
 		if ((video = iks_new("content")) && (video_transport = iks_new("transport"))) {
 			iks_insert_attrib(video, "creator", session->outgoing ? "initiator" : "responder");
-			iks_insert_attrib(video, "name", session->video_content_name);
+			iks_insert_attrib(video, "name", session->video_name);
 			iks_insert_node(jingle, video);
 			iks_insert_node(video, video_transport);
 
 			if (session->transport == JINGLE_TRANSPORT_ICE_UDP) {
-				res = jingle_add_ice_udp_candidates_to_transport(session->vrtp, video_transport, video_candidates);
+				res = jingle_add_ice_udp_candidates_to_transport(session->vrtp, video_transport, video_candidates, session->maxicecandidates);
 			} else if (session->transport == JINGLE_TRANSPORT_GOOGLE_V2) {
-				res = jingle_add_google_candidates_to_transport(session->vrtp, video_transport, video_candidates, 1, session->transport);
+				res = jingle_add_google_candidates_to_transport(session->vrtp, video_transport, video_candidates, 1, session->transport,
+										session->maxicecandidates);
 			}
 		} else {
 			res = -1;
@@ -917,7 +994,7 @@
 	}
 
 	/* Clean up after ourselves */
-	for (i = 0; i < MAX_ICE_CANDIDATES; i++) {
+	for (i = 0; i < session->maxicecandidates; i++) {
 		iks_delete(video_candidates[i]);
 		iks_delete(audio_candidates[i]);
 	}
@@ -937,7 +1014,7 @@
 	int i = 0, res = 0;
 
 	ast_format_cap_iter_start(session->jointcap);
-	while (!(ast_format_cap_iter_next(session->jointcap, &format)) && (i < MAX_PAYLOADS)) {
+	while (!(ast_format_cap_iter_next(session->jointcap, &format)) && (i < (session->maxpayloads - 2))) {
 		int rtp_code;
 		iks *payload;
 		char tmp[32];
@@ -967,7 +1044,7 @@
 		payloads[i++] = payload;
 	}
 	/* If this is for audio and there is room for RFC2833 add it in */
-	if ((type == AST_FORMAT_TYPE_AUDIO) && (i < MAX_PAYLOADS)) {
+	if ((type == AST_FORMAT_TYPE_AUDIO) && (i < session->maxpayloads)) {
 		iks *payload;
 
 		if ((payload = iks_new("payload-type"))) {
@@ -990,11 +1067,49 @@
 	return res;
 }
 
+/*! \brief Helper function which adds content to a description */
+static int jingle_add_content(struct jingle_session *session, iks *jingle, iks *content, iks *description, iks *transport,
+			      const char *name, enum ast_format_type type, struct ast_rtp_instance *rtp, iks **payloads)
+{
+	int res = 0;
+
+	if (session->transport != JINGLE_TRANSPORT_GOOGLE_V1) {
+		iks_insert_attrib(content, "creator", session->outgoing ? "initiator" : "responder");
+		iks_insert_attrib(content, "name", name);
+		iks_insert_node(jingle, content);
+
+		iks_insert_attrib(description, "xmlns", JINGLE_RTP_NS);
+		if (type == AST_FORMAT_TYPE_AUDIO) {
+			iks_insert_attrib(description, "media", "audio");
+		} else if (type == AST_FORMAT_TYPE_VIDEO) {
+			iks_insert_attrib(description, "media", "video");
+		} else {
+			return -1;
+		}
+		iks_insert_node(content, description);
+	} else {
+		iks_insert_attrib(description, "xmlns", GOOGLE_PHONE_NS);
+		iks_insert_node(jingle, description);
+	}
+
+	if (!(res = jingle_add_payloads_to_description(session, rtp, description, payloads, type))) {
+		if (session->transport == JINGLE_TRANSPORT_ICE_UDP) {
+			iks_insert_attrib(transport, "xmlns", JINGLE_ICE_UDP_NS);
+			iks_insert_node(content, transport);
+		} else if (session->transport == JINGLE_TRANSPORT_GOOGLE_V2) {
+			iks_insert_attrib(transport, "xmlns", GOOGLE_TRANSPORT_NS);
+			iks_insert_node(content, transport);
+		}
+	}
+
+	return res;
+}
+
 /*! \brief Internal function which sends a complete session message */
 static void jingle_send_session_action(struct jingle_session *session, const char *action)
 {
 	iks *iq, *jingle, *audio = NULL, *audio_description = NULL, *video = NULL, *video_description = NULL;
-	iks *audio_payloads[MAX_PAYLOADS] = { 0, }, *video_payloads[MAX_PAYLOADS] = { 0, };
+	iks *audio_payloads[session->maxpayloads], *video_payloads[session->maxpayloads];
 	iks *audio_transport = NULL, *video_transport = NULL;
 	int i, res = 0;
 
@@ -1004,6 +1119,9 @@
 		iks_delete(iq);
 		return;
 	}
+
+	memset(audio_payloads, 0, sizeof(audio_payloads));
+	memset(video_payloads, 0, sizeof(video_payloads));
 
 	iks_insert_attrib(iq, "from", session->connection->jid->full);
 	iks_insert_attrib(iq, "to", session->remote);
@@ -1029,52 +1147,20 @@
 
 	if (session->rtp && (audio = iks_new("content")) && (audio_description = iks_new("description")) &&
 	    (audio_transport = iks_new("transport"))) {
-		if (session->transport != JINGLE_TRANSPORT_GOOGLE_V1) {
-			iks_insert_attrib(audio, "creator", session->outgoing ? "initiator" : "responder");
-			iks_insert_attrib(audio, "name", session->audio_content_name);
-			iks_insert_node(jingle, audio);
-
-			iks_insert_attrib(audio_description, "xmlns", JINGLE_RTP_NS);
-			iks_insert_attrib(audio_description, "media", "audio");
-			iks_insert_node(audio, audio_description);
-		} else {
-			iks_insert_attrib(audio_description, "xmlns", GOOGLE_PHONE_NS);
-			iks_insert_node(jingle, audio_description);
-		}
-
-		if (!(res = jingle_add_payloads_to_description(session, session->rtp, audio_description, audio_payloads, AST_FORMAT_TYPE_AUDIO))) {
-			if (session->transport == JINGLE_TRANSPORT_ICE_UDP) {
-				iks_insert_attrib(audio_transport, "xmlns", JINGLE_ICE_UDP_NS);
-				iks_insert_node(audio, audio_transport);
-			} else if (session->transport == JINGLE_TRANSPORT_GOOGLE_V2) {
-				iks_insert_attrib(audio_transport, "xmlns", GOOGLE_TRANSPORT_NS);
-				iks_insert_node(audio, audio_transport);
-			}
-		}
+		res = jingle_add_content(session, jingle, audio, audio_description, audio_transport, session->audio_name,
+					 AST_FORMAT_TYPE_AUDIO, session->rtp, audio_payloads);
 	} else {
+		ast_log(LOG_ERROR, "Failed to allocate audio content stanzas for session '%s', hanging up\n", session->sid);
 		res = -1;
 	}
 
 	if ((session->transport != JINGLE_TRANSPORT_GOOGLE_V1) && !res && session->vrtp) {
 		if ((video = iks_new("content")) && (video_description = iks_new("description")) &&
 		    (video_transport = iks_new("transport"))) {
-			iks_insert_attrib(video, "creator", session->outgoing ? "initiator" : "responder");
-			iks_insert_attrib(video, "name", session->video_content_name);
-			iks_insert_node(jingle, video);
-
-			iks_insert_attrib(video_description, "xmlns", JINGLE_RTP_NS);
-			iks_insert_attrib(video_description, "media", "video");
-			iks_insert_node(video, video_description);
-
-			if (!(res = jingle_add_payloads_to_description(session, session->vrtp, video_description, video_payloads, AST_FORMAT_TYPE_VIDEO))) {
-				if (session->transport == JINGLE_TRANSPORT_ICE_UDP) {
-					iks_insert_attrib(video_transport, "xmlns", JINGLE_ICE_UDP_NS);
-				} else if (session->transport == JINGLE_TRANSPORT_GOOGLE_V2) {
-					iks_insert_attrib(video_transport, "xmlns", GOOGLE_TRANSPORT_NS);
-				}
-				iks_insert_node(video, video_transport);
-			}
+			res = jingle_add_content(session, jingle, video, video_description, video_transport, session->video_name,
+						 AST_FORMAT_TYPE_VIDEO, session->vrtp, video_payloads);
 		} else {
+			ast_log(LOG_ERROR, "Failed to allocate video content stanzas for session '%s', hanging up\n", session->sid);
 			res = -1;
 		}
 	}
@@ -1088,7 +1174,7 @@
 	iks_delete(video_transport);
 	iks_delete(audio_transport);
 
-	for (i = 0; i < MAX_PAYLOADS; i++) {
+	for (i = 0; i < session->maxpayloads; i++) {
 		iks_delete(video_payloads[i]);
 		iks_delete(audio_payloads[i]);
 	}
@@ -1125,15 +1211,13 @@
 
 	/* If no error occurred they accepted our session-initiate message happily */
 	if (!error) {
-		ao2_lock(session);
-
-		jingle_get_owner_lock(session);
-
-		if (session->owner) {
-			ast_queue_control(session->owner, AST_CONTROL_PROCEEDING);
-			ast_channel_unlock(session->owner);
-		}
-
+		struct ast_channel *chan;
+
+		if ((chan = jingle_session_lock_full(session))) {
+			ast_queue_control(chan, AST_CONTROL_PROCEEDING);
+			ast_channel_unlock(chan);
+			ast_channel_unref(chan);
+		}
 		ao2_unlock(session);
 
 		jingle_send_transport_info(session, iks_find_attrib(pak->x, "from"));
@@ -1231,7 +1315,6 @@
 	struct jingle_session *session = ast_channel_tech_pvt(ast);
 	struct ast_frame *frame = &ast_null_frame;
 
-
 	switch (ast_channel_fdno(ast)) {
 	case 0:
 		if (session->rtp) {
@@ -1257,30 +1340,21 @@
 		break;
 	}
 
-	ao2_lock(session);
-
-	jingle_get_owner_lock(session);
-
-	if (session->owner) {
-		if (frame && frame->frametype == AST_FRAME_VOICE &&
-		    !ast_format_cap_iscompatible(ast_channel_nativeformats(session->owner), &frame->subclass.format)) {
-			if (!ast_format_cap_iscompatible(session->jointcap, &frame->subclass.format)) {
-				ast_debug(1, "Bogus frame of format '%s' received from '%s'!\n",
-					  ast_getformatname(&frame->subclass.format), ast_channel_name(session->owner));
-				frame = &ast_null_frame;
-			} else {
-				ast_debug(1, "Oooh, format changed to %s\n",
-					  ast_getformatname(&frame->subclass.format));
-				ast_format_cap_remove_bytype(ast_channel_nativeformats(session->owner), AST_FORMAT_TYPE_AUDIO);
-				ast_format_cap_add(ast_channel_nativeformats(session->owner), &frame->subclass.format);
-				ast_set_read_format(session->owner, ast_channel_readformat(session->owner));
-				ast_set_write_format(session->owner, ast_channel_writeformat(session->owner));
-			}
-		}
-		ast_channel_unlock(session->owner);
-	}
-
-	ao2_unlock(session);
+	if (frame && frame->frametype == AST_FRAME_VOICE &&
+	    !ast_format_cap_iscompatible(ast_channel_nativeformats(ast), &frame->subclass.format)) {
+		if (!ast_format_cap_iscompatible(session->jointcap, &frame->subclass.format)) {
+			ast_debug(1, "Bogus frame of format '%s' received from '%s'!\n",
+				  ast_getformatname(&frame->subclass.format), ast_channel_name(ast));
+			frame = &ast_null_frame;
+		} else {
+			ast_debug(1, "Oooh, format changed to %s\n",
+				  ast_getformatname(&frame->subclass.format));
+			ast_format_cap_remove_bytype(ast_channel_nativeformats(ast), AST_FORMAT_TYPE_AUDIO);
+			ast_format_cap_add(ast_channel_nativeformats(ast), &frame->subclass.format);
+			ast_set_read_format(ast, ast_channel_readformat(ast));
+			ast_set_write_format(ast, ast_channel_writeformat(ast));
+		}
+	}
 
 	return frame;
 }
@@ -1458,6 +1532,8 @@
 {
 	struct jingle_session *session = ast_channel_tech_pvt(ast);
 
+	ao2_lock(session);
+
 	if ((ast_channel_state(ast) != AST_STATE_DOWN) && !session->gone) {
 		int cause = (session->owner ? ast_channel_hangupcause(session->owner) : AST_CAUSE_CONGESTION);
 		const char *reason = "success";
@@ -1475,8 +1551,12 @@
 	}
 
 	ast_channel_tech_pvt_set(ast, NULL);
+	session->owner = NULL;
+
 	ao2_unlink(session->endpoint->sessions, session);
 	ao2_ref(session->endpoint, -1);
+
+	ao2_unlock(session);
 	ao2_ref(session, -1);
 
 	return 0;
@@ -1486,7 +1566,7 @@
 static struct ast_channel *jingle_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
 {
 	char *dialed, target[200] = "";
-	struct jingle_endpoint tmp, *endpoint;
+	struct jingle_endpoint *endpoint;
 	struct aji_buddy *buddy;
 	struct jingle_session *session;
 	struct ast_channel *chan;
@@ -1518,9 +1598,7 @@
 		return NULL;
 	}
 
-	ast_copy_string(tmp.name, args.name, sizeof(tmp.name));
-
-	if (!(endpoint = ao2_find(endpoints, &tmp, OBJ_POINTER))) {
+	if (!(endpoint = ao2_find(endpoints, args.name, OBJ_KEY))) {
 		ast_log(LOG_ERROR, "Endpoint '%s' does not exist.\n", args.name);
 		*cause = AST_CAUSE_CHANNEL_UNACCEPTABLE;
 		return NULL;
@@ -1603,8 +1681,248 @@
 	return chan;
 }
 
-/*! \brief Helper function which locates content stanzas and interprets them */
-static void jingle_interpret_content(struct jingle_session *session, ikspak *pak)
+/*! \brief Helper function which handles content descriptions */
+static int jingle_interpret_description(struct jingle_session *session, iks *description, const char *name, struct ast_rtp_instance **rtp)
+{
+	char *media = iks_find_attrib(description, "media");
+	struct ast_rtp_codecs codecs;
+	iks *codec;
+	int othercapability = 0;
+
+	/* Google-V1 is always carrying audio, but just doesn't tell us so */
+	if (session->transport == JINGLE_TRANSPORT_GOOGLE_V1) {
+		media = "audio";
+	} else if (ast_strlen_zero(media)) {
+		jingle_queue_hangup_with_cause(session, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL);
+		ast_log(LOG_ERROR, "Received a content description on session '%s' without a name\n", session->sid);
+		return -1;
+	}
+
+	/* Determine the type of media that is being carried and update the RTP instance, as well as the name */
+	if (!strcasecmp(media, "audio")) {
+		if (!ast_strlen_zero(name)) {
+			ast_string_field_set(session, audio_name, name);
+		}
+		*rtp = session->rtp;
+		ast_format_cap_remove_bytype(session->peercap, AST_FORMAT_TYPE_AUDIO);
+		ast_format_cap_remove_bytype(session->jointcap, AST_FORMAT_TYPE_AUDIO);
+	} else if (!strcasecmp(media, "video")) {
+		if (!ast_strlen_zero(name)) {
+			ast_string_field_set(session, video_name, name);
+		}
+
+		jingle_enable_video(session);
+		*rtp = session->vrtp;
+
+		/* If video is not present cancel this session */
+		if (!session->vrtp) {
+			jingle_queue_hangup_with_cause(session, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL);
+			ast_log(LOG_ERROR, "Received a video content description on session '%s' but could not enable video\n", session->sid);
+			return -1;
+		}
+
+		ast_format_cap_remove_bytype(session->peercap, AST_FORMAT_TYPE_VIDEO);
+		ast_format_cap_remove_bytype(session->jointcap, AST_FORMAT_TYPE_VIDEO);
+	} else {
+		/* Unknown media type */
+		jingle_queue_hangup_with_cause(session, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL);
+		ast_log(LOG_ERROR, "Unsupported media type '%s' received in content description on session '%s'\n", media, session->sid);
+		return -1;
+	}
+
+	ast_rtp_codecs_payloads_clear(&codecs, NULL);
+
+	/* Iterate the codecs updating the relevant RTP instance as we go */
+	for (codec = iks_child(description); codec; codec = iks_next(codec)) {
+		char *id = iks_find_attrib(codec, "id"), *name = iks_find_attrib(codec, "name");
+		char *clockrate = iks_find_attrib(codec, "clockrate");
+		int rtp_id, rtp_clockrate;
+
+		if (!ast_strlen_zero(id) && !ast_strlen_zero(name) && (sscanf(id, "%30d", &rtp_id) == 1)) {
+			ast_rtp_codecs_payloads_set_m_type(&codecs, NULL, rtp_id);
+
+			if (!ast_strlen_zero(clockrate) && (sscanf(clockrate, "%30d", &rtp_clockrate) == 1)) {
+				ast_rtp_codecs_payloads_set_rtpmap_type_rate(&codecs, NULL, rtp_id, media, name, 0, rtp_clockrate);
+			} else {
+				ast_rtp_codecs_payloads_set_rtpmap_type(&codecs, NULL, rtp_id, media, name, 0);
+			}
+		}
+	}
+
+	ast_rtp_codecs_payload_formats(&codecs, session->peercap, &othercapability);
+	ast_format_cap_joint_append(session->cap, session->peercap, session->jointcap);
+
+	if (ast_format_cap_is_empty(session->jointcap)) {
+		/* We have no compatible codecs, so terminate the session appropriately */
+		jingle_queue_hangup_with_cause(session, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL);
+		return -1;
+	}
+
+	ast_rtp_codecs_payloads_copy(&codecs, ast_rtp_instance_get_codecs(*rtp), *rtp);
+
+	return 0;
+}
+
+/*! \brief Helper function which handles ICE-UDP transport information */
+static int jingle_interpret_ice_udp_transport(struct jingle_session *session, iks *transport, struct ast_rtp_instance *rtp)
+{
+	struct ast_rtp_engine_ice *ice = ast_rtp_instance_get_ice(rtp);
+	char *ufrag = iks_find_attrib(transport, "ufrag"), *pwd = iks_find_attrib(transport, "pwd");
+	iks *candidate;
+
+	if (!ice) {
+		jingle_queue_hangup_with_cause(session, AST_CAUSE_SWITCH_CONGESTION);
+		ast_log(LOG_ERROR, "Received ICE-UDP transport information on session '%s' but ICE support not available\n", session->sid);
+		return -1;
+	}
+
+	if (ast_strlen_zero(ufrag) || ast_strlen_zero(pwd)) {
+		jingle_queue_hangup_with_cause(session, AST_CAUSE_PROTOCOL_ERROR);
+		ast_log(LOG_ERROR, "Invalid ICE-UDP transport information received on session '%s', ufrag or pwd not present\n", session->sid);
+		return -1;
+	}
+
+	ice->set_authentication(rtp, ufrag, pwd);
+
+	for (candidate = iks_child(transport); candidate; candidate = iks_next(candidate)) {
+		char *component = iks_find_attrib(candidate, "component"), *foundation = iks_find_attrib(candidate, "foundation");
+		char *generation = iks_find_attrib(candidate, "generation"), *id = iks_find_attrib(candidate, "id");
+		char *ip = iks_find_attrib(candidate, "ip"), *network = iks_find_attrib(candidate, "network");
+		char *port = iks_find_attrib(candidate, "port"), *priority = iks_find_attrib(candidate, "priority");
+		char *protocol = iks_find_attrib(candidate, "protocol"), *type = iks_find_attrib(candidate, "type");
+		struct ast_rtp_engine_ice_candidate local_candidate = { 0, };
+		int real_port;
+		struct ast_sockaddr remote_address = { { 0, } };
+
+		/* If this candidate is incomplete skip it */
+		if (ast_strlen_zero(component) || ast_strlen_zero(foundation) || ast_strlen_zero(generation) || ast_strlen_zero(id) ||
+		    ast_strlen_zero(ip) || ast_strlen_zero(network) || ast_strlen_zero(port) || ast_strlen_zero(priority) ||
+		    ast_strlen_zero(protocol) || ast_strlen_zero(type)) {
+			jingle_queue_hangup_with_cause(session, AST_CAUSE_PROTOCOL_ERROR);
+			ast_log(LOG_ERROR, "Incomplete ICE-UDP candidate received on session '%s'\n", session->sid);
+			return -1;
+		}
+
+		if ((sscanf(component, "%30u", &local_candidate.id) != 1) ||
+		    (sscanf(priority, "%30u", &local_candidate.priority) != 1) ||
+		    (sscanf(port, "%30d", &real_port) != 1)) {
+			jingle_queue_hangup_with_cause(session, AST_CAUSE_PROTOCOL_ERROR);
+			ast_log(LOG_ERROR, "Invalid ICE-UDP candidate information received on session '%s'\n", session->sid);
+			return -1;
+		}
+
+		local_candidate.foundation = foundation;
+		local_candidate.transport = protocol;
+
+		ast_sockaddr_parse(&local_candidate.address, ip, PARSE_PORT_FORBID);
+
+		/* We only support IPv4 right now */
+		if (!ast_sockaddr_is_ipv4(&local_candidate.address)) {
+			continue;
+		}
+
+		ast_sockaddr_set_port(&local_candidate.address, real_port);
+
+		if (!strcasecmp(type, "host")) {
+			local_candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_HOST;
+		} else if (!strcasecmp(type, "srflx")) {
+			local_candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_SRFLX;
+		} else if (!strcasecmp(type, "relay")) {
+			local_candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_RELAYED;
+		} else {
+			continue;
+		}
+
+		/* Worst case use the first viable address */
+		ast_rtp_instance_get_remote_address(rtp, &remote_address);
+
+		if (ast_sockaddr_is_ipv4(&local_candidate.address) && ast_sockaddr_isnull(&remote_address)) {
+			ast_rtp_instance_set_remote_address(rtp, &local_candidate.address);
+		}
+
+		ice->add_remote_candidate(rtp, &local_candidate);
+	}
+
+	ice->start(rtp);
+
+	return 0;
+}
+
+/*! \brief Helper function which handles Google transport information */
+static int jingle_interpret_google_transport(struct jingle_session *session, iks *transport, struct ast_rtp_instance *rtp)
+{
+	struct ast_rtp_engine_ice *ice = ast_rtp_instance_get_ice(rtp);
+	iks *candidate;
+
+	if (!ice) {
+		jingle_queue_hangup_with_cause(session, AST_CAUSE_SWITCH_CONGESTION);
+		ast_log(LOG_ERROR, "Received Google transport information on session '%s' but ICE support not available\n", session->sid);
+		return -1;
+	}
+
+	/* If this session has not transitioned to the Google transport do so now */
+	if ((session->transport != JINGLE_TRANSPORT_GOOGLE_V2) &&
+	    (session->transport != JINGLE_TRANSPORT_GOOGLE_V1)) {
+		/* Stop built-in ICE support... we need to fall back to the old old old STUN */
+		ice->stop(rtp);
+
+		session->transport = JINGLE_TRANSPORT_GOOGLE_V2;
+	}
+
+	for (candidate = iks_child(transport); candidate; candidate = iks_next(candidate)) {
+		char *address = iks_find_attrib(candidate, "address"), *port = iks_find_attrib(candidate, "port");
+		char *username = iks_find_attrib(candidate, "username"), *name = iks_find_attrib(candidate, "name");
+		char *protocol = iks_find_attrib(candidate, "protocol");
+		int real_port;
+		struct ast_sockaddr target = { { 0, } };
+		/* In Google land the combined value is 32 bytes */
+		char combined[33] = "";
+
+		/* If this is NOT actually a candidate just skip it */
+		if (strcasecmp(iks_name(candidate), "candidate") &&
+		    strcasecmp(iks_name(candidate), "p:candidate") &&
+		    strcasecmp(iks_name(candidate), "ses:candidate")) {
+			continue;
+		}
+
+		/* If this candidate is incomplete skip it */
+		if (ast_strlen_zero(address) || ast_strlen_zero(port) || ast_strlen_zero(username) ||
+		    ast_strlen_zero(name)) {
+			jingle_queue_hangup_with_cause(session, AST_CAUSE_PROTOCOL_ERROR);
+			ast_log(LOG_ERROR, "Incomplete Google candidate received on session '%s'\n", session->sid);
+			return -1;
+		}
+
+		/* We only support UDP so skip any other protocols */
+		if (!ast_strlen_zero(protocol) && strcasecmp(protocol, "udp")) {
+			continue;
+		}
+
+		/* Parse the target information so we can send a STUN request to the candidate */
+		if (sscanf(port, "%30d", &real_port) != 1) {
+			jingle_queue_hangup_with_cause(session, AST_CAUSE_PROTOCOL_ERROR);
+			ast_log(LOG_ERROR, "Invalid Google candidate port '%s' received on session '%s'\n", port, session->sid);
+			return -1;
+		}
+		ast_sockaddr_parse(&target, address, PARSE_PORT_FORBID);
+		ast_sockaddr_set_port(&target, real_port);
+
+		/* Per the STUN support Google talk uses combine the two usernames */
+		snprintf(combined, sizeof(combined), "%s%s", username, ice->get_ufrag(rtp));
+
+		/* This should appease the masses... we will actually change the remote address when we get their STUN packet */
+		ast_rtp_instance_stun_request(rtp, &target, combined);
+	}
+
+	return 0;
+}
+
+/*!
+ * \brief Helper function which locates content stanzas and interprets them
+ *
+ * \note The session *must not* be locked before calling this
+ */
+static int jingle_interpret_content(struct jingle_session *session, ikspak *pak)
 {
 	iks *content;

[... 689 lines stripped ...]



More information about the asterisk-commits mailing list