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 {