Skip to content

Commit 88a750f

Browse files
committed
Improve Stream Management
* Delay the notification of the library user that the connection was successful, until SM is reported by the server as enabled. * Clear the SM queue in case resumption failed. * Improve some debug statements & comments. Signed-off-by: Steffen Jaeckel <[email protected]>
1 parent bfd0872 commit 88a750f

File tree

5 files changed

+156
-85
lines changed

5 files changed

+156
-85
lines changed

src/auth.c

Lines changed: 111 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -897,7 +897,7 @@ static void _auth(xmpp_conn_t *conn)
897897
}
898898
}
899899

900-
static void _auth_success(xmpp_conn_t *conn)
900+
static void _stream_negotiation_success(xmpp_conn_t *conn)
901901
{
902902
tls_clear_password_cache(conn);
903903
conn->stream_negotiation_completed = 1;
@@ -962,6 +962,7 @@ static int _do_bind(xmpp_conn_t *conn, xmpp_stanza_t *bind)
962962
/* send bind request */
963963
iq = xmpp_iq_new(conn->ctx, "set", "_xmpp_bind1");
964964
if (!iq) {
965+
xmpp_stanza_release(bind);
965966
disconnect_mem_error(conn);
966967
return 0;
967968
}
@@ -1102,7 +1103,7 @@ static int
11021103
_handle_bind(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata)
11031104
{
11041105
const char *type;
1105-
xmpp_stanza_t *iq, *enable, *session, *binding, *jid_stanza;
1106+
xmpp_stanza_t *iq, *session, *binding, *jid_stanza, *enable = NULL;
11061107

11071108
UNUSED(userdata);
11081109

@@ -1125,6 +1126,23 @@ _handle_bind(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata)
11251126
}
11261127
}
11271128

1129+
/* send enable directly after the bind request */
1130+
if (conn->sm_state->sm_support && !conn->sm_disable) {
1131+
enable = xmpp_stanza_new(conn->ctx);
1132+
if (!enable) {
1133+
disconnect_mem_error(conn);
1134+
return 0;
1135+
}
1136+
xmpp_stanza_set_name(enable, "enable");
1137+
xmpp_stanza_set_ns(enable, XMPP_NS_SM);
1138+
if (!conn->sm_state->dont_request_resume)
1139+
xmpp_stanza_set_attribute(enable, "resume", "true");
1140+
handler_add(conn, _handle_sm, XMPP_NS_SM, NULL, NULL, NULL);
1141+
send_stanza(conn, enable, XMPP_QUEUE_SM_STROPHE);
1142+
conn->sm_state->sm_sent_nr = 0;
1143+
conn->sm_state->sm_enabled = 1;
1144+
}
1145+
11281146
/* establish a session if required */
11291147
if (conn->session_required) {
11301148
/* setup response handlers */
@@ -1155,22 +1173,12 @@ _handle_bind(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata)
11551173
send_stanza(conn, iq, XMPP_QUEUE_STROPHE);
11561174
}
11571175

1158-
if (conn->sm_state->sm_support && !conn->sm_disable) {
1159-
enable = xmpp_stanza_new(conn->ctx);
1160-
if (!enable) {
1161-
disconnect_mem_error(conn);
1162-
return 0;
1163-
}
1164-
xmpp_stanza_set_name(enable, "enable");
1165-
xmpp_stanza_set_ns(enable, XMPP_NS_SM);
1166-
if (!conn->sm_state->dont_request_resume)
1167-
xmpp_stanza_set_attribute(enable, "resume", "true");
1168-
handler_add(conn, _handle_sm, XMPP_NS_SM, NULL, NULL, NULL);
1169-
send_stanza(conn, enable, XMPP_QUEUE_SM_STROPHE);
1170-
}
1171-
1172-
if (!conn->session_required) {
1173-
_auth_success(conn);
1176+
/* if there's no xmpp session required and we didn't try to enable
1177+
* stream-management, we're done here and the stream-negotiation was
1178+
* successful
1179+
*/
1180+
if (!conn->session_required && !enable) {
1181+
_stream_negotiation_success(conn);
11741182
}
11751183
} else {
11761184
strophe_error(conn->ctx, "xmpp", "Server sent malformed bind reply.");
@@ -1207,7 +1215,7 @@ _handle_session(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata)
12071215
} else if (type && strcmp(type, "result") == 0) {
12081216
strophe_debug(conn->ctx, "xmpp", "Session establishment successful.");
12091217

1210-
_auth_success(conn);
1218+
_stream_negotiation_success(conn);
12111219
} else {
12121220
strophe_error(conn->ctx, "xmpp",
12131221
"Server sent malformed session reply.");
@@ -1238,23 +1246,59 @@ static int _handle_missing_legacy(xmpp_conn_t *conn, void *userdata)
12381246
return 0;
12391247
}
12401248

1249+
static int _get_h_attribute(xmpp_stanza_t *stanza, unsigned long *ul_h)
1250+
{
1251+
const char *h = xmpp_stanza_get_attribute(stanza, "h");
1252+
if (!h || string_to_ul(h, ul_h)) {
1253+
strophe_error(
1254+
stanza->ctx, "xmpp",
1255+
"SM error: failed parsing 'h', \"%s\" got converted to %llu.",
1256+
STR_MAYBE_NULL(h), *ul_h);
1257+
return -1;
1258+
}
1259+
return 0;
1260+
}
1261+
1262+
static void _sm_queue_cleanup(xmpp_conn_t *conn, unsigned long ul_h)
1263+
{
1264+
xmpp_send_queue_t *e;
1265+
while ((e = peek_queue_front(&conn->sm_state->sm_queue))) {
1266+
if (e->sm_h > ul_h)
1267+
break;
1268+
e = pop_queue_front(&conn->sm_state->sm_queue);
1269+
strophe_free(conn->ctx, queue_element_free(conn->ctx, e));
1270+
}
1271+
}
1272+
1273+
static void _sm_queue_resend(xmpp_conn_t *conn)
1274+
{
1275+
xmpp_send_queue_t *e;
1276+
while ((e = pop_queue_front(&conn->sm_state->sm_queue))) {
1277+
/* Re-send what was already sent out and is still in the
1278+
* SM queue (i.e. it hasn't been ACK'ed by the server)
1279+
*/
1280+
strophe_debug_verbose(2, conn->ctx, "conn", "SM_Q_RESEND: %p, h=%lu", e,
1281+
e->sm_h);
1282+
send_raw(conn, e->data, e->len, e->owner, NULL);
1283+
strophe_free(conn->ctx, queue_element_free(conn->ctx, e));
1284+
}
1285+
}
1286+
12411287
static int _handle_sm(xmpp_conn_t *const conn,
12421288
xmpp_stanza_t *const stanza,
12431289
void *const userdata)
12441290
{
1245-
xmpp_stanza_t *failed_cause;
1246-
const char *name, *id, *previd, *resume, *h, *cause;
1247-
xmpp_send_queue_t *e;
1291+
xmpp_stanza_t *failed_cause, *bind = NULL;
1292+
const char *name, *id, *previd, *resume, *cause;
12481293
unsigned long ul_h = 0;
12491294

12501295
UNUSED(userdata);
12511296

12521297
name = xmpp_stanza_get_name(stanza);
12531298
if (!name)
1254-
goto LBL_ERR;
1299+
goto err_sm;
12551300

12561301
if (strcmp(name, "enabled") == 0) {
1257-
conn->sm_state->sm_enabled = 1;
12581302
conn->sm_state->sm_handled_nr = 0;
12591303
resume = xmpp_stanza_get_attribute(stanza, "resume");
12601304
if (resume && (strcasecmp(resume, "true") || strcmp(resume, "1"))) {
@@ -1264,28 +1308,29 @@ static int _handle_sm(xmpp_conn_t *const conn,
12641308
"SM error: server said it can resume, but "
12651309
"didn't provide an ID.");
12661310
name = NULL;
1267-
goto LBL_ERR;
1311+
goto err_sm;
12681312
}
12691313
conn->sm_state->can_resume = 1;
12701314
conn->sm_state->id = strophe_strdup(conn->ctx, id);
12711315
}
1316+
/* We maybe have stuff in the SM queue if we tried to resume, but the
1317+
* server doesn't remember all details of our session, but the `h` was
1318+
* still available.
1319+
*/
1320+
_sm_queue_resend(conn);
1321+
_stream_negotiation_success(conn);
12721322
} else if (strcmp(name, "resumed") == 0) {
12731323
previd = xmpp_stanza_get_attribute(stanza, "previd");
12741324
if (!previd || strcmp(previd, conn->sm_state->previd)) {
12751325
strophe_error(conn->ctx, "xmpp",
12761326
"SM error: previd didn't match, ours is \"%s\".",
12771327
conn->sm_state->previd);
12781328
name = NULL;
1279-
goto LBL_ERR;
1329+
goto err_sm;
12801330
}
1281-
h = xmpp_stanza_get_attribute(stanza, "h");
1282-
if (!h || string_to_ul(h, &ul_h)) {
1283-
strophe_error(conn->ctx, "xmpp",
1284-
"SM error: failed parsing 'h', it got converted "
1285-
"to %llu.",
1286-
ul_h);
1331+
if (_get_h_attribute(stanza, &ul_h)) {
12871332
name = NULL;
1288-
goto LBL_ERR;
1333+
goto err_sm;
12891334
}
12901335
conn->sm_state->sm_enabled = 1;
12911336
conn->sm_state->id = conn->sm_state->previd;
@@ -1296,54 +1341,53 @@ static int _handle_sm(xmpp_conn_t *const conn,
12961341
conn->sm_state->sm_sent_nr = conn->sm_state->sm_queue.head->sm_h;
12971342
else
12981343
conn->sm_state->sm_sent_nr = ul_h;
1299-
while ((e = pop_queue_front(&conn->sm_state->sm_queue))) {
1300-
if (e->sm_h >= ul_h) {
1301-
/* Re-send what was already sent out and is still in the
1302-
* SM queue (i.e. it hasn't been ACK'ed by the server)
1303-
*/
1304-
send_raw(conn, e->data, e->len, e->owner, NULL);
1305-
}
1306-
strophe_free(conn->ctx, queue_element_free(conn->ctx, e));
1307-
}
1344+
_sm_queue_cleanup(conn, ul_h);
1345+
_sm_queue_resend(conn);
13081346
strophe_debug(conn->ctx, "xmpp", "Session resumed successfully.");
1309-
_auth_success(conn);
1347+
_stream_negotiation_success(conn);
13101348
} else if (strcmp(name, "failed") == 0) {
13111349
name = NULL;
1350+
conn->sm_state->sm_enabled = 0;
13121351

13131352
failed_cause =
13141353
xmpp_stanza_get_child_by_ns(stanza, XMPP_NS_STANZAS_IETF);
13151354
if (!failed_cause)
1316-
goto LBL_ERR;
1355+
goto err_sm;
13171356

13181357
cause = xmpp_stanza_get_name(failed_cause);
13191358
if (!cause)
1320-
goto LBL_ERR;
1359+
goto err_sm;
13211360

1322-
if (!strcmp(cause, "item-not-found") ||
1323-
!strcmp(cause, "feature-not-implemented")) {
1361+
if (!strcmp(cause, "item-not-found")) {
13241362
if (conn->sm_state->resume) {
1325-
conn->sm_state->resume = 0;
1326-
conn->sm_state->can_resume = 0;
1327-
/* remember that the server reports having support
1328-
* for resumption, but actually it doesn't ...
1363+
/* It's no error if there's no `h` attribute included
1364+
* but if there is, it gives a hint at what the server
1365+
* already received.
13291366
*/
1330-
conn->sm_state->dont_request_resume =
1331-
!strcmp(cause, "feature-not-implemented");
1332-
strophe_free(conn->ctx, conn->sm_state->previd);
1333-
conn->sm_state->previd = NULL;
1334-
strophe_free(conn->ctx, conn->sm_state->bound_jid);
1335-
conn->sm_state->bound_jid = NULL;
1336-
_do_bind(conn, conn->sm_state->bind);
1337-
conn->sm_state->bind = NULL;
1367+
if (!_get_h_attribute(stanza, &ul_h)) {
1368+
/* In cases there's no `h` included, drop all elements. */
1369+
ul_h = (unsigned long)-1;
1370+
}
1371+
_sm_queue_cleanup(conn, ul_h);
13381372
}
1373+
} else if (!strcmp(cause, "feature-not-implemented")) {
1374+
conn->sm_state->resume = 0;
1375+
conn->sm_state->can_resume = 0;
1376+
/* remember that the server reports having support
1377+
* for resumption, but actually it doesn't ...
1378+
*/
1379+
conn->sm_state->dont_request_resume = 1;
13391380
}
1340-
conn->sm_state->sm_handled_nr = 0;
1381+
bind = conn->sm_state->bind;
1382+
conn->sm_state->bind = NULL;
1383+
reset_sm_state(conn->sm_state);
1384+
_do_bind(conn, bind);
13411385
} else {
13421386
/* unknown stanza received */
13431387
name = NULL;
13441388
}
13451389

1346-
LBL_ERR:
1390+
err_sm:
13471391
if (!name) {
13481392
char *err = "Couldn't convert stanza to text!";
13491393
char *buf;
@@ -1362,7 +1406,8 @@ static int _handle_sm(xmpp_conn_t *const conn,
13621406
buf);
13631407
if (buf != err)
13641408
strophe_free(conn->ctx, buf);
1365-
conn->sm_state->sm_enabled = 0;
1409+
/* Don't disable for <failure> cases, they're no hard errors */
1410+
conn->sm_state->sm_enabled = bind != NULL;
13661411
}
13671412
return 0;
13681413
}
@@ -1395,7 +1440,7 @@ _handle_legacy(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata)
13951440
/* auth succeeded */
13961441
strophe_debug(conn->ctx, "xmpp", "Legacy auth succeeded.");
13971442

1398-
_auth_success(conn);
1443+
_stream_negotiation_success(conn);
13991444
} else {
14001445
strophe_error(conn->ctx, "xmpp",
14011446
"Server sent us a legacy authentication "
@@ -1585,7 +1630,7 @@ int _handle_component_hs_response(xmpp_conn_t *conn,
15851630
xmpp_disconnect(conn);
15861631
return XMPP_EINT;
15871632
} else {
1588-
_auth_success(conn);
1633+
_stream_negotiation_success(conn);
15891634
}
15901635

15911636
/* We don't need this handler anymore, return 0 so it can be deleted
@@ -1607,8 +1652,8 @@ int _handle_missing_handshake(xmpp_conn_t *conn, void *userdata)
16071652
void auth_handle_open_raw(xmpp_conn_t *conn)
16081653
{
16091654
handler_reset_timed(conn, 0);
1610-
/* user handlers are not called before authentication is completed. */
1611-
_auth_success(conn);
1655+
/* user handlers are not called before stream negotiation has completed. */
1656+
_stream_negotiation_success(conn);
16121657
}
16131658

16141659
void auth_handle_open_stub(xmpp_conn_t *conn)

src/common.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ void strophe_log_internal(const xmpp_ctx_t *ctx,
127127
const char *fmt,
128128
va_list ap);
129129

130+
#if defined(__OpenBSD__)
131+
#define STR_MAYBE_NULL(p) (p) ? (p) : "(null)"
132+
#else
133+
#define STR_MAYBE_NULL(p) (p)
134+
#endif
135+
130136
/** connection **/
131137

132138
/* opaque connection object */
@@ -193,10 +199,10 @@ struct _xmpp_sm_t {
193199
int sm_support;
194200
int sm_enabled;
195201
int can_resume, resume, dont_request_resume;
202+
xmpp_queue_t sm_queue;
196203
int r_sent;
197204
uint32_t sm_handled_nr;
198205
uint32_t sm_sent_nr;
199-
xmpp_queue_t sm_queue;
200206
char *id, *previd, *bound_jid;
201207
xmpp_stanza_t *bind;
202208
};
@@ -276,7 +282,8 @@ struct _xmpp_conn_t {
276282
/* stream open handler */
277283
xmpp_open_handler open_handler;
278284

279-
/* user handlers only get called after authentication */
285+
/* user handlers only get called after the stream negotiation has completed
286+
*/
280287
int stream_negotiation_completed;
281288

282289
/* connection events handler */
@@ -341,6 +348,7 @@ void handler_add(xmpp_conn_t *conn,
341348
void handler_system_delete_all(xmpp_conn_t *conn);
342349

343350
/* utility functions */
351+
void reset_sm_state(xmpp_sm_state_t *sm_state);
344352
void disconnect_mem_error(xmpp_conn_t *conn);
345353

346354
/* auth functions */
@@ -351,6 +359,7 @@ void auth_handle_open_stub(xmpp_conn_t *conn);
351359

352360
/* queue functions */
353361
void add_queue_back(xmpp_queue_t *queue, xmpp_send_queue_t *item);
362+
xmpp_send_queue_t *peek_queue_front(xmpp_queue_t *queue);
354363
xmpp_send_queue_t *pop_queue_front(xmpp_queue_t *queue);
355364
char *queue_element_free(xmpp_ctx_t *ctx, xmpp_send_queue_t *e);
356365

0 commit comments

Comments
 (0)