/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 | } |