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 : }
|