Coverage Report

Created: 2023-09-25 07:18

/src/uWebSockets/src/PerMessageDeflate.h
Line
Count
Source
1
/*
2
 * Authored by Alex Hultman, 2018-2021.
3
 * Intellectual property of third-party.
4
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
9
 *     http://www.apache.org/licenses/LICENSE-2.0
10
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
/* This standalone module implements deflate / inflate streams */
19
20
#ifndef UWS_PERMESSAGEDEFLATE_H
21
#define UWS_PERMESSAGEDEFLATE_H
22
23
#include <cstdint>
24
#include <cstring>
25
26
/* We always define these options no matter if ZLIB is enabled or not */
27
namespace uWS {
28
    /* Compressor mode is 8 lowest bits where HIGH4(windowBits), LOW4(memLevel).
29
     * Decompressor mode is 8 highest bits LOW4(windowBits).
30
     * If compressor or decompressor bits are 1, then they are shared.
31
     * If everything is just simply 0, then everything is disabled. */
32
    enum CompressOptions : uint16_t {
33
        /* These are not actual compression options */
34
        _COMPRESSOR_MASK = 0x00FF,
35
        _DECOMPRESSOR_MASK = 0x0F00,
36
        /* Disabled, shared, shared are "special" values */
37
        DISABLED = 0,
38
        SHARED_COMPRESSOR = 1,
39
        SHARED_DECOMPRESSOR = 1 << 8,
40
        /* Highest 4 bits describe decompressor */
41
        DEDICATED_DECOMPRESSOR_32KB = 15 << 8,
42
        DEDICATED_DECOMPRESSOR_16KB = 14 << 8,
43
        DEDICATED_DECOMPRESSOR_8KB = 13 << 8,
44
        DEDICATED_DECOMPRESSOR_4KB = 12 << 8,
45
        DEDICATED_DECOMPRESSOR_2KB = 11 << 8,
46
        DEDICATED_DECOMPRESSOR_1KB = 10 << 8,
47
        DEDICATED_DECOMPRESSOR_512B = 9 << 8,
48
        /* Same as 32kb */
49
        DEDICATED_DECOMPRESSOR = 15 << 8,
50
51
        /* Lowest 8 bit describe compressor */
52
        DEDICATED_COMPRESSOR_3KB = 9 << 4 | 1,
53
        DEDICATED_COMPRESSOR_4KB = 9 << 4 | 2,
54
        DEDICATED_COMPRESSOR_8KB = 10 << 4 | 3,
55
        DEDICATED_COMPRESSOR_16KB = 11 << 4 | 4,
56
        DEDICATED_COMPRESSOR_32KB = 12 << 4 | 5,
57
        DEDICATED_COMPRESSOR_64KB = 13 << 4 | 6,
58
        DEDICATED_COMPRESSOR_128KB = 14 << 4 | 7,
59
        DEDICATED_COMPRESSOR_256KB = 15 << 4 | 8,
60
        /* Same as 256kb */
61
        DEDICATED_COMPRESSOR = 15 << 4 | 8
62
    };
63
}
64
65
#if !defined(UWS_NO_ZLIB) && !defined(UWS_MOCK_ZLIB)
66
#include <zlib.h>
67
#endif
68
69
#include <string>
70
#include <optional>
71
72
#ifdef UWS_USE_LIBDEFLATE
73
#include "libdeflate.h"
74
#include <cstring>
75
#endif
76
77
namespace uWS {
78
79
/* Do not compile this module if we don't want it */
80
#if defined(UWS_NO_ZLIB) || defined(UWS_MOCK_ZLIB)
81
struct ZlibContext {};
82
struct InflationStream {
83
35.7k
    std::optional<std::string_view> inflate(ZlibContext * /*zlibContext*/, std::string_view compressed, size_t maxPayloadLength, bool /*reset*/) {
84
35.7k
        return compressed.substr(0, std::min(maxPayloadLength, compressed.length()));
85
35.7k
    }
86
11.5k
    InflationStream(CompressOptions /*compressOptions*/) {
87
11.5k
    }
88
};
89
struct DeflationStream {
90
39.4k
    std::string_view deflate(ZlibContext * /*zlibContext*/, std::string_view raw, bool /*reset*/) {
91
39.4k
        return raw;
92
39.4k
    }
93
13.8k
    DeflationStream(CompressOptions /*compressOptions*/) {
94
13.8k
    }
95
};
96
#else
97
98
#define LARGE_BUFFER_SIZE 1024 * 16 // todo: fix this
99
100
struct ZlibContext {
101
    /* Any returned data is valid until next same-class call.
102
     * We need to have two classes to allow inflation followed
103
     * by many deflations without modifying the inflation */
104
    std::string dynamicDeflationBuffer;
105
    std::string dynamicInflationBuffer;
106
    char *deflationBuffer;
107
    char *inflationBuffer;
108
109
#ifdef UWS_USE_LIBDEFLATE
110
    libdeflate_decompressor *decompressor;
111
    libdeflate_compressor *compressor;
112
#endif
113
114
    ZlibContext() {
115
        deflationBuffer = (char *) malloc(LARGE_BUFFER_SIZE);
116
        inflationBuffer = (char *) malloc(LARGE_BUFFER_SIZE);
117
118
#ifdef UWS_USE_LIBDEFLATE
119
        decompressor = libdeflate_alloc_decompressor();
120
        compressor = libdeflate_alloc_compressor(6);
121
#endif
122
    }
123
124
    ~ZlibContext() {
125
        free(deflationBuffer);
126
        free(inflationBuffer);
127
128
#ifdef UWS_USE_LIBDEFLATE
129
        libdeflate_free_decompressor(decompressor);
130
        libdeflate_free_compressor(compressor);
131
#endif
132
    }
133
};
134
135
struct DeflationStream {
136
    z_stream deflationStream = {};
137
138
    DeflationStream(CompressOptions compressOptions) {
139
140
        /* Sliding inflator should be about 44kb by default, less than compressor */
141
142
        /* Memory usage is given by 2 ^ (windowBits + 2) + 2 ^ (memLevel + 9) */
143
        int windowBits = -(int) ((compressOptions & _COMPRESSOR_MASK) >> 4), memLevel = compressOptions & 0xF;
144
145
        //printf("windowBits: %d, memLevel: %d\n", windowBits, memLevel);
146
147
        deflateInit2(&deflationStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, windowBits, memLevel, Z_DEFAULT_STRATEGY);
148
    }
149
150
    /* Deflate and optionally reset. You must not deflate an empty string. */
151
    std::string_view deflate(ZlibContext *zlibContext, std::string_view raw, bool reset) {
152
153
#ifdef UWS_USE_LIBDEFLATE
154
        /* Run a fast path in case of shared_compressor */
155
        if (reset) {
156
            size_t written = 0;
157
            static unsigned char buf[1024 + 1];
158
159
            written = libdeflate_deflate_compress(zlibContext->compressor, raw.data(), raw.length(), buf, 1024);
160
161
            if (written) {
162
                memcpy(&buf[written], "\x00", 1);
163
                return std::string_view((char *) buf, written + 1);
164
            }
165
        }
166
#endif
167
168
        /* Odd place to clear this one, fix */
169
        zlibContext->dynamicDeflationBuffer.clear();
170
171
        deflationStream.next_in = (Bytef *) raw.data();
172
        deflationStream.avail_in = (unsigned int) raw.length();
173
174
        /* This buffer size has to be at least 6 bytes for Z_SYNC_FLUSH to work */
175
        const int DEFLATE_OUTPUT_CHUNK = LARGE_BUFFER_SIZE;
176
177
        int err;
178
        do {
179
            deflationStream.next_out = (Bytef *) zlibContext->deflationBuffer;
180
            deflationStream.avail_out = DEFLATE_OUTPUT_CHUNK;
181
182
            err = ::deflate(&deflationStream, Z_SYNC_FLUSH);
183
            if (Z_OK == err && deflationStream.avail_out == 0) {
184
                zlibContext->dynamicDeflationBuffer.append(zlibContext->deflationBuffer, DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out);
185
                continue;
186
            } else {
187
                break;
188
            }
189
        } while (true);
190
191
        /* This must not change avail_out */
192
        if (reset) {
193
            deflateReset(&deflationStream);
194
        }
195
196
        if (zlibContext->dynamicDeflationBuffer.length()) {
197
            zlibContext->dynamicDeflationBuffer.append(zlibContext->deflationBuffer, DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out);
198
199
            return std::string_view((char *) zlibContext->dynamicDeflationBuffer.data(), zlibContext->dynamicDeflationBuffer.length() - 4);
200
        }
201
202
        /* Note: We will get an interger overflow resulting in heap buffer overflow if Z_BUF_ERROR is returned
203
         * from passing 0 as avail_in. Therefore we must not deflate an empty string */
204
        return {
205
            zlibContext->deflationBuffer,
206
            DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out - 4
207
        };
208
    }
209
210
    ~DeflationStream() {
211
        deflateEnd(&deflationStream);
212
    }
213
};
214
215
struct InflationStream {
216
    z_stream inflationStream = {};
217
218
    InflationStream(CompressOptions compressOptions) {
219
        /* Inflation windowBits are the top 8 bits of the 16 bit compressOptions */
220
        inflateInit2(&inflationStream, -(compressOptions >> 8));
221
    }
222
223
    ~InflationStream() {
224
        inflateEnd(&inflationStream);
225
    }
226
227
    /* Zero length inflates are possible and valid */
228
    std::optional<std::string_view> inflate(ZlibContext *zlibContext, std::string_view compressed, size_t maxPayloadLength, bool reset) {
229
230
#ifdef UWS_USE_LIBDEFLATE
231
        /* Try fast path first */
232
        size_t written = 0;
233
        static char buf[1024];
234
235
        /* We have to pad 9 bytes and restore those bytes when done since 9 is more than 6 of next WebSocket message */
236
        char tmp[9];
237
        memcpy(tmp, (char *) compressed.data() + compressed.length(), 9);
238
        memcpy((char *) compressed.data() + compressed.length(), "\x00\x00\xff\xff\x01\x00\x00\xff\xff", 9);
239
        libdeflate_result res = libdeflate_deflate_decompress(zlibContext->decompressor, compressed.data(), compressed.length() + 9, buf, 1024, &written);
240
        memcpy((char *) compressed.data() + compressed.length(), tmp, 9);
241
242
        if (res == 0) {
243
            /* Fast path wins */
244
            return std::string_view(buf, written);
245
        }
246
#endif
247
248
        /* Save off the bytes we're about to overwrite */
249
        char* tailLocation = (char*)compressed.data() + compressed.length();
250
        char preTailBytes[4];
251
        memcpy(preTailBytes, tailLocation, 4);
252
253
        /* Append tail to chunk */
254
        unsigned char tail[4] = {0x00, 0x00, 0xff, 0xff};
255
        memcpy(tailLocation, tail, 4);
256
        compressed = {compressed.data(), compressed.length() + 4};
257
258
        /* We clear this one here, could be done better */
259
        zlibContext->dynamicInflationBuffer.clear();
260
261
        inflationStream.next_in = (Bytef *) compressed.data();
262
        inflationStream.avail_in = (unsigned int) compressed.length();
263
264
        int err;
265
        do {
266
            inflationStream.next_out = (Bytef *) zlibContext->inflationBuffer;
267
            inflationStream.avail_out = LARGE_BUFFER_SIZE;
268
269
            err = ::inflate(&inflationStream, Z_SYNC_FLUSH);
270
            if (err == Z_OK && inflationStream.avail_out) {
271
                break;
272
            }
273
274
            zlibContext->dynamicInflationBuffer.append(zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out);
275
276
277
        } while (inflationStream.avail_out == 0 && zlibContext->dynamicInflationBuffer.length() <= maxPayloadLength);
278
279
        if (reset) {
280
            inflateReset(&inflationStream);
281
        }
282
283
        /* Restore the bytes we used for the tail */
284
        memcpy(tailLocation, preTailBytes, 4);
285
286
        if ((err != Z_BUF_ERROR && err != Z_OK) || zlibContext->dynamicInflationBuffer.length() > maxPayloadLength) {
287
            return std::nullopt;
288
        }
289
290
        if (zlibContext->dynamicInflationBuffer.length()) {
291
            zlibContext->dynamicInflationBuffer.append(zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out);
292
293
            /* Let's be strict about the max size */
294
            if (zlibContext->dynamicInflationBuffer.length() > maxPayloadLength) {
295
                return std::nullopt;
296
            }
297
298
            return std::string_view(zlibContext->dynamicInflationBuffer.data(), zlibContext->dynamicInflationBuffer.length());
299
        }
300
301
        /* Let's be strict about the max size */
302
        if ((LARGE_BUFFER_SIZE - inflationStream.avail_out) > maxPayloadLength) {
303
            return std::nullopt;
304
        }
305
306
        return std::string_view(zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out);
307
    }
308
309
};
310
311
#endif
312
313
}
314
315
#endif // UWS_PERMESSAGEDEFLATE_H