LCOV - code coverage report
Current view: top level - src - compression.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 3 136 2.2 %
Date: 2024-02-24 14:12:49 Functions: 1 13 7.7 %

          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 = ret;
      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           0 :             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 = ret;
     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             : 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             : }
     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 ret = deflateInit(&comp->compression.stream, Z_DEFAULT_COMPRESSION);
     231           0 :     if (ret != Z_OK) {
     232           0 :         strophe_free_and_null(conn->ctx, comp->compression.buffer);
     233           0 :         conn->error = ret;
     234           0 :         conn_disconnect(conn);
     235           0 :         return ret;
     236             :     }
     237             : 
     238           0 :     _init_zlib_compression(conn->ctx, &comp->decompression);
     239             : 
     240           0 :     ret = inflateInit(&comp->decompression.stream);
     241           0 :     if (ret != Z_OK) {
     242           0 :         strophe_free_and_null(conn->ctx, comp->decompression.buffer);
     243           0 :         conn->error = ret;
     244           0 :         conn_disconnect(conn);
     245           0 :         return ret;
     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 1.14