# HG changeset patch # User Paul Aurich # Date 1247636970 0 # Node ID 70dcaa0b6fea55c9474ee0d56eae128c2982d7b2 # Parent 5c17c400153d0c626886b48716d48e49c8548e7a 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. diff -r 5c17c400153d -r 70dcaa0b6fea libpurple/protocols/jabber/bosh.c --- 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,