diff libpurple/protocols/jabber/bosh.c @ 27622:70dcaa0b6fea

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.
author Paul Aurich <paul@darkrain42.org>
date Wed, 15 Jul 2009 05:49:30 +0000
parents f541583e31bd
children 14e5eadff540
line wrap: on
line diff
--- 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,