/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 |