diff --git a/inc/common/q2proto_config.h b/inc/common/q2proto_config.h index 3b43eee42..e9fa0ce98 100644 --- a/inc/common/q2proto_config.h +++ b/inc/common/q2proto_config.h @@ -25,3 +25,4 @@ with this program; if not, write to the Free Software Foundation, Inc., #define Q2PROTO_RETURN_IO_ERROR_CODES 0 #define Q2PROTO_ERROR_FEEDBACK 1 #define Q2PROTO_SHOWNET USE_CLIENT && USE_DEBUG +#define Q2PROTO_COMPRESSION_DEFLATE USE_ZLIB diff --git a/inc/common/q2proto_shared.h b/inc/common/q2proto_shared.h index d9530d077..ce163e6c7 100644 --- a/inc/common/q2proto_shared.h +++ b/inc/common/q2proto_shared.h @@ -20,6 +20,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "q2proto/q2proto.h" +#if USE_ZLIB +#include + +struct q2protoio_deflate_args_s +{ + /// Buffer to store deflated data + byte *z_buffer; + /// Size of deflated data buffer + unsigned z_buffer_size; + /// Deflate stream + z_streamp z; +}; +#endif // USE_ZLIB + typedef struct q2protoio_ioarg_s { sizebuf_t *sz_read; } q2protoio_ioarg_t; diff --git a/meson.build b/meson.build index c99ddd1b5..af960cd2a 100644 --- a/meson.build +++ b/meson.build @@ -41,6 +41,7 @@ common_src = [ 'q2proto/src/dummy_q2protoerr_client_write.c', 'q2proto/src/dummy_q2protoerr_server_read.c', 'q2proto/src/dummy_q2protoerr_server_write.c', + 'q2proto/src/dummy_q2protoio_deflate.c', 'q2proto/src/dummy_q2protoio_write.c', 'q2proto/src/q2proto_client.c', 'q2proto/src/q2proto_coords.c', diff --git a/src/common/q2proto-glue.c b/src/common/q2proto-glue.c index 7badd361f..c5b7c1d5c 100644 --- a/src/common/q2proto-glue.c +++ b/src/common/q2proto-glue.c @@ -25,11 +25,39 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "q2proto/q2proto.h" +#if USE_ZLIB +#include + +#define MAX_DEFLATED_SIZE 0x10000 + +// FIXME: need a mechanism to clean up the stream(s)? +static struct { + /// Buffer to receive inflated data + byte buffer[MAX_DEFLATED_SIZE]; + /// zlib stream + z_stream z; + /// Whether the deflate stream ended + bool stream_end; +} io_inflate; + +#endif + +// Sizebuf with inflated data +static sizebuf_t msg_inflate; + q2protoio_ioarg_t default_q2protoio_ioarg = {.sz_read = &msg_read}; +static q2protoio_ioarg_t inflate_q2protoio_ioarg = {.sz_read = &msg_inflate}; + +// I/O arg: read from inflated data +#define IOARG_INFLATE ((uintptr_t)&inflate_q2protoio_ioarg) static byte* io_read_data(uintptr_t io_arg, size_t len, size_t *readcount) { +#if !USE_ZLIB Q_assert(io_arg == _Q2PROTO_IOARG_DEFAULT); +#else + Q_assert(io_arg == _Q2PROTO_IOARG_DEFAULT || io_arg == IOARG_INFLATE); +#endif sizebuf_t *sz = ((q2protoio_ioarg_t*)io_arg)->sz_read; if (readcount) { @@ -83,6 +111,73 @@ const void* q2protoio_read_raw(uintptr_t io_arg, size_t size, size_t* readcount) return io_read_data(io_arg, size, readcount); } +#if USE_ZLIB +q2proto_error_t q2protoio_inflate_begin(uintptr_t io_arg, uintptr_t* inflate_io_arg) +{ + if (io_arg != _Q2PROTO_IOARG_DEFAULT) { + Com_Error(ERR_DROP, "%s: recursively entered", __func__); + } + + int ret; + if (io_inflate.z.state) + ret = inflateReset(&io_inflate.z); + else + ret = inflateInit2(&io_inflate.z, -MAX_WBITS); + if (ret != Z_OK) { + Com_Error(ERR_DROP, "%s: inflate initialization failed with error %d", __func__, ret); + } + io_inflate.stream_end = false; + + *inflate_io_arg = IOARG_INFLATE; + return Q2P_ERR_SUCCESS; +} + +q2proto_error_t q2protoio_inflate_data(uintptr_t io_arg, uintptr_t inflate_io_arg, size_t compressed_size) +{ + Q_assert(io_arg == _Q2PROTO_IOARG_DEFAULT); + Q_assert(inflate_io_arg == IOARG_INFLATE); + + byte *in_data; + if (compressed_size == (size_t)-1) + in_data = io_read_data(io_arg, SIZE_MAX, &compressed_size); + else + in_data = io_read_data(io_arg, compressed_size, NULL); + io_inflate.z.next_in = in_data; + io_inflate.z.avail_in = compressed_size; + io_inflate.z.next_out = io_inflate.buffer; + io_inflate.z.avail_out = sizeof(io_inflate.buffer); + int ret = inflate(&io_inflate.z, Z_SYNC_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) { + inflateEnd(&io_inflate.z); + Com_Error(ERR_DROP, "%s: inflate() failed with error %d", __func__, ret); + } + + io_inflate.stream_end = ret == Z_STREAM_END; + + SZ_InitRead(&msg_inflate, io_inflate.buffer, sizeof(io_inflate.buffer)); + msg_inflate.cursize = sizeof(io_inflate.buffer) - io_inflate.z.avail_out; + + return Q2P_ERR_SUCCESS; +} + +q2proto_error_t q2protoio_inflate_stream_ended(uintptr_t inflate_io_arg, bool *stream_end) +{ + Q_assert(inflate_io_arg == IOARG_INFLATE); + *stream_end = io_inflate.stream_end; + return Q2P_ERR_SUCCESS; +} + +q2proto_error_t q2protoio_inflate_end(uintptr_t inflate_io_arg) +{ + Q_assert(inflate_io_arg == IOARG_INFLATE); + int ret = inflateEnd(&io_inflate.z); + if (ret != Z_OK && ret != Z_STREAM_END) { + Com_Error(ERR_DROP, "%s: inflateEnd() failed with error %d", __func__, ret); + } + return msg_inflate.readcount < msg_inflate.cursize ? Q2P_ERR_MORE_DATA_DEFLATED : Q2P_ERR_SUCCESS; +} +#endif + bool nonfatal_client_read_errors = false; q2proto_error_t q2protoerr_client_read(uintptr_t io_arg, q2proto_error_t err, const char *msg, ...)