Coverage Report

Created: 2025-08-28 06:24

/src/pidgin/libpurple/protocols/jabber/stream_management.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * purple - Jabber Protocol Plugin
3
 *
4
 * Purple is the legal property of its developers, whose names are too numerous
5
 * to list here.  Please refer to the COPYRIGHT file distributed with this
6
 * source distribution.
7
 *
8
 * This program is free software; you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation; either version 2 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21
 *
22
 */
23
24
#include "internal.h"
25
26
#include <glib.h>
27
#include "namespaces.h"
28
#include "xmlnode.h"
29
#include "jabber.h"
30
#include "debug.h"
31
#include "notify.h"
32
#include "stream_management.h"
33
34
0
#define MAX_QUEUE_LENGTH 10000
35
36
GHashTable *jabber_sm_accounts;
37
38
static void
39
jabber_sm_accounts_queue_free(gpointer q)
40
0
{
41
0
  g_queue_free_full(q, (GDestroyNotify)xmlnode_free);
42
0
}
43
44
/* Returns a queue for a JabberStream's account (based on JID),
45
   creates it if there's none. */
46
static GQueue *
47
jabber_sm_accounts_queue_get(JabberStream *js)
48
0
{
49
0
  GQueue *queue;
50
0
  gchar *jid = jabber_id_get_bare_jid(js->user);
51
0
  if (g_hash_table_contains(jabber_sm_accounts, jid) == TRUE) {
52
0
    queue = g_hash_table_lookup(jabber_sm_accounts, jid);
53
0
    g_free(jid);
54
0
  } else {
55
0
    queue = g_queue_new();
56
0
    g_hash_table_insert(jabber_sm_accounts, jid, queue);
57
0
  }
58
0
  return queue;
59
0
}
60
61
void
62
jabber_sm_init(void)
63
0
{
64
0
  jabber_sm_accounts = g_hash_table_new_full(g_str_hash, g_str_equal, free,
65
0
                                             jabber_sm_accounts_queue_free);
66
0
}
67
68
void
69
jabber_sm_uninit(void)
70
0
{
71
0
  g_hash_table_destroy(jabber_sm_accounts);
72
0
}
73
74
/* Processes incoming NS_STREAM_MANAGEMENT packets. */
75
void
76
0
jabber_sm_process_packet(JabberStream *js, xmlnode *packet) {
77
0
  gchar *jid;
78
0
  const char *name = packet->name;
79
0
  if (purple_strequal(name, "enabled")) {
80
0
    purple_debug_info("XEP-0198", "Stream management is enabled\n");
81
0
    js->sm_inbound_count = 0;
82
0
    js->sm_state = SM_ENABLED;
83
0
  } else if (purple_strequal(name, "failed")) {
84
0
    purple_debug_error("XEP-0198", "Failed to enable stream management\n");
85
0
    js->sm_state = SM_DISABLED;
86
0
    jid = jabber_id_get_bare_jid(js->user);
87
0
    g_hash_table_remove(jabber_sm_accounts, jid);
88
0
    g_free(jid);
89
0
  } else if (purple_strequal(name, "r")) {
90
0
    jabber_sm_ack_send(js);
91
0
  } else if (purple_strequal(name, "a")) {
92
0
    jabber_sm_ack_read(js, packet);
93
0
  } else {
94
0
    purple_debug_error("XEP-0198", "Unknown packet: %s\n", name);
95
0
  }
96
0
}
97
98
/* Sends an acknowledgement. */
99
void
100
jabber_sm_ack_send(JabberStream *js)
101
0
{
102
0
  xmlnode *ack;
103
0
  char *ack_h;
104
0
  if (js->sm_state != SM_ENABLED) {
105
0
    return;
106
0
  }
107
0
  ack = xmlnode_new("a");
108
0
  ack_h = g_strdup_printf("%u", js->sm_inbound_count);
109
0
  xmlnode_set_namespace(ack, NS_STREAM_MANAGEMENT);
110
0
  xmlnode_set_attrib(ack, "h", ack_h);
111
0
  jabber_send(js, ack);
112
0
  xmlnode_free(ack);
113
0
  g_free(ack_h);
114
0
}
115
116
/* Reads acknowledgements, removes queued stanzas. */
117
void
118
jabber_sm_ack_read(JabberStream *js, xmlnode *packet)
119
0
{
120
0
  guint32 i;
121
0
  guint32 h;
122
0
  GQueue *queue;
123
0
  xmlnode *stanza;
124
0
  const char *ack_h = xmlnode_get_attrib(packet, "h");
125
0
  if (ack_h == NULL) {
126
0
    purple_debug_error("XEP-0198",
127
0
                       "The 'h' attribute is not defined for an answer.");
128
0
    return;
129
0
  }
130
0
  h = strtoul(ack_h, NULL, 10);
131
132
  /* Remove stanzas from the queue */
133
0
  queue = jabber_sm_accounts_queue_get(js);
134
0
  for (i = js->sm_outbound_confirmed; i < h; i++) {
135
0
    stanza = g_queue_pop_head(queue);
136
0
    if (stanza == NULL) {
137
0
      purple_debug_error("XEP-0198", "The queue is empty\n");
138
0
      break;
139
0
    }
140
0
    xmlnode_free(stanza);
141
0
  }
142
143
0
  js->sm_outbound_confirmed = h;
144
0
  purple_debug_info("XEP-0198",
145
0
                    "Acknowledged %u out of %u outbound stanzas\n",
146
0
                    js->sm_outbound_confirmed, js->sm_outbound_count);
147
0
}
148
149
/* Asks a server to enable stream management, resends queued
150
   stanzas. */
151
void
152
jabber_sm_enable(JabberStream *js)
153
0
{
154
0
  xmlnode *enable;
155
0
  xmlnode *stanza;
156
0
  GQueue *queue;
157
0
  guint queue_len;
158
0
  guint i;
159
0
  js->server_caps |= JABBER_CAP_STREAM_MANAGEMENT;
160
0
  purple_debug_info("XEP-0198", "Enabling stream management\n");
161
0
  enable = xmlnode_new("enable");
162
0
  xmlnode_set_namespace(enable, NS_STREAM_MANAGEMENT);
163
0
  jabber_send(js, enable);
164
0
  xmlnode_free(enable);
165
0
  js->sm_outbound_count = 0;
166
0
  js->sm_outbound_confirmed = 0;
167
0
  js->sm_state = SM_REQUESTED;
168
169
  /* Resend unacknowledged stanzas from the queue. */
170
0
  queue = jabber_sm_accounts_queue_get(js);
171
0
  queue_len = g_queue_get_length(queue);
172
0
  if (queue_len > 0) {
173
0
    purple_debug_info("XEP-0198", "Resending %u stanzas\n", queue_len);
174
0
  }
175
0
  for (i = 0; i < queue_len; i++) {
176
0
    stanza = g_queue_pop_head(queue);
177
0
    jabber_send(js, stanza);
178
0
    xmlnode_free(stanza);
179
0
  }
180
0
}
181
182
/* Tracks outbound stanzas, stores those into a queue, requests
183
   acknowledgements. */
184
void
185
jabber_sm_outbound(JabberStream *js, xmlnode *packet)
186
0
{
187
0
  if (jabber_is_stanza(packet)
188
0
      && (js->sm_state == SM_REQUESTED || js->sm_state == SM_ENABLED)) {
189
    /* Counting stanzas even if there's no confirmation that SM is
190
       enabled yet, so that we won't miss any. */
191
192
    /* Add this stanza to the queue, unless the queue is full. */
193
0
    xmlnode *stanza;
194
0
    xmlnode *req;
195
0
    GQueue *queue = jabber_sm_accounts_queue_get(js);
196
0
    if (g_queue_get_length(queue) < MAX_QUEUE_LENGTH) {
197
0
      stanza = xmlnode_copy(packet);
198
0
      g_queue_push_tail(queue, stanza);
199
0
      if (g_queue_get_length(queue) == MAX_QUEUE_LENGTH) {
200
0
        gchar *jid;
201
0
        gchar *queue_is_full_message;
202
0
        jid = jabber_id_get_bare_jid(js->user);
203
0
        queue_is_full_message =
204
0
          g_strdup_printf(
205
0
            _("The queue for %s has reached its maximum length of %u."),
206
0
            jid, MAX_QUEUE_LENGTH);
207
0
        purple_debug_warning("XEP-0198",
208
0
                             "Stanza queue for %s is full (%u stanzas).\n",
209
0
                             jid, MAX_QUEUE_LENGTH);
210
0
        g_free(jid);
211
0
        purple_notify_formatted(js->gc, _("XMPP stream management"),
212
0
                                _("Stanza queue is full"),
213
0
                                _("No further messages will be queued"),
214
0
                                queue_is_full_message,
215
0
                                NULL, NULL);
216
0
        g_free(queue_is_full_message);
217
0
      }
218
0
    }
219
220
    /* Count the stanza */
221
0
    js->sm_outbound_count++;
222
223
    /* Requesting acknowledgements with either SM_REQUESTED or
224
       SM_ENABLED state as well, so that it would be harder to lose
225
       stanzas. */
226
0
    req = xmlnode_new("r");
227
0
    xmlnode_set_namespace(req, NS_STREAM_MANAGEMENT);
228
0
    jabber_send(js, req);
229
0
    xmlnode_free(req);
230
0
  }
231
0
}
232
233
/* Counts inbound stanzas. */
234
void
235
jabber_sm_inbound(JabberStream *js, xmlnode *packet)
236
0
{
237
  /* Count stanzas for XEP-0198, excluding stream management
238
     packets. */
239
0
  if (jabber_is_stanza(packet)) {
240
0
    js->sm_inbound_count++;
241
0
  }
242
0
}