--- a/libpurple/protocols/jabber/Makefile.am Thu May 03 21:13:48 2018 -0500
+++ b/libpurple/protocols/jabber/Makefile.am Fri May 25 23:35:53 2018 +0000
@@ -80,6 +80,8 @@
--- a/libpurple/protocols/jabber/jabber.c Thu May 03 21:13:48 2018 -0500
+++ b/libpurple/protocols/jabber/jabber.c Fri May 25 23:35:53 2018 +0000
@@ -68,6 +68,7 @@
#include "adhoccommands.h"
+#include "stream_management.h" #include "jingle/jingle.h"
@@ -312,6 +313,13 @@
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) ) static void jabber_stream_handle_error(JabberStream *js, xmlnode *packet)
@@ -340,6 +348,8 @@
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 @@
/* TODO: Handle <failure/>, I guess? */
+ } else if (purple_strequal(xmlns, NS_STREAM_MANAGEMENT)) { + jabber_sm_process_packet(js, *packet); purple_debug_warning("jabber", "Unknown packet: %s\n", (*packet)->name);
@@ -583,6 +595,28 @@
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). */ +jabber_is_stanza(xmlnode *packet) { + g_return_val_if_fail(packet != NULL, FALSE); + g_return_val_if_fail(packet->name != NULL, FALSE); + xmlns = xmlnode_get_namespace(packet); + return ((purple_strequal(name, "message") + || purple_strequal(name, "iq") + || purple_strequal(name, "presence")) + || purple_strequal(xmlns, NS_XMPP_CLIENT) + || purple_strequal(xmlns, NS_XMPP_SERVER))); void jabber_send_signal_cb(PurpleConnection *pc, xmlnode **packet,
@@ -601,13 +635,13 @@
- 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);
+ jabber_sm_outbound(js, *packet); void jabber_send(JabberStream *js, xmlnode *packet)
@@ -1015,6 +1049,8 @@
if (purple_presence_is_idle(presence))
js->idle = purple_presence_get_idle_time(presence);
+ js->sm_state = SM_DISABLED; @@ -1597,8 +1633,10 @@
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);
purple_srv_cancel(js->srv_query_data);
@@ -3896,12 +3934,15 @@
/* reverse order of jabber_do_init */
--- a/libpurple/protocols/jabber/jabber.h Thu May 03 21:13:48 2018 -0500
+++ b/libpurple/protocols/jabber/jabber.h Fri May 25 23:35:53 2018 +0000
@@ -49,6 +49,8 @@
JABBER_CAP_ITEMS = 1 << 14,
JABBER_CAP_ROSTER_VERSIONING = 1 << 15,
+ JABBER_CAP_STREAM_MANAGEMENT = 1 << 16, JABBER_CAP_RETRIEVED = 1 << 31
@@ -96,6 +98,12 @@
+} JabberStreamManagementState; @@ -282,6 +290,12 @@
gchar *google_relay_host;
GList *google_relay_requests; /* the HTTP requests to get */
+ /* 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_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);
--- a/libpurple/protocols/jabber/namespaces.h Thu May 03 21:13:48 2018 -0500
+++ b/libpurple/protocols/jabber/namespaces.h Fri May 25 23:35:53 2018 +0000
@@ -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" #define NS_PING "urn:xmpp:ping"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/stream_management.c Fri May 25 23:35:53 2018 +0000
@@ -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 + * 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 "stream_management.h" +#define MAX_QUEUE_LENGTH 10000 +GHashTable *jabber_sm_accounts; +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. */ +jabber_sm_accounts_queue_get(JabberStream *js) + 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_hash_table_insert(jabber_sm_accounts, jid, queue); + jabber_sm_accounts = g_hash_table_new_full(g_str_hash, g_str_equal, free, + jabber_sm_accounts_queue_free); + g_hash_table_destroy(jabber_sm_accounts); +/* Processes incoming NS_STREAM_MANAGEMENT packets. */ +jabber_sm_process_packet(JabberStream *js, xmlnode *packet) { + 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); + } else if (purple_strequal(name, "r")) { + jabber_sm_ack_send(js); + } else if (purple_strequal(name, "a")) { + jabber_sm_ack_read(js, packet); + purple_debug_error("XEP-0198", "Unknown packet: %s\n", name); +/* Sends an acknowledgement. */ +jabber_sm_ack_send(JabberStream *js) + if (js->sm_state != SM_ENABLED) { + 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); +/* Reads acknowledgements, removes queued stanzas. */ +jabber_sm_ack_read(JabberStream *js, xmlnode *packet) + const char *ack_h = xmlnode_get_attrib(packet, "h"); + purple_debug_error("XEP-0198", + "The 'h' attribute is not defined for an answer."); + 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); + purple_debug_error("XEP-0198", "The queue is empty\n"); + 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 +jabber_sm_enable(JabberStream *js) + 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); + 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); + 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); +/* Tracks outbound stanzas, stores those into a queue, requests +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. */ + 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 *queue_is_full_message; + jid = jabber_id_get_bare_jid(js->user); + queue_is_full_message = + _("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); + purple_notify_formatted(js->gc, _("XMPP stream management"), + _("Stanza queue is full"), + _("No further messages will be queued"), + g_free(queue_is_full_message); + js->sm_outbound_count++; + /* Requesting acknowledgements with either SM_REQUESTED or + SM_ENABLED state as well, so that it would be harder to lose + req = xmlnode_new("r"); + xmlnode_set_namespace(req, NS_STREAM_MANAGEMENT); +/* Counts inbound stanzas. */ +jabber_sm_inbound(JabberStream *js, xmlnode *packet) + /* Count stanzas for XEP-0198, excluding stream management + if (jabber_is_stanza(packet)) { + js->sm_inbound_count++; --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/stream_management.h Fri May 25 23:35:53 2018 +0000
@@ -0,0 +1,36 @@
+ * @file stream_management.h XEP-0198 + * 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 + * 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);