Coverage Report

Created: 2025-07-04 06:41

/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
762k
    std::optional<std::string_view> inflate(ZlibContext * /*zlibContext*/, std::string_view compressed, size_t maxPayloadLength, bool /*reset*/) {
84
762k
        return compressed.substr(0, std::min(maxPayloadLength, compressed.length()));
85
762k
    }
86
11.9k
    InflationStream(CompressOptions /*compressOptions*/) {
87
11.9k
    }
88
};
89
struct DeflationStream {
90
765k
    std::string_view deflate(ZlibContext * /*zlibContext*/, std::string_view raw, bool /*reset*/) {
91
765k
        return raw;
92
765k
    }
93
14.5k
    DeflationStream(CompressOptions /*compressOptions*/) {
94
14.5k
    }
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
        /* Odd place to clear this one, fix */
154
        zlibContext->dynamicDeflationBuffer.clear();
155
156
        deflationStream.next_in = (Bytef *) raw.data();
157
        deflationStream.avail_in = (unsigned int) raw.length();
158
159
        /* This buffer size has to be at least 6 bytes for Z_SYNC_FLUSH to work */
160
        const int DEFLATE_OUTPUT_CHUNK = LARGE_BUFFER_SIZE;
161
162
        int err;
163
        do {
164
            deflationStream.next_out = (Bytef *) zlibContext->deflationBuffer;
165
            deflationStream.avail_out = DEFLATE_OUTPUT_CHUNK;
166
167
            err = ::deflate(&deflationStream, Z_SYNC_FLUSH);
168
            if (Z_OK == err && deflationStream.avail_out == 0) {
169
                zlibContext->dynamicDeflationBuffer.append(zlibContext->deflationBuffer, DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out);
170
                continue;
171
            } else {
172
                break;
173
            }
174
        } while (true);
175
176
        /* This must not change avail_out */
177
        if (reset) {
178
            deflateReset(&deflationStream);
179
        }
180
181
        if (zlibContext->dynamicDeflationBuffer.length()) {
182
            zlibContext->dynamicDeflationBuffer.append(zlibContext->deflationBuffer, DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out);
183
184
            return std::string_view((char *) zlibContext->dynamicDeflationBuffer.data(), zlibContext->dynamicDeflationBuffer.length() - 4);
185
        }
186
187
        /* Note: We will get an interger overflow resulting in heap buffer overflow if Z_BUF_ERROR is returned
188
         * from passing 0 as avail_in. Therefore we must not deflate an empty string */
189
        return {
190
            zlibContext->deflationBuffer,
191
            DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out - 4
192
        };
193
    }
194
195
    ~DeflationStream() {
196
        deflateEnd(&deflationStream);
197
    }
198
};
199
200
struct InflationStream {
201
    z_stream inflationStream = {};
202
203
    InflationStream(CompressOptions compressOptions) {
204
        /* Inflation windowBits are the top 8 bits of the 16 bit compressOptions */
205
        inflateInit2(&inflationStream, -(compressOptions >> 8));
206
    }
207
208
    ~InflationStream() {
209
        inflateEnd(&inflationStream);
210
    }
211
212
    /* Zero length inflates are possible and valid */
213
    std::optional<std::string_view> inflate(ZlibContext *zlibContext, std::string_view compressed, size_t maxPayloadLength, bool reset) {
214
215
#ifdef UWS_USE_LIBDEFLATE
216
        if (reset) {
217
            /* Try fast path first (assuming single DEFLATE block and shared compressor aka reset = true) */
218
            size_t written = 0, consumed;
219
            zlibContext->dynamicInflationBuffer.clear();
220
            zlibContext->dynamicInflationBuffer.reserve(maxPayloadLength);
221
222
            ((char *)compressed.data())[0] |= 0x1; // BFINAL = 1
223
            libdeflate_result res = libdeflate_deflate_decompress_ex(zlibContext->decompressor, compressed.data(), compressed.length(), zlibContext->dynamicInflationBuffer.data(), maxPayloadLength, &consumed, &written);
224
    
225
            /* Still not entirely sure why 1 extra zero byte is optional and ignored by both zlib and libdeflate in some cases */
226
            /* Minimal reproducer is load_test.c with 102 byte message size. It should be tested with Chrome at various message sizes as well. */
227
            if (res == 0 && (consumed == compressed.length() || (consumed + 1 == compressed.length() && compressed[consumed] == '\0'))) {
228
                return std::string_view(zlibContext->dynamicInflationBuffer.data(), written);
229
            } else {
230
                /* We can only end up here if the first DEFLATE block was not the last, so mark it as such */
231
                ((char *)compressed.data())[0] &= ~0x1; // BFINAL = 0
232
            }
233
        }
234
#endif
235
236
        /* Save off the bytes we're about to overwrite */
237
        char* tailLocation = (char*)compressed.data() + compressed.length();
238
        char preTailBytes[4];
239
        memcpy(preTailBytes, tailLocation, 4);
240
241
        /* Append tail to chunk */
242
        unsigned char tail[4] = {0x00, 0x00, 0xff, 0xff};
243
        memcpy(tailLocation, tail, 4);
244
        compressed = {compressed.data(), compressed.length() + 4};
245
246
        /* We clear this one here, could be done better */
247
        zlibContext->dynamicInflationBuffer.clear();
248
249
        inflationStream.next_in = (Bytef *) compressed.data();
250
        inflationStream.avail_in = (unsigned int) compressed.length();
251
252
        int err;
253
        do {
254
            inflationStream.next_out = (Bytef *) zlibContext->inflationBuffer;
255
            inflationStream.avail_out = LARGE_BUFFER_SIZE;
256
257
            err = ::inflate(&inflationStream, Z_SYNC_FLUSH);
258
            if (err == Z_OK && inflationStream.avail_out) {
259
                break;
260
            }
261
262
            zlibContext->dynamicInflationBuffer.append(zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out);
263
264
265
        } while (inflationStream.avail_out == 0 && zlibContext->dynamicInflationBuffer.length() <= maxPayloadLength);
266
267
        if (reset) {
268
            inflateReset(&inflationStream);
269
        }
270
271
        /* Restore the bytes we used for the tail */
272
        memcpy(tailLocation, preTailBytes, 4);
273
274
        if ((err != Z_BUF_ERROR && err != Z_OK) || zlibContext->dynamicInflationBuffer.length() > maxPayloadLength) {
275
            return std::nullopt;
276
        }
277
278
        if (zlibContext->dynamicInflationBuffer.length()) {
279
            zlibContext->dynamicInflationBuffer.append(zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out);
280
281
            /* Let's be strict about the max size */
282
            if (zlibContext->dynamicInflationBuffer.length() > maxPayloadLength) {
283
                return std::nullopt;
284
            }
285
286
            return std::string_view(zlibContext->dynamicInflationBuffer.data(), zlibContext->dynamicInflationBuffer.length());
287
        }
288
289
        /* Let's be strict about the max size */
290
        if ((LARGE_BUFFER_SIZE - inflationStream.avail_out) > maxPayloadLength) {
291
            return std::nullopt;
292
        }
293
294
        return std::string_view(zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out);
295
    }
296
297
};
298
299
#endif
300
301
}
302
303
#endif // UWS_PERMESSAGEDEFLATE_H