Various fixes to get BOSH working with Prosody 0.5.

Wed, 15 Jul 2009 05:49:30 +0000

author
Paul Aurich <darkrain42@pidgin.im>
date
Wed, 15 Jul 2009 05:49:30 +0000
changeset 27811
9db18cacd8fc
parent 27799
3a6174211b40
child 27812
388f33d7ca53

Various fixes to get BOSH working with Prosody 0.5.

Prosody closes the TCP socket immediately after every reply, so is an
extremely good test of that corner case.

libpurple/protocols/jabber/bosh.c file | annotate | diff | comparison | revisions
--- a/libpurple/protocols/jabber/bosh.c	Wed Jul 15 04:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/bosh.c	Wed Jul 15 05:49:30 2009 +0000
@@ -51,8 +51,13 @@
 	PurpleHTTPConnection *connections[MAX_HTTP_CONNECTIONS];
 	unsigned short failed_connections;
 
-	gboolean ready;
+	enum {
+		BOSH_CONN_OFFLINE,
+		BOSH_CONN_BOOTING,
+		BOSH_CONN_ONLINE
+	} state;
 	gboolean ssl;
+	gboolean needs_restart;
 
 	/* decoded URL */
 	char *host;
@@ -84,7 +89,11 @@
 
 	PurpleCircBuffer *write_buffer;
 
-	gboolean ready;
+	enum {
+		HTTP_CONN_OFFLINE,
+		HTTP_CONN_CONNECTING,
+		HTTP_CONN_CONNECTED
+	} state;
 	int requests; /* number of outstanding HTTP requests */
 
 	GString *buf;
@@ -129,7 +138,7 @@
 	PurpleHTTPConnection *conn = g_new0(PurpleHTTPConnection, 1);
 	conn->bosh = bosh;
 	conn->fd = -1;
-	conn->ready = FALSE;
+	conn->state = HTTP_CONN_OFFLINE;
 
 	conn->write_buffer = purple_circ_buffer_new(0 /* default grow size */);
 
@@ -176,6 +185,7 @@
 	conn->path = g_strdup_printf("/%s", path);
 	g_free(path);
 	conn->pipelining = TRUE;
+	conn->needs_restart = FALSE;
 
 	if ((user && user[0] != '\0') || (passwd && passwd[0] != '\0')) {
 		purple_debug_info("jabber", "Ignoring unexpected username and password "
@@ -198,7 +208,7 @@
 
 	conn->pending = purple_circ_buffer_new(0 /* default grow size */);
 
-	conn->ready = FALSE;
+	conn->state = BOSH_CONN_OFFLINE;
 	if (purple_strcasestr(url, "https://") != NULL)
 		conn->ssl = TRUE;
 	else
@@ -243,18 +253,28 @@
 	/* Easy solution: Does everyone involved support pipelining? Hooray! Just use
 	 * one TCP connection! */
 	if (conn->pipelining)
-		return conn->connections[0]->ready ? conn->connections[0] : NULL;
+		return conn->connections[0]->state == HTTP_CONN_CONNECTED ?
+				conn->connections[0] : NULL;
 
 	/* First loop, look for a connection that's ready */
 	for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
-		if (conn->connections[i] && conn->connections[i]->ready &&
-		                            conn->connections[i]->requests == 0)
+		if (conn->connections[i] &&
+				conn->connections[i]->state == HTTP_CONN_CONNECTED &&
+				conn->connections[i]->requests == 0)
 			return conn->connections[i];
 	}
 
-	/* Second loop, look for one that's NULL and create a new connection */
+	/* Second loop, is something currently connecting? If so, just queue up. */
+	for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
+		if (conn->connections[i] &&
+				conn->connections[i]->state == HTTP_CONN_CONNECTING)
+			return NULL;
+	}
+
+	/* Third loop, look for one that's NULL and create a new connection */
 	for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
 		if (!conn->connections[i]) {
+			purple_debug_info("jabber", "bosh: Creating and connecting new httpconn\n");
 			conn->connections[i] = jabber_bosh_http_connection_init(conn);
 
 			http_connection_connect(conn->connections[i]);
@@ -268,7 +288,7 @@
 
 static void
 jabber_bosh_connection_send(PurpleBOSHConnection *conn,
-                            PurpleBOSHPacketType type, const char *data)
+                            const PurpleBOSHPacketType type, const char *data)
 {
 	PurpleHTTPConnection *chosen;
 	GString *packet = NULL;
@@ -277,12 +297,12 @@
 
 	if (type != PACKET_NORMAL && !chosen) {
 		/*
-		 * For non-ordinary traffic, we don't want to 'buffer' it, so use the
+		 * For non-ordinary traffic, we can't 'buffer' it, so use the
 		 * first connection.
 		 */
 		chosen = conn->connections[0];
 
-		if (!chosen->ready) {
+		if (chosen->state != HTTP_CONN_CONNECTED) {
 			purple_debug_info("jabber", "Unable to find a ready BOSH "
 					"connection. Ignoring send of type 0x%02x.\n", type);
 			return;
@@ -300,6 +320,7 @@
 			int len = data ? strlen(data) : 0;
 			purple_circ_buffer_append(conn->pending, data, len);
 		}
+
 		return;
 	}
 
@@ -316,9 +337,11 @@
 	                conn->sid,
 	                conn->js->user->domain);
 
-	if (type == PACKET_STREAM_RESTART)
+	if (type == PACKET_STREAM_RESTART) {
 		packet = g_string_append(packet, " xmpp:restart='true'/>");
-	else {
+		/* TODO: Do we need to wait for a response? */
+		conn->needs_restart = FALSE;
+	} else {
 		gsize read_amt;
 		if (type == PACKET_TERMINATE)
 			packet = g_string_append(packet, " type='terminate'");
@@ -343,7 +366,9 @@
 	jabber_bosh_connection_send(conn, PACKET_TERMINATE, NULL);
 }
 
-static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn) {
+static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn)
+{
+	conn->needs_restart = TRUE;
 	jabber_bosh_connection_send(conn, PACKET_STREAM_RESTART, NULL);
 }
 
@@ -353,7 +378,7 @@
 	type = xmlnode_get_attrib(node, "type");
 
 	if (type != NULL && !strcmp(type, "terminate")) {
-		conn->ready = FALSE;
+		conn->state = BOSH_CONN_OFFLINE;
 		purple_connection_error_reason(conn->js->gc,
 			PURPLE_CONNECTION_ERROR_OTHER_ERROR,
 			_("The BOSH connection manager terminated your session."));
@@ -384,11 +409,6 @@
 		/* jabber_process_packet might free child */
 		xmlnode *next = child->next;
 		if (child->type == XMLNODE_TYPE_TAG) {
-			if (!strcmp(child->name, "iq")) {
-				if (xmlnode_get_child(child, "session"))
-					conn->ready = TRUE;
-			}
-
 			jabber_process_packet(js, &child);
 		}
 
@@ -476,9 +496,13 @@
 			conn->max_inactivity = 0;
 		} else {
 			/* TODO: Integrate this with jabber.c keepalive checks... */
-			conn->inactivity_timer = purple_timeout_add_seconds(
-					conn->max_inactivity - 2 /* rounding */, bosh_inactivity_cb,
-					conn);
+			if (conn->inactivity_timer == 0) {
+				purple_debug_misc("jabber", "Starting BOSH inactivity timer for %d secs (compensating for rounding)\n",
+						conn->max_inactivity - 5);
+				conn->inactivity_timer = purple_timeout_add_seconds(
+						conn->max_inactivity - 5 /* rounding */,
+						bosh_inactivity_cb, conn);
+			}
 		}
 	}
 
@@ -487,6 +511,7 @@
 
 	/* FIXME: Depending on receiving features might break with some hosts */
 	packet = xmlnode_get_child(node, "features");
+	conn->state = BOSH_CONN_ONLINE;
 	conn->js->use_bosh = TRUE;
 	conn->receive_cb = auth_response_cb;
 	jabber_stream_features_parse(conn->js, packet);
@@ -511,6 +536,8 @@
 	                conn->js->user->domain,
 	                ++conn->rid);
 
+	purple_debug_misc("jabber", "SendBOSH Boot %s(%" G_GSIZE_FORMAT "): %s\n",
+	                  conn->ssl ? "(ssl)" : "", buf->len, buf->str);
 	conn->receive_cb = boot_response_cb;
 	http_connection_send_request(conn->connections[0], buf);
 	g_string_free(buf, TRUE);
@@ -550,7 +577,7 @@
 connection_common_established_cb(PurpleHTTPConnection *conn)
 {
 	/* Indicate we're ready and reset some variables */
-	conn->ready = TRUE;
+	conn->state = HTTP_CONN_CONNECTED;
 	conn->requests = 0;
 	if (conn->buf) {
 		g_string_free(conn->buf, TRUE);
@@ -559,9 +586,11 @@
 	conn->headers_done = FALSE;
 	conn->handled_len = conn->body_len = 0;
 
-	if (conn->bosh->ready) {
+	if (conn->bosh->needs_restart)
+		jabber_bosh_connection_stream_restart(conn->bosh);
+	else if (conn->bosh->state == BOSH_CONN_ONLINE) {
 		purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n");
-		if (conn->bosh->pending->bufused > 0) {
+		if (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0) {
 			/* Send the pending data */
 			jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL);
 		}
@@ -585,7 +614,7 @@
 	 * Well, then. Fine! I never liked you anyway, server! I was cheating on you
 	 * with AIM!
 	 */
-	conn->ready = FALSE;
+	conn->state = HTTP_CONN_OFFLINE;
 	if (conn->psc) {
 		purple_ssl_close(conn->psc);
 		conn->psc = NULL;
@@ -620,6 +649,10 @@
 
 void jabber_bosh_connection_connect(PurpleBOSHConnection *bosh) {
 	PurpleHTTPConnection *conn = bosh->connections[0];
+
+	g_return_if_fail(bosh->state == BOSH_CONN_OFFLINE);
+	bosh->state = BOSH_CONN_BOOTING;
+
 	http_connection_connect(conn);
 }
 
@@ -679,10 +712,10 @@
 	http_received_cb(conn->buf->str + conn->handled_len, conn->body_len,
 	                 conn->bosh);
 
-	if (conn->bosh->ready &&
+	if (conn->bosh->state == BOSH_CONN_ONLINE &&
 			(conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0)) {
+		purple_debug_misc("jabber", "BOSH: Sending an empty request\n");
 		jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL);
-		purple_debug_misc("jabber", "BOSH: Sending an empty request\n");
 	}
 
 	g_string_free(conn->buf, TRUE);
@@ -802,6 +835,8 @@
 	PurpleConnection *gc = bosh->js->gc;
 	PurpleAccount *account = purple_connection_get_account(gc);
 
+	conn->state = HTTP_CONN_CONNECTING;
+
 	if (bosh->ssl) {
 		if (purple_ssl_is_supported()) {
 			conn->psc = purple_ssl_connect(account, bosh->host, bosh->port,

mercurial