From c7d410f38b7e51db71eff065c00d3b9d9c895a53 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Fri, 26 Jan 2024 15:57:35 +0100 Subject: [PATCH] First version of XEP-0138 support Signed-off-by: Steffen Jaeckel --- Makefile.am | 7 +- configure.ac | 16 ++++ examples/bot.c | 9 +- examples/complex.c | 7 ++ src/auth.c | 166 +++++++++++++++++++++++++-------- src/common.h | 8 ++ src/conn.c | 36 ++++++-- src/event.c | 222 +++++++++++++++++++++++++++++++++++++++++---- strophe.h | 13 +++ 9 files changed, 419 insertions(+), 65 deletions(-) diff --git a/Makefile.am b/Makefile.am index 9849815..dedd0f6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -10,6 +10,9 @@ COVERAGE_POST=@COVERAGE_POST@ PARSER_CFLAGS=@PARSER_CFLAGS@ PARSER_LIBS=@PARSER_LIBS@ +ZLIB_CFLAGS=@ZLIB_CFLAGS@ +ZLIB_LIBS=@ZLIB_LIBS@ + if TLS_WITH_GNUTLS SSL_CFLAGS = @gnutls_CFLAGS@ SSL_LIBS = @gnutls_LIBS@ @@ -32,8 +35,8 @@ STROPHE_LIBS = $(COVERAGE_PRE) libstrophe.la $(COVERAGE_POST) $(COVERAGE_LDFLAGS ## Main build targets lib_LTLIBRARIES = libstrophe.la -libstrophe_la_CFLAGS = $(SSL_CFLAGS) $(STROPHE_FLAGS) $(PARSER_CFLAGS) $(RESOLV_CFLAGS) $(COVERAGE_CFLAGS) -libstrophe_la_LDFLAGS = $(SSL_LIBS) $(PARSER_LIBS) $(RESOLV_LIBS) $(MINGW_LIBS) -no-undefined +libstrophe_la_CFLAGS = $(SSL_CFLAGS) $(STROPHE_FLAGS) $(PARSER_CFLAGS) $(RESOLV_CFLAGS) $(COVERAGE_CFLAGS) $(ZLIB_CFLAGS) +libstrophe_la_LDFLAGS = $(SSL_LIBS) $(PARSER_LIBS) $(RESOLV_LIBS) $(MINGW_LIBS) $(ZLIB_LIBS) -no-undefined # Export only public API libstrophe_la_LDFLAGS += -export-symbols-regex '^xmpp_' -version-info @VERSION_INFO@ diff --git a/configure.ac b/configure.ac index 0bfe6e2..0973b49 100644 --- a/configure.ac +++ b/configure.ac @@ -51,6 +51,8 @@ AC_ARG_ENABLE([cares], [AS_HELP_STRING([--enable-cares], [use c-ares for DNS resolution])]) AC_ARG_ENABLE([getrandom], [AS_HELP_STRING([--disable-getrandom], [disable usage of the getrandom() system call])]) +AC_ARG_ENABLE([zlib], + [AS_HELP_STRING([--disable-zlib], [disable compression support])]) AC_ARG_ENABLE([fuzzing], [AS_HELP_STRING([--enable-fuzzing], [turn on fuzzing test])], @@ -247,6 +249,18 @@ fi fi +if test "x$enable_zlib" != xno; then + PKG_CHECK_MODULES([zlib], [zlib >= 1.2.0], + [ + PC_REQUIRES="libzlib ${PC_REQUIRES}" + ZLIB_CFLAGS=$zlib_CFLAGS + ZLIB_LIBS=$zlib_LIBS + AC_DEFINE([HAVE_ZLIB]) + ], + [AC_MSG_ERROR([zlib not found])]) + # TODO: if pkg-config doesn't find, check the library manually +fi + m4_ifdef([PKG_INSTALLDIR], [PKG_INSTALLDIR], [AC_ARG_WITH([pkgconfigdir], [AS_HELP_STRING([--with-pkgconfigdir], @@ -290,5 +304,7 @@ AC_SUBST(PARSER_LIBS) AC_SUBST(RESOLV_CFLAGS) AC_SUBST(RESOLV_LIBS) AC_SUBST(WARNING_FLAGS) +AC_SUBST(ZLIB_CFLAGS) +AC_SUBST(ZLIB_LIBS) AC_CONFIG_FILES([Makefile libstrophe.pc]) AC_OUTPUT diff --git a/examples/bot.c b/examples/bot.c index 976becd..bf10a02 100644 --- a/examples/bot.c +++ b/examples/bot.c @@ -214,7 +214,10 @@ static void usage(int exit_code) " --legacy-ssl Use old style SSL.\n" " --legacy-auth Allow legacy authentication.\n" "Note: --disable-tls conflicts with --mandatory-tls or " - "--legacy-ssl\n"); + "--legacy-ssl\n" + " --zlib Enable compression via zlib.\n" + " --dont-flush When using zlib, don't flush after " + "compression.\n"); exit(exit_code); } @@ -244,6 +247,10 @@ int main(int argc, char **argv) flags |= XMPP_CONN_FLAG_LEGACY_SSL; else if (strcmp(argv[i], "--legacy-auth") == 0) flags |= XMPP_CONN_FLAG_LEGACY_AUTH; + else if (strcmp(argv[i], "--zlib") == 0) + flags |= XMPP_CONN_FLAG_ENABLE_COMPRESSION; + else if (strcmp(argv[i], "--dont-flush") == 0) + flags |= XMPP_CONN_FLAG_COMPRESSION_DONT_FLUSH; else if ((strcmp(argv[i], "--jid") == 0) && (++i < argc)) jid = argv[i]; else if ((strcmp(argv[i], "--pass") == 0) && (++i < argc)) diff --git a/examples/complex.c b/examples/complex.c index bfcdfd6..393db32 100644 --- a/examples/complex.c +++ b/examples/complex.c @@ -239,6 +239,9 @@ static void usage(int exit_code) " --enable-certfail Enable certfail handler.\n" " --legacy-ssl Use old style SSL.\n" " --legacy-auth Allow legacy authentication.\n" + " --zlib Enable compression via zlib.\n" + " --dont-flush When using zlib, don't flush after " + "compression.\n" " --verbose Increase the verbosity level.\n" " --tcp-keepalive Configure TCP keepalive.\n\n" "Note: --disable-tls conflicts with --mandatory-tls or " @@ -273,6 +276,10 @@ int main(int argc, char **argv) flags |= XMPP_CONN_FLAG_LEGACY_SSL; else if (strcmp(argv[i], "--legacy-auth") == 0) flags |= XMPP_CONN_FLAG_LEGACY_AUTH; + else if (strcmp(argv[i], "--zlib") == 0) + flags |= XMPP_CONN_FLAG_ENABLE_COMPRESSION; + else if (strcmp(argv[i], "--dont-flush") == 0) + flags |= XMPP_CONN_FLAG_COMPRESSION_DONT_FLUSH; else if (strcmp(argv[i], "--verbose") == 0) verbosity++; else if (strcmp(argv[i], "--tcp-keepalive") == 0) diff --git a/src/auth.c b/src/auth.c index cebbf3d..6dfc4d9 100644 --- a/src/auth.c +++ b/src/auth.c @@ -62,6 +62,7 @@ static void _auth(xmpp_conn_t *conn); static void _auth_legacy(xmpp_conn_t *conn); +static void _handle_open_compress(xmpp_conn_t *conn); static void _handle_open_sasl(xmpp_conn_t *conn); static void _handle_open_tls(xmpp_conn_t *conn); @@ -72,6 +73,9 @@ static int _handle_component_hs_response(xmpp_conn_t *conn, static int _handle_features_sasl(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata); +static int _handle_features_compress(xmpp_conn_t *conn, + xmpp_stanza_t *stanza, + void *userdata); static int _handle_sasl_result(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata); static int _handle_digestmd5_challenge(xmpp_conn_t *conn, @@ -207,10 +211,61 @@ static int _handle_missing_features(xmpp_conn_t *conn, void *userdata) return 0; } +typedef void (*text_handler)(xmpp_conn_t *conn, const char *text); +static void _foreach_child(xmpp_conn_t *conn, + xmpp_stanza_t *parent, + const char *name, + text_handler hndl) +{ + xmpp_stanza_t *children; + for (children = xmpp_stanza_get_children(parent); children; + children = xmpp_stanza_get_next(children)) { + const char *child_name = xmpp_stanza_get_name(children); + if (child_name && strcmp(child_name, name) == 0) { + char *text = xmpp_stanza_get_text(children); + if (text == NULL) + continue; + + hndl(conn, text); + + strophe_free(conn->ctx, text); + } + } +} + +static void _handle_sasl_children(xmpp_conn_t *conn, const char *text) +{ + if (strcasecmp(text, "PLAIN") == 0) { + conn->sasl_support |= SASL_MASK_PLAIN; + } else if (strcasecmp(text, "EXTERNAL") == 0 && + (conn->tls_client_cert || conn->tls_client_key)) { + conn->sasl_support |= SASL_MASK_EXTERNAL; + } else if (strcasecmp(text, "DIGEST-MD5") == 0) { + conn->sasl_support |= SASL_MASK_DIGESTMD5; + } else if (strcasecmp(text, "ANONYMOUS") == 0) { + conn->sasl_support |= SASL_MASK_ANONYMOUS; + } else { + size_t n; + for (n = 0; n < scram_algs_num; ++n) { + if (strcasecmp(text, scram_algs[n]->scram_name) == 0) { + conn->sasl_support |= scram_algs[n]->mask; + break; + } + } + } +} + +static void _handle_compression_children(xmpp_conn_t *conn, const char *text) +{ + if (strcasecmp(text, "zlib") == 0) { + conn->compression_supported = 1; + } +} + static int _handle_features(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) { - xmpp_stanza_t *child, *mech; + xmpp_stanza_t *child, *children; const char *ns; char *text; @@ -222,10 +277,9 @@ _handle_features(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) /* check for TLS */ if (!conn->secured) { if (!conn->tls_disabled) { - child = xmpp_stanza_get_child_by_name(stanza, "starttls"); - if (child) { - ns = xmpp_stanza_get_ns(child); - conn->tls_support = ns != NULL && strcmp(ns, XMPP_NS_TLS) == 0; + if (xmpp_stanza_get_child_by_name_and_ns(stanza, "starttls", + XMPP_NS_TLS)) { + conn->tls_support = 1; } } else { conn->tls_support = 0; @@ -233,45 +287,23 @@ _handle_features(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) } /* check for SASL */ - child = xmpp_stanza_get_child_by_name(stanza, "mechanisms"); - ns = child ? xmpp_stanza_get_ns(child) : NULL; - if (child && ns && strcmp(ns, XMPP_NS_SASL) == 0) { - for (mech = xmpp_stanza_get_children(child); mech; - mech = xmpp_stanza_get_next(mech)) { - if (xmpp_stanza_get_name(mech) && - strcmp(xmpp_stanza_get_name(mech), "mechanism") == 0) { - text = xmpp_stanza_get_text(mech); - if (text == NULL) - continue; - - if (strcasecmp(text, "PLAIN") == 0) { - conn->sasl_support |= SASL_MASK_PLAIN; - } else if (strcasecmp(text, "EXTERNAL") == 0 && - (conn->tls_client_cert || conn->tls_client_key)) { - conn->sasl_support |= SASL_MASK_EXTERNAL; - } else if (strcasecmp(text, "DIGEST-MD5") == 0) { - conn->sasl_support |= SASL_MASK_DIGESTMD5; - } else if (strcasecmp(text, "ANONYMOUS") == 0) { - conn->sasl_support |= SASL_MASK_ANONYMOUS; - } else { - size_t n; - for (n = 0; n < scram_algs_num; ++n) { - if (strcasecmp(text, scram_algs[n]->scram_name) == 0) { - conn->sasl_support |= scram_algs[n]->mask; - break; - } - } - } - - strophe_free(conn->ctx, text); - } - } + child = xmpp_stanza_get_child_by_name_and_ns(stanza, "mechanisms", + XMPP_NS_SASL); + if (child) { + _foreach_child(conn, child, "mechanism", _handle_sasl_children); } /* Disable PLAIN when other secure mechanisms are supported */ if (conn->sasl_support & ~(SASL_MASK_PLAIN | SASL_MASK_ANONYMOUS)) conn->sasl_support &= ~SASL_MASK_PLAIN; + /* check for compression */ + child = xmpp_stanza_get_child_by_name_and_ns(stanza, "compression", + XMPP_NS_COMPRESSION); + if (conn->compression_allowed && child) { + _foreach_child(conn, child, "method", _handle_compression_children); + } + _auth(conn); return 0; @@ -339,7 +371,9 @@ _handle_sasl_result(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) (char *)userdata); /* reset parser */ - conn_prepare_reset(conn, _handle_open_sasl); + conn_prepare_reset(conn, conn->compression_allowed + ? _handle_open_compress + : _handle_open_sasl); /* send stream tag */ conn_open_stream(conn); @@ -950,6 +984,17 @@ static void _handle_open_sasl(xmpp_conn_t *conn) NULL); } +/* called when stream:stream tag received after compression has been enabled */ +static void _handle_open_compress(xmpp_conn_t *conn) +{ + strophe_debug(conn->ctx, "xmpp", "Reopened stream successfully."); + + /* setup stream:features handlers */ + handler_add(conn, _handle_features_compress, XMPP_NS_STREAMS, "features", + NULL, NULL); + handler_add_timed(conn, _handle_missing_features, FEATURES_TIMEOUT, NULL); +} + static int _do_bind(xmpp_conn_t *conn, xmpp_stanza_t *bind) { xmpp_stanza_t *iq, *res, *text; @@ -1007,6 +1052,49 @@ static int _do_bind(xmpp_conn_t *conn, xmpp_stanza_t *bind) return 0; } +static int _handle_compress_result(xmpp_conn_t *const conn, + xmpp_stanza_t *const stanza, + void *const userdata) +{ + const char *name = xmpp_stanza_get_name(stanza); + + if (!name) + return 0; + if (strcmp(name, "compressed") == 0) { + /* Stream compression enabled, we need to restart the stream */ + strophe_debug(conn->ctx, "xmpp", "Stream compression enabled"); + + /* reset parser */ + conn_prepare_reset(conn, _handle_open_sasl); + + /* make compression effective */ + conn->compress = 1; + + /* send stream tag */ + conn_open_stream(conn); + } + return 0; +} + +static int _handle_features_compress(xmpp_conn_t *conn, + xmpp_stanza_t *stanza, + void *userdata) +{ + const char *compress = "zlib"; + + UNUSED(userdata); + + /* remove missing features handler */ + xmpp_timed_handler_delete(conn, _handle_missing_features); + + send_raw(conn, compress, strlen(compress), XMPP_QUEUE_STROPHE, NULL); + handler_add(conn, _handle_compress_result, XMPP_NS_COMPRESSION, NULL, NULL, + NULL); + + return 0; +} + static int _handle_features_sasl(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) { diff --git a/src/common.h b/src/common.h index b502859..b7c05f7 100644 --- a/src/common.h +++ b/src/common.h @@ -19,6 +19,7 @@ #include #include +#include #include "strophe.h" #include "ostypes.h" @@ -255,6 +256,13 @@ struct _xmpp_conn_t { int sm_disable; xmpp_sm_state_t *sm_state; + int compression_allowed, compression_supported; + int compress, compression_dont_flush; + struct zlib_compression { + void *buffer, *buffer_end; + z_stream stream; + } compression, decompression; + char *lang; char *domain; char *jid; diff --git a/src/conn.c b/src/conn.c index 95e7f0c..d2d46c2 100644 --- a/src/conn.c +++ b/src/conn.c @@ -1106,12 +1106,15 @@ long xmpp_conn_get_flags(const xmpp_conn_t *conn) { long flags; - flags = XMPP_CONN_FLAG_DISABLE_TLS * conn->tls_disabled | - XMPP_CONN_FLAG_MANDATORY_TLS * conn->tls_mandatory | - XMPP_CONN_FLAG_LEGACY_SSL * conn->tls_legacy_ssl | - XMPP_CONN_FLAG_TRUST_TLS * conn->tls_trust | - XMPP_CONN_FLAG_DISABLE_SM * conn->sm_disable | - XMPP_CONN_FLAG_LEGACY_AUTH * conn->auth_legacy_enabled; + flags = + XMPP_CONN_FLAG_DISABLE_TLS * conn->tls_disabled | + XMPP_CONN_FLAG_MANDATORY_TLS * conn->tls_mandatory | + XMPP_CONN_FLAG_LEGACY_SSL * conn->tls_legacy_ssl | + XMPP_CONN_FLAG_TRUST_TLS * conn->tls_trust | + XMPP_CONN_FLAG_DISABLE_SM * conn->sm_disable | + XMPP_CONN_FLAG_ENABLE_COMPRESSION * conn->compression_allowed | + XMPP_CONN_FLAG_COMPRESSION_DONT_FLUSH * conn->compression_dont_flush | + XMPP_CONN_FLAG_LEGACY_AUTH * conn->auth_legacy_enabled; return flags; } @@ -1160,6 +1163,19 @@ int xmpp_conn_set_flags(xmpp_conn_t *conn, long flags) conn->tls_trust = (flags & XMPP_CONN_FLAG_TRUST_TLS) ? 1 : 0; conn->auth_legacy_enabled = (flags & XMPP_CONN_FLAG_LEGACY_AUTH) ? 1 : 0; conn->sm_disable = (flags & XMPP_CONN_FLAG_DISABLE_SM) ? 1 : 0; + conn->compression_allowed = + (flags & XMPP_CONN_FLAG_ENABLE_COMPRESSION) ? 1 : 0; + conn->compression_dont_flush = + (flags & XMPP_CONN_FLAG_COMPRESSION_DONT_FLUSH) ? 1 : 0; + flags &= ~(XMPP_CONN_FLAG_DISABLE_TLS | XMPP_CONN_FLAG_MANDATORY_TLS | + XMPP_CONN_FLAG_LEGACY_SSL | XMPP_CONN_FLAG_TRUST_TLS | + XMPP_CONN_FLAG_LEGACY_AUTH | XMPP_CONN_FLAG_DISABLE_SM | + XMPP_CONN_FLAG_ENABLE_COMPRESSION | + XMPP_CONN_FLAG_COMPRESSION_DONT_FLUSH); + if (flags) { + strophe_error(conn->ctx, "conn", "Flags 0x%04lx unknown", flags); + return XMPP_EINVOP; + } return 0; } @@ -1752,6 +1768,14 @@ static void _conn_reset(xmpp_conn_t *conn) return; } + if (conn->compression.buffer) { + deflateEnd(&conn->compression.stream); + strophe_free_and_null(ctx, conn->compression.buffer); + } + if (conn->decompression.buffer) { + inflateEnd(&conn->decompression.stream); + strophe_free_and_null(ctx, conn->decompression.buffer); + } /* free queued */ sq = conn->send_queue_head; while (sq) { diff --git a/src/event.c b/src/event.c index 6543263..3b191cc 100644 --- a/src/event.c +++ b/src/event.c @@ -75,6 +75,202 @@ static int _connect_next(xmpp_conn_t *conn) return 0; } +static int +_conn_write_to_network(xmpp_conn_t *conn, const void *buff, size_t len) +{ + int ret; + if (conn->tls) { + ret = tls_write(conn->tls, buff, len); + if (ret < 0 && !tls_is_recoverable(tls_error(conn->tls))) + conn->error = tls_error(conn->tls); + } else { + ret = sock_write(conn->sock, buff, len); + if (ret < 0 && !sock_is_recoverable(sock_error())) + conn->error = sock_error(); + } + return ret; +} + +static int _try_compressed_write_to_network(xmpp_conn_t *conn, int force) +{ + int ret = 0; + size_t len = + conn->compression.stream.next_out - (Bytef *)conn->compression.buffer; + int buffer_full = + conn->compression.stream.next_out == conn->compression.buffer_end; + if ((buffer_full || force) && len) { + ret = _conn_write_to_network(conn, conn->compression.buffer, len); + if (ret < 0) + return ret; + // print_hex(xmpp_base64_encode(conn->ctx, + // conn->compression.buffer, len), + // conn->compression.buffer, len); + char *b = xmpp_base64_encode(conn->ctx, conn->compression.buffer, len); + printf("Sent: %s\n", b); + xmpp_free(conn->ctx, b); + + conn->compression.stream.next_out = conn->compression.buffer; + conn->compression.stream.avail_out = STROPHE_MESSAGE_BUFFER_SIZE; + } + return ret; +} + +static int _conn_compress(xmpp_conn_t *conn, void *buff, size_t len, int flush) +{ + int ret; + void *buff_end = buff + len; + conn->compression.stream.next_in = buff; + conn->compression.stream.avail_in = len; + do { + ret = _try_compressed_write_to_network(conn, 0); + if (ret < 0) { + return ret; + } + + ret = deflate(&conn->compression.stream, flush); + if (ret == Z_STREAM_END) { + break; + } + if (flush && ret == Z_BUF_ERROR) { + break; + } + if (ret != Z_OK) { + strophe_error(conn->ctx, "zlib", "deflate error %d", ret); + conn->error = EBADFD; + conn_disconnect(conn); + return ret; + } + ret = conn->compression.stream.next_in - (const Bytef *)buff; + } while (conn->compression.stream.next_in < (const Bytef *)buff_end); + if (flush) { + ret = _try_compressed_write_to_network(conn, 1); + if (ret < 0) { + return ret; + } + } + return ret; +} + +static void *_zlib_alloc(void *opaque, unsigned int items, unsigned int size) +{ + size_t sz = items * size; + if (sz < items || sz < size) + return NULL; + return strophe_alloc(opaque, sz); +} + +static void _init_zlib_compression(xmpp_ctx_t *ctx, struct zlib_compression *s) +{ + s->buffer = strophe_alloc(ctx, STROPHE_MESSAGE_BUFFER_SIZE); + s->buffer_end = s->buffer + STROPHE_MESSAGE_BUFFER_SIZE; + + s->stream.opaque = ctx; + s->stream.zalloc = _zlib_alloc; + s->stream.zfree = (free_func)strophe_free; +} + +static int _conn_write(xmpp_conn_t *conn, void *buff, size_t len) +{ + if (conn->compress) { + if (conn->compression.buffer == NULL) { + _init_zlib_compression(conn->ctx, &conn->compression); + + conn->compression.stream.next_out = conn->compression.buffer; + conn->compression.stream.avail_out = STROPHE_MESSAGE_BUFFER_SIZE; + int err = + deflateInit(&conn->compression.stream, Z_DEFAULT_COMPRESSION); + if (err != Z_OK) { + strophe_free_and_null(conn->ctx, conn->compression.buffer); + conn->error = EBADFD; + conn_disconnect(conn); + return err; + } + } + return _conn_compress(conn, buff, len, Z_NO_FLUSH); + } else { + return _conn_write_to_network(conn, buff, len); + } +} + +static int _conn_read_from_network(xmpp_conn_t *conn, void *buff, size_t len) +{ + if (conn->tls) { + return tls_read(conn->tls, buff, len); + } else { + return sock_read(conn->sock, buff, len); + } +} + +static int +_conn_decompress(xmpp_conn_t *conn, size_t c_len, void *buff, size_t len) +{ + if (conn->decompression.stream.next_in == NULL) { + conn->decompression.stream.next_in = conn->decompression.buffer; + conn->decompression.buffer_end = + conn->decompression.stream.next_in + c_len; + conn->decompression.stream.avail_in = c_len; + } else if (c_len) { + strophe_error(conn->ctx, "zlib", + "_conn_decompress() called with c_len=%zu", c_len); + } + conn->decompression.stream.next_out = buff; + conn->decompression.stream.avail_out = len; + int ret = inflate(&conn->decompression.stream, Z_SYNC_FLUSH); + switch (ret) { + case Z_STREAM_END: + case Z_OK: + if (conn->decompression.buffer_end == + conn->decompression.stream.next_in) + conn->decompression.stream.next_in = NULL; + /* -fallthrough */ + return conn->decompression.stream.next_out - (Bytef *)buff; + case Z_BUF_ERROR: + break; + default: + strophe_error(conn->ctx, "zlib", "inflate error %d", ret); + conn->error = EBADFD; + conn_disconnect(conn); + break; + } + return 0; +} + +static int _conn_read(xmpp_conn_t *conn, void *buff, size_t len) +{ + void *dbuff = buff; + size_t dlen = len; + if (conn->compress) { + if (conn->decompression.buffer == NULL) { + _init_zlib_compression(conn->ctx, &conn->decompression); + + int err = inflateInit(&conn->decompression.stream); + if (err != Z_OK) { + strophe_free_and_null(conn->ctx, conn->decompression.buffer); + return err; + } + } + if (conn->decompression.stream.next_in != NULL) { + return _conn_decompress(conn, 0, buff, len); + } + dbuff = conn->decompression.buffer; + dlen = STROPHE_MESSAGE_BUFFER_SIZE; + } + int ret = _conn_read_from_network(conn, dbuff, dlen); + if (ret > 0 && conn->compress) { + char *b = xmpp_base64_encode(conn->ctx, dbuff, ret); + printf("Read: %s\n", b); + xmpp_free(conn->ctx, b); + return _conn_decompress(conn, ret, buff, len); + } + return ret; +} + +static int _conn_pending(xmpp_conn_t *conn) +{ + return (conn->compress && conn->decompression.stream.next_in != NULL) || + (conn->tls && tls_pending(conn->tls)); +} + /** Run the event loop once. * This function will run send any data that has been queued by * xmpp_send and related functions and run through the Strophe even @@ -136,15 +332,7 @@ void xmpp_run_once(xmpp_ctx_t *ctx, unsigned long timeout) while (sq) { towrite = sq->len - sq->written; - if (conn->tls) { - ret = tls_write(conn->tls, &sq->data[sq->written], towrite); - if (ret < 0 && !tls_is_recoverable(tls_error(conn->tls))) - conn->error = tls_error(conn->tls); - } else { - ret = sock_write(conn->sock, &sq->data[sq->written], towrite); - if (ret < 0 && !sock_is_recoverable(sock_error())) - conn->error = sock_error(); - } + ret = _conn_write(conn, &sq->data[sq->written], towrite); if (ret > 0 && ret < towrite) sq->written += ret; /* not all data could be sent now */ sq->wip = 1; @@ -181,6 +369,11 @@ void xmpp_run_once(xmpp_ctx_t *ctx, unsigned long timeout) if (!sq) conn->send_queue_tail = NULL; } + if (conn->compress) { + _conn_compress(conn, conn->compression.buffer, 0, + conn->compression_dont_flush ? Z_SYNC_FLUSH + : Z_FULL_FLUSH); + } /* tear down connection on error */ if (conn->error) { @@ -309,14 +502,9 @@ next_item: break; case XMPP_STATE_CONNECTED: - if (FD_ISSET(conn->sock, &rfds) || - (conn->tls && tls_pending(conn->tls))) { - if (conn->tls) { - ret = tls_read(conn->tls, buf, STROPHE_MESSAGE_BUFFER_SIZE); - } else { - ret = - sock_read(conn->sock, buf, STROPHE_MESSAGE_BUFFER_SIZE); - } + if (FD_ISSET(conn->sock, &rfds) || _conn_pending(conn)) { + + ret = _conn_read(conn, buf, STROPHE_MESSAGE_BUFFER_SIZE); if (ret > 0) { ret = parser_feed(conn->parser, buf, ret); diff --git a/strophe.h b/strophe.h index 8f347bb..c4a6946 100644 --- a/strophe.h +++ b/strophe.h @@ -84,6 +84,10 @@ extern "C" { * Namespace definition for Stream Management. */ #define XMPP_NS_SM "urn:xmpp:sm:3" +/** @def XMPP_NS_COMPRESSION + * Namespace definition for Stream Compression. + */ +#define XMPP_NS_COMPRESSION "http://jabber.org/protocol/compress" /* error defines */ /** @def XMPP_EOK @@ -191,6 +195,15 @@ typedef struct _xmpp_sm_t xmpp_sm_state_t; * Disable Stream-Management XEP-0198. */ #define XMPP_CONN_FLAG_DISABLE_SM (1UL << 5) +/** @def XMPP_CONN_FLAG_ENABLE_COMPRESSION + * Enable Stream-Compression XEP-0138. + */ +#define XMPP_CONN_FLAG_ENABLE_COMPRESSION (1UL << 6) +/** @def XMPP_CONN_FLAG_COMPRESSION_DONT_FLUSH + * Don't flush the compressed stream after each stanza. + * Only enable this flag if you know what you're doing. + */ +#define XMPP_CONN_FLAG_COMPRESSION_DONT_FLUSH (1UL << 7) /* connect callback */ typedef enum {