comparison libpurple/protocols/jabber/bosh.c @ 27546: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
comparison
equal deleted inserted replaced
27545:5c17c400153d 27546:70dcaa0b6fea
49 JabberStream *js; 49 JabberStream *js;
50 gboolean pipelining; 50 gboolean pipelining;
51 PurpleHTTPConnection *connections[MAX_HTTP_CONNECTIONS]; 51 PurpleHTTPConnection *connections[MAX_HTTP_CONNECTIONS];
52 unsigned short failed_connections; 52 unsigned short failed_connections;
53 53
54 gboolean ready; 54 enum {
55 BOSH_CONN_OFFLINE,
56 BOSH_CONN_BOOTING,
57 BOSH_CONN_ONLINE
58 } state;
55 gboolean ssl; 59 gboolean ssl;
60 gboolean needs_restart;
56 61
57 /* decoded URL */ 62 /* decoded URL */
58 char *host; 63 char *host;
59 int port; 64 int port;
60 char *path; 65 char *path;
82 guint readh; 87 guint readh;
83 guint writeh; 88 guint writeh;
84 89
85 PurpleCircBuffer *write_buffer; 90 PurpleCircBuffer *write_buffer;
86 91
87 gboolean ready; 92 enum {
93 HTTP_CONN_OFFLINE,
94 HTTP_CONN_CONNECTING,
95 HTTP_CONN_CONNECTED
96 } state;
88 int requests; /* number of outstanding HTTP requests */ 97 int requests; /* number of outstanding HTTP requests */
89 98
90 GString *buf; 99 GString *buf;
91 gboolean headers_done; 100 gboolean headers_done;
92 gsize handled_len; 101 gsize handled_len;
127 jabber_bosh_http_connection_init(PurpleBOSHConnection *bosh) 136 jabber_bosh_http_connection_init(PurpleBOSHConnection *bosh)
128 { 137 {
129 PurpleHTTPConnection *conn = g_new0(PurpleHTTPConnection, 1); 138 PurpleHTTPConnection *conn = g_new0(PurpleHTTPConnection, 1);
130 conn->bosh = bosh; 139 conn->bosh = bosh;
131 conn->fd = -1; 140 conn->fd = -1;
132 conn->ready = FALSE; 141 conn->state = HTTP_CONN_OFFLINE;
133 142
134 conn->write_buffer = purple_circ_buffer_new(0 /* default grow size */); 143 conn->write_buffer = purple_circ_buffer_new(0 /* default grow size */);
135 144
136 return conn; 145 return conn;
137 } 146 }
174 conn->host = host; 183 conn->host = host;
175 conn->port = port; 184 conn->port = port;
176 conn->path = g_strdup_printf("/%s", path); 185 conn->path = g_strdup_printf("/%s", path);
177 g_free(path); 186 g_free(path);
178 conn->pipelining = TRUE; 187 conn->pipelining = TRUE;
188 conn->needs_restart = FALSE;
179 189
180 if ((user && user[0] != '\0') || (passwd && passwd[0] != '\0')) { 190 if ((user && user[0] != '\0') || (passwd && passwd[0] != '\0')) {
181 purple_debug_info("jabber", "Ignoring unexpected username and password " 191 purple_debug_info("jabber", "Ignoring unexpected username and password "
182 "in BOSH URL.\n"); 192 "in BOSH URL.\n");
183 } 193 }
196 conn->rid = ((guint64)g_random_int() << 32) | g_random_int(); 206 conn->rid = ((guint64)g_random_int() << 32) | g_random_int();
197 conn->rid &= 0xFFFFFFFFFFFFFLL; 207 conn->rid &= 0xFFFFFFFFFFFFFLL;
198 208
199 conn->pending = purple_circ_buffer_new(0 /* default grow size */); 209 conn->pending = purple_circ_buffer_new(0 /* default grow size */);
200 210
201 conn->ready = FALSE; 211 conn->state = BOSH_CONN_OFFLINE;
202 if (purple_strcasestr(url, "https://") != NULL) 212 if (purple_strcasestr(url, "https://") != NULL)
203 conn->ssl = TRUE; 213 conn->ssl = TRUE;
204 else 214 else
205 conn->ssl = FALSE; 215 conn->ssl = FALSE;
206 216
241 int i; 251 int i;
242 252
243 /* Easy solution: Does everyone involved support pipelining? Hooray! Just use 253 /* Easy solution: Does everyone involved support pipelining? Hooray! Just use
244 * one TCP connection! */ 254 * one TCP connection! */
245 if (conn->pipelining) 255 if (conn->pipelining)
246 return conn->connections[0]->ready ? conn->connections[0] : NULL; 256 return conn->connections[0]->state == HTTP_CONN_CONNECTED ?
257 conn->connections[0] : NULL;
247 258
248 /* First loop, look for a connection that's ready */ 259 /* First loop, look for a connection that's ready */
249 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) { 260 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
250 if (conn->connections[i] && conn->connections[i]->ready && 261 if (conn->connections[i] &&
251 conn->connections[i]->requests == 0) 262 conn->connections[i]->state == HTTP_CONN_CONNECTED &&
263 conn->connections[i]->requests == 0)
252 return conn->connections[i]; 264 return conn->connections[i];
253 } 265 }
254 266
255 /* Second loop, look for one that's NULL and create a new connection */ 267 /* Second loop, is something currently connecting? If so, just queue up. */
268 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
269 if (conn->connections[i] &&
270 conn->connections[i]->state == HTTP_CONN_CONNECTING)
271 return NULL;
272 }
273
274 /* Third loop, look for one that's NULL and create a new connection */
256 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) { 275 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
257 if (!conn->connections[i]) { 276 if (!conn->connections[i]) {
277 purple_debug_info("jabber", "bosh: Creating and connecting new httpconn\n");
258 conn->connections[i] = jabber_bosh_http_connection_init(conn); 278 conn->connections[i] = jabber_bosh_http_connection_init(conn);
259 279
260 http_connection_connect(conn->connections[i]); 280 http_connection_connect(conn->connections[i]);
261 return NULL; 281 return NULL;
262 } 282 }
266 return NULL; 286 return NULL;
267 } 287 }
268 288
269 static void 289 static void
270 jabber_bosh_connection_send(PurpleBOSHConnection *conn, 290 jabber_bosh_connection_send(PurpleBOSHConnection *conn,
271 PurpleBOSHPacketType type, const char *data) 291 const PurpleBOSHPacketType type, const char *data)
272 { 292 {
273 PurpleHTTPConnection *chosen; 293 PurpleHTTPConnection *chosen;
274 GString *packet = NULL; 294 GString *packet = NULL;
275 295
276 chosen = find_available_http_connection(conn); 296 chosen = find_available_http_connection(conn);
277 297
278 if (type != PACKET_NORMAL && !chosen) { 298 if (type != PACKET_NORMAL && !chosen) {
279 /* 299 /*
280 * For non-ordinary traffic, we don't want to 'buffer' it, so use the 300 * For non-ordinary traffic, we can't 'buffer' it, so use the
281 * first connection. 301 * first connection.
282 */ 302 */
283 chosen = conn->connections[0]; 303 chosen = conn->connections[0];
284 304
285 if (!chosen->ready) { 305 if (chosen->state != HTTP_CONN_CONNECTED) {
286 purple_debug_info("jabber", "Unable to find a ready BOSH " 306 purple_debug_info("jabber", "Unable to find a ready BOSH "
287 "connection. Ignoring send of type 0x%02x.\n", type); 307 "connection. Ignoring send of type 0x%02x.\n", type);
288 return; 308 return;
289 } 309 }
290 } 310 }
298 */ 318 */
299 if (data) { 319 if (data) {
300 int len = data ? strlen(data) : 0; 320 int len = data ? strlen(data) : 0;
301 purple_circ_buffer_append(conn->pending, data, len); 321 purple_circ_buffer_append(conn->pending, data, len);
302 } 322 }
323
303 return; 324 return;
304 } 325 }
305 326
306 packet = g_string_new(NULL); 327 packet = g_string_new(NULL);
307 328
314 "xmlns:xmpp='urn:xmpp:xbosh'", 335 "xmlns:xmpp='urn:xmpp:xbosh'",
315 ++conn->rid, 336 ++conn->rid,
316 conn->sid, 337 conn->sid,
317 conn->js->user->domain); 338 conn->js->user->domain);
318 339
319 if (type == PACKET_STREAM_RESTART) 340 if (type == PACKET_STREAM_RESTART) {
320 packet = g_string_append(packet, " xmpp:restart='true'/>"); 341 packet = g_string_append(packet, " xmpp:restart='true'/>");
321 else { 342 /* TODO: Do we need to wait for a response? */
343 conn->needs_restart = FALSE;
344 } else {
322 gsize read_amt; 345 gsize read_amt;
323 if (type == PACKET_TERMINATE) 346 if (type == PACKET_TERMINATE)
324 packet = g_string_append(packet, " type='terminate'"); 347 packet = g_string_append(packet, " type='terminate'");
325 348
326 packet = g_string_append_c(packet, '>'); 349 packet = g_string_append_c(packet, '>');
341 void jabber_bosh_connection_close(PurpleBOSHConnection *conn) 364 void jabber_bosh_connection_close(PurpleBOSHConnection *conn)
342 { 365 {
343 jabber_bosh_connection_send(conn, PACKET_TERMINATE, NULL); 366 jabber_bosh_connection_send(conn, PACKET_TERMINATE, NULL);
344 } 367 }
345 368
346 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn) { 369 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn)
370 {
371 conn->needs_restart = TRUE;
347 jabber_bosh_connection_send(conn, PACKET_STREAM_RESTART, NULL); 372 jabber_bosh_connection_send(conn, PACKET_STREAM_RESTART, NULL);
348 } 373 }
349 374
350 static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) { 375 static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) {
351 const char *type; 376 const char *type;
352 377
353 type = xmlnode_get_attrib(node, "type"); 378 type = xmlnode_get_attrib(node, "type");
354 379
355 if (type != NULL && !strcmp(type, "terminate")) { 380 if (type != NULL && !strcmp(type, "terminate")) {
356 conn->ready = FALSE; 381 conn->state = BOSH_CONN_OFFLINE;
357 purple_connection_error_reason(conn->js->gc, 382 purple_connection_error_reason(conn->js->gc,
358 PURPLE_CONNECTION_ERROR_OTHER_ERROR, 383 PURPLE_CONNECTION_ERROR_OTHER_ERROR,
359 _("The BOSH connection manager terminated your session.")); 384 _("The BOSH connection manager terminated your session."));
360 return TRUE; 385 return TRUE;
361 } 386 }
382 child = node->child; 407 child = node->child;
383 while (child != NULL) { 408 while (child != NULL) {
384 /* jabber_process_packet might free child */ 409 /* jabber_process_packet might free child */
385 xmlnode *next = child->next; 410 xmlnode *next = child->next;
386 if (child->type == XMLNODE_TYPE_TAG) { 411 if (child->type == XMLNODE_TYPE_TAG) {
387 if (!strcmp(child->name, "iq")) {
388 if (xmlnode_get_child(child, "session"))
389 conn->ready = TRUE;
390 }
391
392 jabber_process_packet(js, &child); 412 jabber_process_packet(js, &child);
393 } 413 }
394 414
395 child = next; 415 child = next;
396 } 416 }
474 purple_debug_warning("jabber", "Ignoring bogusly small inactivity: %s\n", 494 purple_debug_warning("jabber", "Ignoring bogusly small inactivity: %s\n",
475 inactivity); 495 inactivity);
476 conn->max_inactivity = 0; 496 conn->max_inactivity = 0;
477 } else { 497 } else {
478 /* TODO: Integrate this with jabber.c keepalive checks... */ 498 /* TODO: Integrate this with jabber.c keepalive checks... */
479 conn->inactivity_timer = purple_timeout_add_seconds( 499 if (conn->inactivity_timer == 0) {
480 conn->max_inactivity - 2 /* rounding */, bosh_inactivity_cb, 500 purple_debug_misc("jabber", "Starting BOSH inactivity timer for %d secs (compensating for rounding)\n",
481 conn); 501 conn->max_inactivity - 5);
502 conn->inactivity_timer = purple_timeout_add_seconds(
503 conn->max_inactivity - 5 /* rounding */,
504 bosh_inactivity_cb, conn);
505 }
482 } 506 }
483 } 507 }
484 508
485 if (requests) 509 if (requests)
486 conn->max_requests = atoi(requests); 510 conn->max_requests = atoi(requests);
487 511
488 /* FIXME: Depending on receiving features might break with some hosts */ 512 /* FIXME: Depending on receiving features might break with some hosts */
489 packet = xmlnode_get_child(node, "features"); 513 packet = xmlnode_get_child(node, "features");
514 conn->state = BOSH_CONN_ONLINE;
490 conn->js->use_bosh = TRUE; 515 conn->js->use_bosh = TRUE;
491 conn->receive_cb = auth_response_cb; 516 conn->receive_cb = auth_response_cb;
492 jabber_stream_features_parse(conn->js, packet); 517 jabber_stream_features_parse(conn->js, packet);
493 } 518 }
494 519
509 "hold='1' " 534 "hold='1' "
510 "xmlns='http://jabber.org/protocol/httpbind'/>", 535 "xmlns='http://jabber.org/protocol/httpbind'/>",
511 conn->js->user->domain, 536 conn->js->user->domain,
512 ++conn->rid); 537 ++conn->rid);
513 538
539 purple_debug_misc("jabber", "SendBOSH Boot %s(%" G_GSIZE_FORMAT "): %s\n",
540 conn->ssl ? "(ssl)" : "", buf->len, buf->str);
514 conn->receive_cb = boot_response_cb; 541 conn->receive_cb = boot_response_cb;
515 http_connection_send_request(conn->connections[0], buf); 542 http_connection_send_request(conn->connections[0], buf);
516 g_string_free(buf, TRUE); 543 g_string_free(buf, TRUE);
517 } 544 }
518 545
548 575
549 static void 576 static void
550 connection_common_established_cb(PurpleHTTPConnection *conn) 577 connection_common_established_cb(PurpleHTTPConnection *conn)
551 { 578 {
552 /* Indicate we're ready and reset some variables */ 579 /* Indicate we're ready and reset some variables */
553 conn->ready = TRUE; 580 conn->state = HTTP_CONN_CONNECTED;
554 conn->requests = 0; 581 conn->requests = 0;
555 if (conn->buf) { 582 if (conn->buf) {
556 g_string_free(conn->buf, TRUE); 583 g_string_free(conn->buf, TRUE);
557 conn->buf = NULL; 584 conn->buf = NULL;
558 } 585 }
559 conn->headers_done = FALSE; 586 conn->headers_done = FALSE;
560 conn->handled_len = conn->body_len = 0; 587 conn->handled_len = conn->body_len = 0;
561 588
562 if (conn->bosh->ready) { 589 if (conn->bosh->needs_restart)
590 jabber_bosh_connection_stream_restart(conn->bosh);
591 else if (conn->bosh->state == BOSH_CONN_ONLINE) {
563 purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n"); 592 purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n");
564 if (conn->bosh->pending->bufused > 0) { 593 if (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0) {
565 /* Send the pending data */ 594 /* Send the pending data */
566 jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL); 595 jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL);
567 } 596 }
568 #if 0 597 #if 0
569 conn->bosh->receive_cb = jabber_bosh_connection_received; 598 conn->bosh->receive_cb = jabber_bosh_connection_received;
583 { 612 {
584 /* 613 /*
585 * Well, then. Fine! I never liked you anyway, server! I was cheating on you 614 * Well, then. Fine! I never liked you anyway, server! I was cheating on you
586 * with AIM! 615 * with AIM!
587 */ 616 */
588 conn->ready = FALSE; 617 conn->state = HTTP_CONN_OFFLINE;
589 if (conn->psc) { 618 if (conn->psc) {
590 purple_ssl_close(conn->psc); 619 purple_ssl_close(conn->psc);
591 conn->psc = NULL; 620 conn->psc = NULL;
592 } else if (conn->fd >= 0) { 621 } else if (conn->fd >= 0) {
593 close(conn->fd); 622 close(conn->fd);
618 } 647 }
619 } 648 }
620 649
621 void jabber_bosh_connection_connect(PurpleBOSHConnection *bosh) { 650 void jabber_bosh_connection_connect(PurpleBOSHConnection *bosh) {
622 PurpleHTTPConnection *conn = bosh->connections[0]; 651 PurpleHTTPConnection *conn = bosh->connections[0];
652
653 g_return_if_fail(bosh->state == BOSH_CONN_OFFLINE);
654 bosh->state = BOSH_CONN_BOOTING;
655
623 http_connection_connect(conn); 656 http_connection_connect(conn);
624 } 657 }
625 658
626 static void 659 static void
627 jabber_bosh_http_connection_process(PurpleHTTPConnection *conn) 660 jabber_bosh_http_connection_process(PurpleHTTPConnection *conn)
677 --conn->bosh->requests; 710 --conn->bosh->requests;
678 711
679 http_received_cb(conn->buf->str + conn->handled_len, conn->body_len, 712 http_received_cb(conn->buf->str + conn->handled_len, conn->body_len,
680 conn->bosh); 713 conn->bosh);
681 714
682 if (conn->bosh->ready && 715 if (conn->bosh->state == BOSH_CONN_ONLINE &&
683 (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0)) { 716 (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0)) {
717 purple_debug_misc("jabber", "BOSH: Sending an empty request\n");
684 jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL); 718 jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL);
685 purple_debug_misc("jabber", "BOSH: Sending an empty request\n");
686 } 719 }
687 720
688 g_string_free(conn->buf, TRUE); 721 g_string_free(conn->buf, TRUE);
689 conn->buf = NULL; 722 conn->buf = NULL;
690 conn->headers_done = FALSE; 723 conn->headers_done = FALSE;
799 static void http_connection_connect(PurpleHTTPConnection *conn) 832 static void http_connection_connect(PurpleHTTPConnection *conn)
800 { 833 {
801 PurpleBOSHConnection *bosh = conn->bosh; 834 PurpleBOSHConnection *bosh = conn->bosh;
802 PurpleConnection *gc = bosh->js->gc; 835 PurpleConnection *gc = bosh->js->gc;
803 PurpleAccount *account = purple_connection_get_account(gc); 836 PurpleAccount *account = purple_connection_get_account(gc);
837
838 conn->state = HTTP_CONN_CONNECTING;
804 839
805 if (bosh->ssl) { 840 if (bosh->ssl) {
806 if (purple_ssl_is_supported()) { 841 if (purple_ssl_is_supported()) {
807 conn->psc = purple_ssl_connect(account, bosh->host, bosh->port, 842 conn->psc = purple_ssl_connect(account, bosh->host, bosh->port,
808 ssl_connection_established_cb, 843 ssl_connection_established_cb,