diff options
author | Gary Kramlich <grim@reaperworld.com> | 2018-05-25 23:35:53 +0000 |
---|---|---|
committer | Gary Kramlich <grim@reaperworld.com> | 2018-05-25 23:35:53 +0000 |
commit | 2970014f2208011ba806e9d03668daedd2a5a581 (patch) | |
tree | dbc24ac30f082f97e844413b5aa45f548298943d | |
parent | a7bcce60fd37b5f73bed6b702e2ba8bb14dbbe28 (diff) | |
parent | f5e292e94a23174fcb26e60a5626b7d8051c6a64 (diff) | |
download | pidgin-2970014f2208011ba806e9d03668daedd2a5a581.tar.gz |
Merged in defanor/pidgin/xep-0198 (pull request #309)
XEP-0198: Stream management
Approved-by: Eion Robb <eionrobb@gmail.com>
Approved-by: Gary Kramlich <grim@reaperworld.com>
Approved-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
-rw-r--r-- | libpurple/protocols/jabber/Makefile.am | 2 | ||||
-rw-r--r-- | libpurple/protocols/jabber/jabber.c | 49 | ||||
-rw-r--r-- | libpurple/protocols/jabber/jabber.h | 16 | ||||
-rw-r--r-- | libpurple/protocols/jabber/namespaces.h | 4 | ||||
-rw-r--r-- | libpurple/protocols/jabber/stream_management.c | 241 | ||||
-rw-r--r-- | libpurple/protocols/jabber/stream_management.h | 36 |
6 files changed, 344 insertions, 4 deletions
diff --git a/libpurple/protocols/jabber/Makefile.am b/libpurple/protocols/jabber/Makefile.am index 9fdd3a9476..a1c6c2cea4 100644 --- a/libpurple/protocols/jabber/Makefile.am +++ b/libpurple/protocols/jabber/Makefile.am @@ -80,6 +80,8 @@ JABBERSOURCES = \ roster.h \ si.c \ si.h \ + stream_management.c \ + stream_management.h \ useravatar.c \ useravatar.h \ usermood.c \ diff --git a/libpurple/protocols/jabber/jabber.c b/libpurple/protocols/jabber/jabber.c index ca6640ed02..da67dc8d7a 100644 --- a/libpurple/protocols/jabber/jabber.c +++ b/libpurple/protocols/jabber/jabber.c @@ -68,6 +68,7 @@ #include "xdata.h" #include "pep.h" #include "adhoccommands.h" +#include "stream_management.h" #include "jingle/jingle.h" #include "jingle/rtp.h" @@ -312,6 +313,13 @@ void jabber_stream_features_parse(JabberStream *js, xmlnode *packet) jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING); jabber_auth_start_old(js); } + + /* Stream management */ + if (xmlnode_get_child_with_namespace(packet, "sm", NS_STREAM_MANAGEMENT) + && (js->sm_state == SM_DISABLED) ) + { + jabber_sm_enable(js); + } } static void jabber_stream_handle_error(JabberStream *js, xmlnode *packet) @@ -340,6 +348,8 @@ void jabber_process_packet(JabberStream *js, xmlnode **packet) name = (*packet)->name; xmlns = xmlnode_get_namespace(*packet); + jabber_sm_inbound(js, *packet); + if(purple_strequal((*packet)->name, "iq")) { jabber_iq_parse(js, *packet); } else if(purple_strequal((*packet)->name, "presence")) { @@ -370,6 +380,8 @@ void jabber_process_packet(JabberStream *js, xmlnode **packet) tls_init(js); /* TODO: Handle <failure/>, I guess? */ } + } else if (purple_strequal(xmlns, NS_STREAM_MANAGEMENT)) { + jabber_sm_process_packet(js, *packet); } else { purple_debug_warning("jabber", "Unknown packet: %s\n", (*packet)->name); } @@ -583,6 +595,28 @@ int jabber_prpl_send_raw(PurpleConnection *gc, const char *buf, int len) return (len < 0 ? (int)strlen(buf) : len); } +/* Checks whether a packet may be a stanza (not strictly: returns TRUE + if there's no namespace, assuming that the default namespace is + jabber:client or jabber:server). */ +gboolean +jabber_is_stanza(xmlnode *packet) { + const char *name; + const char *xmlns; + + g_return_val_if_fail(packet != NULL, FALSE); + g_return_val_if_fail(packet->name != NULL, FALSE); + + name = packet->name; + xmlns = xmlnode_get_namespace(packet); + + return ((purple_strequal(name, "message") + || purple_strequal(name, "iq") + || purple_strequal(name, "presence")) + && ((xmlns == NULL) + || purple_strequal(xmlns, NS_XMPP_CLIENT) + || purple_strequal(xmlns, NS_XMPP_SERVER))); +} + void jabber_send_signal_cb(PurpleConnection *pc, xmlnode **packet, gpointer unused) { @@ -601,13 +635,13 @@ void jabber_send_signal_cb(PurpleConnection *pc, xmlnode **packet, return; if (js->bosh) - if (purple_strequal((*packet)->name, "message") || - purple_strequal((*packet)->name, "iq") || - purple_strequal((*packet)->name, "presence")) + if (jabber_is_stanza(*packet)) xmlnode_set_namespace(*packet, NS_XMPP_CLIENT); txt = xmlnode_to_str(*packet, &len); jabber_send_raw(js, txt, len); g_free(txt); + + jabber_sm_outbound(js, *packet); } void jabber_send(JabberStream *js, xmlnode *packet) @@ -1015,6 +1049,8 @@ jabber_stream_new(PurpleAccount *account) if (purple_presence_is_idle(presence)) js->idle = purple_presence_get_idle_time(presence); + js->sm_state = SM_DISABLED; + return js; } @@ -1597,8 +1633,10 @@ void jabber_close(PurpleConnection *gc) if (js->bosh) jabber_bosh_connection_close(js->bosh); - else if ((js->gsc && js->gsc->fd > 0) || js->fd > 0) + else if ((js->gsc && js->gsc->fd > 0) || js->fd > 0) { + jabber_sm_ack_send(js); jabber_send_raw(js, "</stream:stream>", -1); + } if (js->srv_query_data) purple_srv_cancel(js->srv_query_data); @@ -3896,12 +3934,15 @@ jabber_do_init(void) jabber_si_init(); jabber_auth_init(); + + jabber_sm_init(); } static void jabber_do_uninit(void) { /* reverse order of jabber_do_init */ + jabber_sm_uninit(); jabber_bosh_uninit(); jabber_data_uninit(); jabber_si_uninit(); diff --git a/libpurple/protocols/jabber/jabber.h b/libpurple/protocols/jabber/jabber.h index ebe5bad596..db94383839 100644 --- a/libpurple/protocols/jabber/jabber.h +++ b/libpurple/protocols/jabber/jabber.h @@ -49,6 +49,8 @@ typedef enum { JABBER_CAP_ITEMS = 1 << 14, JABBER_CAP_ROSTER_VERSIONING = 1 << 15, + JABBER_CAP_STREAM_MANAGEMENT = 1 << 16, + JABBER_CAP_RETRIEVED = 1 << 31 } JabberCapabilities; @@ -96,6 +98,12 @@ typedef enum { JABBER_STREAM_CONNECTED } JabberStreamState; +typedef enum { + SM_DISABLED, + SM_REQUESTED, + SM_ENABLED +} JabberStreamManagementState; + struct _JabberStream { int fd; @@ -282,6 +290,12 @@ struct _JabberStream gchar *google_relay_host; GList *google_relay_requests; /* the HTTP requests to get */ /* relay info */ + + /* XEP-0198 (stream management) state */ + guint32 sm_outbound_count; + guint32 sm_inbound_count; + guint32 sm_outbound_confirmed; + JabberStreamManagementState sm_state; }; typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *namespace); @@ -314,6 +328,8 @@ extern GList *jabber_features; */ extern GList *jabber_identities; +gboolean jabber_is_stanza(xmlnode *packet); + void jabber_stream_features_parse(JabberStream *js, xmlnode *packet); void jabber_process_packet(JabberStream *js, xmlnode **packet); void jabber_send(JabberStream *js, xmlnode *data); diff --git a/libpurple/protocols/jabber/namespaces.h b/libpurple/protocols/jabber/namespaces.h index 12c42411c1..b9b4473b6a 100644 --- a/libpurple/protocols/jabber/namespaces.h +++ b/libpurple/protocols/jabber/namespaces.h @@ -26,6 +26,7 @@ #define NS_XMPP_BIND "urn:ietf:params:xml:ns:xmpp-bind" #define NS_XMPP_CLIENT "jabber:client" +#define NS_XMPP_SERVER "jabber:server" #define NS_XMPP_SASL "urn:ietf:params:xml:ns:xmpp-sasl" #define NS_XMPP_SESSION "urn:ietf:params:xml:ns:xmpp-session" #define NS_XMPP_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas" @@ -70,6 +71,9 @@ /* XEP-0191 Simple Communications Blocking */ #define NS_SIMPLE_BLOCKING "urn:xmpp:blocking" +/* XEP-0198 Stream Management */ +#define NS_STREAM_MANAGEMENT "urn:xmpp:sm:3" + /* XEP-0199 Ping */ #define NS_PING "urn:xmpp:ping" diff --git a/libpurple/protocols/jabber/stream_management.c b/libpurple/protocols/jabber/stream_management.c new file mode 100644 index 0000000000..72a7d2cbe4 --- /dev/null +++ b/libpurple/protocols/jabber/stream_management.c @@ -0,0 +1,241 @@ +/* + * purple - Jabber Protocol Plugin + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + */ + +#include "internal.h" + +#include <glib.h> +#include "namespaces.h" +#include "xmlnode.h" +#include "jabber.h" +#include "debug.h" +#include "notify.h" +#include "stream_management.h" + +#define MAX_QUEUE_LENGTH 10000 + +GHashTable *jabber_sm_accounts; + +static void +jabber_sm_accounts_queue_free(gpointer q) +{ + g_queue_free_full(q, (GDestroyNotify)xmlnode_free); +} + +/* Returns a queue for a JabberStream's account (based on JID), + creates it if there's none. */ +static GQueue * +jabber_sm_accounts_queue_get(JabberStream *js) +{ + GQueue *queue; + gchar *jid = jabber_id_get_bare_jid(js->user); + if (g_hash_table_contains(jabber_sm_accounts, jid) == TRUE) { + queue = g_hash_table_lookup(jabber_sm_accounts, jid); + g_free(jid); + } else { + queue = g_queue_new(); + g_hash_table_insert(jabber_sm_accounts, jid, queue); + } + return queue; +} + +void +jabber_sm_init(void) +{ + jabber_sm_accounts = g_hash_table_new_full(g_str_hash, g_str_equal, free, + jabber_sm_accounts_queue_free); +} + +void +jabber_sm_uninit(void) +{ + g_hash_table_destroy(jabber_sm_accounts); +} + +/* Processes incoming NS_STREAM_MANAGEMENT packets. */ +void +jabber_sm_process_packet(JabberStream *js, xmlnode *packet) { + gchar *jid; + const char *name = packet->name; + if (purple_strequal(name, "enabled")) { + purple_debug_info("XEP-0198", "Stream management is enabled\n"); + js->sm_inbound_count = 0; + js->sm_state = SM_ENABLED; + } else if (purple_strequal(name, "failed")) { + purple_debug_error("XEP-0198", "Failed to enable stream management\n"); + js->sm_state = SM_DISABLED; + jid = jabber_id_get_bare_jid(js->user); + g_hash_table_remove(jabber_sm_accounts, jid); + g_free(jid); + } else if (purple_strequal(name, "r")) { + jabber_sm_ack_send(js); + } else if (purple_strequal(name, "a")) { + jabber_sm_ack_read(js, packet); + } else { + purple_debug_error("XEP-0198", "Unknown packet: %s\n", name); + } +} + +/* Sends an acknowledgement. */ +void +jabber_sm_ack_send(JabberStream *js) +{ + xmlnode *ack; + char *ack_h; + if (js->sm_state != SM_ENABLED) { + return; + } + ack = xmlnode_new("a"); + ack_h = g_strdup_printf("%u", js->sm_inbound_count); + xmlnode_set_namespace(ack, NS_STREAM_MANAGEMENT); + xmlnode_set_attrib(ack, "h", ack_h); + jabber_send(js, ack); + xmlnode_free(ack); + g_free(ack_h); +} + +/* Reads acknowledgements, removes queued stanzas. */ +void +jabber_sm_ack_read(JabberStream *js, xmlnode *packet) +{ + guint32 i; + guint32 h; + GQueue *queue; + xmlnode *stanza; + const char *ack_h = xmlnode_get_attrib(packet, "h"); + if (ack_h == NULL) { + purple_debug_error("XEP-0198", + "The 'h' attribute is not defined for an answer."); + return; + } + h = strtoul(ack_h, NULL, 10); + + /* Remove stanzas from the queue */ + queue = jabber_sm_accounts_queue_get(js); + for (i = js->sm_outbound_confirmed; i < h; i++) { + stanza = g_queue_pop_head(queue); + if (stanza == NULL) { + purple_debug_error("XEP-0198", "The queue is empty\n"); + break; + } + xmlnode_free(stanza); + } + + js->sm_outbound_confirmed = h; + purple_debug_info("XEP-0198", + "Acknowledged %u out of %u outbound stanzas\n", + js->sm_outbound_confirmed, js->sm_outbound_count); +} + +/* Asks a server to enable stream management, resends queued + stanzas. */ +void +jabber_sm_enable(JabberStream *js) +{ + xmlnode *enable; + xmlnode *stanza; + GQueue *queue; + guint queue_len; + guint i; + js->server_caps |= JABBER_CAP_STREAM_MANAGEMENT; + purple_debug_info("XEP-0198", "Enabling stream management\n"); + enable = xmlnode_new("enable"); + xmlnode_set_namespace(enable, NS_STREAM_MANAGEMENT); + jabber_send(js, enable); + xmlnode_free(enable); + js->sm_outbound_count = 0; + js->sm_outbound_confirmed = 0; + js->sm_state = SM_REQUESTED; + + /* Resend unacknowledged stanzas from the queue. */ + queue = jabber_sm_accounts_queue_get(js); + queue_len = g_queue_get_length(queue); + if (queue_len > 0) { + purple_debug_info("XEP-0198", "Resending %u stanzas\n", queue_len); + } + for (i = 0; i < queue_len; i++) { + stanza = g_queue_pop_head(queue); + jabber_send(js, stanza); + xmlnode_free(stanza); + } +} + +/* Tracks outbound stanzas, stores those into a queue, requests + acknowledgements. */ +void +jabber_sm_outbound(JabberStream *js, xmlnode *packet) +{ + if (jabber_is_stanza(packet) && js->sm_state != SM_DISABLED) { + /* Counting stanzas even if there's no confirmation that SM is + enabled yet, so that we won't miss any. */ + + /* Add this stanza to the queue, unless the queue is full. */ + xmlnode *stanza; + xmlnode *req; + GQueue *queue = jabber_sm_accounts_queue_get(js); + if (g_queue_get_length(queue) < MAX_QUEUE_LENGTH) { + stanza = xmlnode_copy(packet); + g_queue_push_tail(queue, stanza); + if (g_queue_get_length(queue) == MAX_QUEUE_LENGTH) { + gchar *jid; + gchar *queue_is_full_message; + jid = jabber_id_get_bare_jid(js->user); + queue_is_full_message = + g_strdup_printf( + _("The queue for %s has reached its maximum length of %u."), + jid, MAX_QUEUE_LENGTH); + purple_debug_warning("XEP-0198", + "Stanza queue for %s is full (%u stanzas).\n", + jid, MAX_QUEUE_LENGTH); + g_free(jid); + purple_notify_formatted(js->gc, _("XMPP stream management"), + _("Stanza queue is full"), + _("No further messages will be queued"), + queue_is_full_message, + NULL, NULL); + g_free(queue_is_full_message); + } + } + + /* Count the stanza */ + js->sm_outbound_count++; + + /* Requesting acknowledgements with either SM_REQUESTED or + SM_ENABLED state as well, so that it would be harder to lose + stanzas. */ + req = xmlnode_new("r"); + xmlnode_set_namespace(req, NS_STREAM_MANAGEMENT); + jabber_send(js, req); + xmlnode_free(req); + } +} + +/* Counts inbound stanzas. */ +void +jabber_sm_inbound(JabberStream *js, xmlnode *packet) +{ + /* Count stanzas for XEP-0198, excluding stream management + packets. */ + if (jabber_is_stanza(packet)) { + js->sm_inbound_count++; + } +} diff --git a/libpurple/protocols/jabber/stream_management.h b/libpurple/protocols/jabber/stream_management.h new file mode 100644 index 0000000000..1ff630989b --- /dev/null +++ b/libpurple/protocols/jabber/stream_management.h @@ -0,0 +1,36 @@ +/** + * @file stream_management.h XEP-0198 + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + + +void jabber_sm_init(void); +void jabber_sm_uninit(void); + +void jabber_sm_enable(JabberStream *js); +void jabber_sm_process_packet(JabberStream *js, xmlnode *packet); + +void jabber_sm_ack_send(JabberStream *js); +void jabber_sm_ack_read(JabberStream *js, xmlnode *packet); + +void jabber_sm_outbound(JabberStream *js, xmlnode *packet); +void jabber_sm_inbound(JabberStream *js, xmlnode *packet); |