Files
libstrophe-gh/src/compression.c
Steffen Jaeckel 3f03de031c Re-factor into separate compression module
Introduce a `conn_interface` to simplify the decision logic which API
we must call.

This also fixes some bugs of the previous commit.

Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
2024-01-31 20:37:30 +01:00

271 lines
8.1 KiB
C

/* SPDX-License-Identifier: MIT OR GPL-3.0-only */
/* compression.c
** strophe XMPP client library -- XEP-0138 Stream Compression
**
** Copyright (C) 2024 Steffen Jaeckel <jaeckel-floss@eyet-services.de>
**
** This software is provided AS-IS with no warranty, either express
** or implied.
**
** This program is dual licensed under the MIT or GPLv3 licenses.
*/
/** @file
* XEP-0138 Stream Compression.
*/
#include <zlib.h>
#include <string.h>
#include <errno.h>
#include "common.h"
#ifndef STROPHE_COMPRESSION_BUFFER_SIZE
/** Max buffer size for compressed data (send & receive). */
#define STROPHE_COMPRESSION_BUFFER_SIZE 4096
#endif
struct zlib_compression {
void *buffer, *buffer_end;
z_stream stream;
};
struct xmpp_compression {
xmpp_conn_t *conn;
struct zlib_compression compression, decompression;
struct conn_interface next;
};
static int _conn_decompress(struct xmpp_compression *comp,
size_t c_len,
void *buff,
size_t len)
{
if (comp->decompression.stream.next_in == NULL) {
comp->decompression.stream.next_in = comp->decompression.buffer;
comp->decompression.buffer_end =
comp->decompression.stream.next_in + c_len;
comp->decompression.stream.avail_in = c_len;
} else if (c_len) {
strophe_error(comp->conn->ctx, "zlib",
"_conn_decompress() called with c_len=%zu", c_len);
}
comp->decompression.stream.next_out = buff;
comp->decompression.stream.avail_out = len;
int ret = inflate(&comp->decompression.stream, Z_SYNC_FLUSH);
switch (ret) {
case Z_STREAM_END:
case Z_OK:
if (comp->decompression.buffer_end ==
comp->decompression.stream.next_in)
comp->decompression.stream.next_in = NULL;
return comp->decompression.stream.next_out - (Bytef *)buff;
case Z_BUF_ERROR:
break;
default:
strophe_error(comp->conn->ctx, "zlib", "inflate error %d", ret);
comp->conn->error = EBADFD;
conn_disconnect(comp->conn);
break;
}
return 0;
}
static int compression_read(struct conn_interface *intf, void *buff, size_t len)
{
xmpp_conn_t *conn = intf->conn;
struct xmpp_compression *comp = conn->compression.state;
void *dbuff = buff;
size_t dlen = len;
if (comp->decompression.stream.next_in != NULL) {
return _conn_decompress(comp, 0, buff, len);
}
dbuff = comp->decompression.buffer;
dlen = STROPHE_COMPRESSION_BUFFER_SIZE;
int ret = comp->next.read(intf, dbuff, dlen);
if (ret > 0) {
return _conn_decompress(comp, ret, buff, len);
}
return ret;
}
static int _try_compressed_write_to_network(xmpp_conn_t *conn, int force)
{
struct xmpp_compression *comp = conn->compression.state;
int ret = 0;
ptrdiff_t len =
comp->compression.stream.next_out - (Bytef *)comp->compression.buffer;
int buffer_full =
comp->compression.stream.next_out == comp->compression.buffer_end;
if ((buffer_full || force) && len > 0) {
ret = conn_interface_write(&comp->next, comp->compression.buffer, len);
if (ret < 0)
return ret;
comp->compression.stream.next_out = comp->compression.buffer;
comp->compression.stream.avail_out = STROPHE_COMPRESSION_BUFFER_SIZE;
}
return ret;
}
static int
_compression_write(xmpp_conn_t *conn, const void *buff, size_t len, int flush)
{
int ret;
const void *buff_end = buff + len;
struct xmpp_compression *comp = conn->compression.state;
comp->compression.stream.next_in = (Bytef *)buff;
comp->compression.stream.avail_in = len;
do {
ret = _try_compressed_write_to_network(conn, 0);
if (ret < 0) {
return ret;
}
ret = deflate(&comp->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 = comp->compression.stream.next_in - (Bytef *)buff;
} while (comp->compression.stream.next_in < (Bytef *)buff_end);
if (flush) {
ret = _try_compressed_write_to_network(conn, 1);
if (ret < 0) {
return ret;
}
}
return ret;
}
static int
compression_write(struct conn_interface *intf, const void *buff, size_t len)
{
return _compression_write(intf->conn, buff, len, Z_NO_FLUSH);
}
static int compression_flush(struct conn_interface *intf)
{
xmpp_conn_t *conn = intf->conn;
struct xmpp_compression *comp = conn->compression.state;
return _compression_write(conn, comp->compression.buffer, 0,
conn->compression.dont_reset ? Z_SYNC_FLUSH
: Z_FULL_FLUSH);
}
static int compression_pending(struct conn_interface *intf)
{
xmpp_conn_t *conn = intf->conn;
struct xmpp_compression *comp = conn->compression.state;
return comp->decompression.stream.next_in != NULL ||
comp->next.pending(intf);
}
static int compression_get_error(struct conn_interface *intf)
{
struct conn_interface *next = &intf->conn->compression.state->next;
return next->get_error(next);
}
static int compression_is_recoverable(struct conn_interface *intf, int err)
{
struct conn_interface *next = &intf->conn->compression.state->next;
return next->error_is_recoverable(next, err);
}
static const struct conn_interface compression_intf = {
compression_read,
compression_write,
compression_flush,
compression_pending,
compression_get_error,
compression_is_recoverable,
NULL,
};
static void *_zlib_alloc(void *opaque, unsigned int items, unsigned int size)
{
size_t sz = items * size;
/* Poor man's multiplication overflow check */
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_COMPRESSION_BUFFER_SIZE);
s->buffer_end = s->buffer + STROPHE_COMPRESSION_BUFFER_SIZE;
s->stream.opaque = ctx;
s->stream.zalloc = _zlib_alloc;
s->stream.zfree = (free_func)strophe_free;
}
int compression_init(xmpp_conn_t *conn)
{
if (!conn->compression.allowed || !conn->compression.supported)
return -1;
conn->compression.state =
strophe_alloc(conn->ctx, sizeof(*conn->compression.state));
struct xmpp_compression *comp = conn->compression.state;
memset(comp, 0, sizeof(*comp));
comp->conn = conn;
comp->next = conn->intf;
conn->intf = compression_intf;
conn->intf.conn = conn;
_init_zlib_compression(conn->ctx, &comp->compression);
comp->compression.stream.next_out = comp->compression.buffer;
comp->compression.stream.avail_out = STROPHE_COMPRESSION_BUFFER_SIZE;
int err = deflateInit(&comp->compression.stream, Z_DEFAULT_COMPRESSION);
if (err != Z_OK) {
strophe_free_and_null(conn->ctx, comp->compression.buffer);
conn->error = EBADFD;
conn_disconnect(conn);
return err;
}
_init_zlib_compression(conn->ctx, &comp->decompression);
err = inflateInit(&comp->decompression.stream);
if (err != Z_OK) {
strophe_free_and_null(conn->ctx, comp->decompression.buffer);
conn->error = EBADFD;
conn_disconnect(conn);
return err;
}
return 0;
}
void compression_free(xmpp_conn_t *conn)
{
struct xmpp_compression *comp = conn->compression.state;
if (!comp)
return;
if (comp->compression.buffer) {
deflateEnd(&comp->compression.stream);
strophe_free_and_null(conn->ctx, comp->compression.buffer);
}
if (comp->decompression.buffer) {
inflateEnd(&comp->decompression.stream);
strophe_free_and_null(conn->ctx, comp->decompression.buffer);
}
}
void compression_handle_feature_children(xmpp_conn_t *conn, const char *text)
{
if (strcasecmp(text, "zlib") == 0) {
conn->compression.supported = 1;
}
}