LCOV - code coverage report
Current view: top level - src - compression.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 2.2 % 137 3
Test Date: 2024-08-20 10:03:45 Functions: 7.1 % 14 1

            Line data    Source code
       1              : /* SPDX-License-Identifier: MIT OR GPL-3.0-only */
       2              : /* compression.c
       3              : ** strophe XMPP client library -- XEP-0138 Stream Compression
       4              : **
       5              : ** Copyright (C) 2024 Steffen Jaeckel <jaeckel-floss@eyet-services.de>
       6              : **
       7              : **  This software is provided AS-IS with no warranty, either express
       8              : **  or implied.
       9              : **
      10              : **  This program is dual licensed under the MIT or GPLv3 licenses.
      11              : */
      12              : 
      13              : /** @file
      14              :  *  XEP-0138 Stream Compression.
      15              :  */
      16              : #include <zlib.h>
      17              : #include <string.h>
      18              : #include <errno.h>
      19              : 
      20              : #include "common.h"
      21              : 
      22              : #ifndef STROPHE_COMPRESSION_BUFFER_SIZE
      23              : /** Max buffer size for compressed data (send & receive). */
      24              : #define STROPHE_COMPRESSION_BUFFER_SIZE 4096
      25              : #endif
      26              : 
      27              : struct zlib_compression {
      28              :     void *buffer, *buffer_end;
      29              :     z_stream stream;
      30              : };
      31              : 
      32              : struct xmpp_compression {
      33              :     xmpp_conn_t *conn;
      34              :     struct zlib_compression compression, decompression;
      35              :     struct conn_interface next;
      36              : };
      37              : 
      38            0 : static int _conn_decompress(struct xmpp_compression *comp,
      39              :                             size_t c_len,
      40              :                             void *buff,
      41              :                             size_t len)
      42              : {
      43            0 :     if (comp->decompression.stream.next_in == NULL) {
      44            0 :         comp->decompression.stream.next_in = comp->decompression.buffer;
      45            0 :         comp->decompression.buffer_end =
      46            0 :             comp->decompression.stream.next_in + c_len;
      47            0 :         comp->decompression.stream.avail_in = c_len;
      48            0 :     } else if (c_len) {
      49            0 :         strophe_error(comp->conn->ctx, "zlib",
      50              :                       "_conn_decompress() called with c_len=%zu", c_len);
      51              :     }
      52            0 :     comp->decompression.stream.next_out = buff;
      53            0 :     comp->decompression.stream.avail_out = len;
      54            0 :     int ret = inflate(&comp->decompression.stream, Z_SYNC_FLUSH);
      55            0 :     switch (ret) {
      56            0 :     case Z_STREAM_END:
      57              :     case Z_OK:
      58            0 :         if (comp->decompression.buffer_end ==
      59            0 :             comp->decompression.stream.next_in)
      60            0 :             comp->decompression.stream.next_in = NULL;
      61            0 :         return comp->decompression.stream.next_out - (Bytef *)buff;
      62              :     case Z_BUF_ERROR:
      63              :         break;
      64            0 :     default:
      65            0 :         strophe_error(comp->conn->ctx, "zlib", "inflate error %d", ret);
      66            0 :         comp->conn->error = EBADFD;
      67            0 :         conn_disconnect(comp->conn);
      68            0 :         break;
      69              :     }
      70              :     return 0;
      71              : }
      72              : 
      73            0 : static int compression_read(struct conn_interface *intf, void *buff, size_t len)
      74              : {
      75            0 :     xmpp_conn_t *conn = intf->conn;
      76            0 :     struct xmpp_compression *comp = conn->compression.state;
      77            0 :     void *dbuff = buff;
      78            0 :     size_t dlen = len;
      79            0 :     if (comp->decompression.stream.next_in != NULL) {
      80            0 :         return _conn_decompress(comp, 0, buff, len);
      81              :     }
      82            0 :     dbuff = comp->decompression.buffer;
      83            0 :     dlen = STROPHE_COMPRESSION_BUFFER_SIZE;
      84            0 :     int ret = comp->next.read(intf, dbuff, dlen);
      85            0 :     if (ret > 0) {
      86            0 :         return _conn_decompress(comp, ret, buff, len);
      87              :     }
      88              :     return ret;
      89              : }
      90              : 
      91            0 : static int _try_compressed_write_to_network(xmpp_conn_t *conn, int force)
      92              : {
      93            0 :     struct xmpp_compression *comp = conn->compression.state;
      94            0 :     int ret = 0;
      95            0 :     ptrdiff_t len =
      96            0 :         comp->compression.stream.next_out - (Bytef *)comp->compression.buffer;
      97            0 :     int buffer_full =
      98            0 :         comp->compression.stream.next_out == comp->compression.buffer_end;
      99            0 :     if ((buffer_full || force) && len > 0) {
     100            0 :         ret = conn_interface_write(&comp->next, comp->compression.buffer, len);
     101            0 :         if (ret < 0)
     102              :             return ret;
     103            0 :         comp->compression.stream.next_out = comp->compression.buffer;
     104            0 :         comp->compression.stream.avail_out = STROPHE_COMPRESSION_BUFFER_SIZE;
     105              :     }
     106              :     return ret;
     107              : }
     108              : 
     109              : static int
     110            0 : _compression_write(xmpp_conn_t *conn, const void *buff, size_t len, int flush)
     111              : {
     112            0 :     int ret;
     113            0 :     const void *buff_end = buff + len;
     114            0 :     struct xmpp_compression *comp = conn->compression.state;
     115            0 :     comp->compression.stream.next_in = (Bytef *)buff;
     116            0 :     comp->compression.stream.avail_in = len;
     117            0 :     do {
     118            0 :         ret = _try_compressed_write_to_network(conn, 0);
     119            0 :         if (ret < 0) {
     120            0 :             return ret;
     121              :         }
     122              : 
     123            0 :         ret = deflate(&comp->compression.stream, flush);
     124            0 :         if (ret == Z_STREAM_END) {
     125              :             break;
     126              :         }
     127            0 :         if (flush && ret == Z_BUF_ERROR) {
     128              :             break;
     129              :         }
     130            0 :         if (ret != Z_OK) {
     131            0 :             strophe_error(conn->ctx, "zlib", "deflate error %d", ret);
     132            0 :             conn->error = EBADFD;
     133            0 :             conn_disconnect(conn);
     134            0 :             return ret;
     135              :         }
     136            0 :         ret = comp->compression.stream.next_in - (Bytef *)buff;
     137            0 :     } while (comp->compression.stream.next_in < (Bytef *)buff_end);
     138            0 :     if (flush) {
     139            0 :         ret = _try_compressed_write_to_network(conn, 1);
     140            0 :         if (ret < 0) {
     141              :             return ret;
     142              :         }
     143              :     }
     144              :     return ret;
     145              : }
     146              : 
     147              : static int
     148            0 : compression_write(struct conn_interface *intf, const void *buff, size_t len)
     149              : {
     150            0 :     return _compression_write(intf->conn, buff, len, Z_NO_FLUSH);
     151              : }
     152              : 
     153            0 : static int compression_flush(struct conn_interface *intf)
     154              : {
     155            0 :     xmpp_conn_t *conn = intf->conn;
     156            0 :     struct xmpp_compression *comp = conn->compression.state;
     157            0 :     return _compression_write(conn, comp->compression.buffer, 0,
     158            0 :                               conn->compression.dont_reset ? Z_SYNC_FLUSH
     159              :                                                            : Z_FULL_FLUSH);
     160              : }
     161              : 
     162            0 : static int compression_pending(struct conn_interface *intf)
     163              : {
     164            0 :     xmpp_conn_t *conn = intf->conn;
     165            0 :     struct xmpp_compression *comp = conn->compression.state;
     166            0 :     return comp->decompression.stream.next_in != NULL ||
     167            0 :            comp->next.pending(intf);
     168              : }
     169              : 
     170            0 : static int compression_get_error(struct conn_interface *intf)
     171              : {
     172            0 :     struct conn_interface *next = &intf->conn->compression.state->next;
     173            0 :     return next->get_error(next);
     174              : }
     175              : 
     176            0 : static int compression_is_recoverable(struct conn_interface *intf, int err)
     177              : {
     178            0 :     struct conn_interface *next = &intf->conn->compression.state->next;
     179            0 :     return next->error_is_recoverable(next, err);
     180              : }
     181              : 
     182              : static const struct conn_interface compression_intf = {
     183              :     compression_read,
     184              :     compression_write,
     185              :     compression_flush,
     186              :     compression_pending,
     187              :     compression_get_error,
     188              :     compression_is_recoverable,
     189              :     NULL,
     190              : };
     191              : 
     192            0 : static void *_zlib_alloc(void *opaque, unsigned int items, unsigned int size)
     193              : {
     194            0 :     size_t sz = items * size;
     195              :     /* Poor man's multiplication overflow check */
     196            0 :     if (sz < items || sz < size)
     197              :         return NULL;
     198            0 :     return strophe_alloc(opaque, sz);
     199              : }
     200              : 
     201            0 : static void _init_zlib_compression(xmpp_ctx_t *ctx, struct zlib_compression *s)
     202              : {
     203            0 :     s->buffer = strophe_alloc(ctx, STROPHE_COMPRESSION_BUFFER_SIZE);
     204            0 :     s->buffer_end = s->buffer + STROPHE_COMPRESSION_BUFFER_SIZE;
     205              : 
     206            0 :     s->stream.opaque = ctx;
     207            0 :     s->stream.zalloc = _zlib_alloc;
     208            0 :     s->stream.zfree = (free_func)strophe_free;
     209            0 : }
     210              : 
     211            0 : int compression_init(xmpp_conn_t *conn)
     212              : {
     213            0 :     if (!conn->compression.allowed || !conn->compression.supported)
     214              :         return -1;
     215            0 :     conn->compression.state =
     216            0 :         strophe_alloc(conn->ctx, sizeof(*conn->compression.state));
     217            0 :     struct xmpp_compression *comp = conn->compression.state;
     218            0 :     memset(comp, 0, sizeof(*comp));
     219              : 
     220            0 :     comp->conn = conn;
     221              : 
     222            0 :     comp->next = conn->intf;
     223            0 :     conn->intf = compression_intf;
     224            0 :     conn->intf.conn = conn;
     225              : 
     226            0 :     _init_zlib_compression(conn->ctx, &comp->compression);
     227              : 
     228            0 :     comp->compression.stream.next_out = comp->compression.buffer;
     229            0 :     comp->compression.stream.avail_out = STROPHE_COMPRESSION_BUFFER_SIZE;
     230            0 :     int err = deflateInit(&comp->compression.stream, Z_DEFAULT_COMPRESSION);
     231            0 :     if (err != Z_OK) {
     232            0 :         strophe_free_and_null(conn->ctx, comp->compression.buffer);
     233            0 :         conn->error = EBADFD;
     234            0 :         conn_disconnect(conn);
     235            0 :         return err;
     236              :     }
     237              : 
     238            0 :     _init_zlib_compression(conn->ctx, &comp->decompression);
     239              : 
     240            0 :     err = inflateInit(&comp->decompression.stream);
     241            0 :     if (err != Z_OK) {
     242            0 :         strophe_free_and_null(conn->ctx, comp->decompression.buffer);
     243            0 :         conn->error = EBADFD;
     244            0 :         conn_disconnect(conn);
     245            0 :         return err;
     246              :     }
     247              :     return 0;
     248              : }
     249              : 
     250            8 : void compression_free(xmpp_conn_t *conn)
     251              : {
     252            8 :     struct xmpp_compression *comp = conn->compression.state;
     253            8 :     if (!comp)
     254              :         return;
     255            0 :     if (comp->compression.buffer) {
     256            0 :         deflateEnd(&comp->compression.stream);
     257            0 :         strophe_free_and_null(conn->ctx, comp->compression.buffer);
     258              :     }
     259            0 :     if (comp->decompression.buffer) {
     260            0 :         inflateEnd(&comp->decompression.stream);
     261            0 :         strophe_free_and_null(conn->ctx, comp->decompression.buffer);
     262              :     }
     263              : }
     264              : 
     265            0 : void compression_handle_feature_children(xmpp_conn_t *conn, const char *text)
     266              : {
     267            0 :     if (strcasecmp(text, "zlib") == 0) {
     268            0 :         conn->compression.supported = 1;
     269              :     }
     270            0 : }
        

Generated by: LCOV version 2.0-1