File Coverage

src/panda/protocol/websocket/DeflateExt.cc
Criterion Covered Total %
statement 194 215 90.2
branch 174 286 60.8
condition n/a
subroutine n/a
pod n/a
total 368 501 73.4


line stmt bran cond sub pod time code
1             #include "DeflateExt.h"
2             #include
3             #include
4              
5             namespace panda { namespace protocol { namespace websocket {
6              
7             const char* DeflateExt::extension_name = "permessage-deflate";
8              
9             static const int UNCOMPRESS_PREALLOCATE_RATIO = 10;
10             static const float COMPRESS_PREALLOCATE_RATIO = 1;
11             static const float GROW_RATIO = 1.5;
12              
13             static const char PARAM_SERVER_NO_CONTEXT_TAKEOVER[] = "server_no_context_takeover";
14             static const char PARAM_CLIENT_NO_CONTEXT_TAKEOVER[] = "client_no_context_takeover";
15             static const char PARAM_SERVER_MAX_WINDOW_BITS[] = "server_max_window_bits";
16             static const char PARAM_CLIENT_MAX_WINDOW_BITS[] = "client_max_window_bits";
17              
18 18           panda::optional DeflateExt::bootstrap() {
19             using result_t = panda::optional;
20 36           panda::string compiled_verison{ZLIB_VERSION};
21 36 50         panda::string loaded_version{zlibVersion()};
22              
23 18 50         if (compiled_verison != loaded_version) {
    50          
24 0 0         panda::string err = "zlib version mismatch, loaded: " + loaded_version + ", compiled" + compiled_verison;
    0          
    0          
25 0 0         return result_t{err};
26             }
27 18           return result_t{}; // all OK
28             }
29              
30 88           void DeflateExt::request(HeaderValues& ws_extensions, const Config& cfg) {
31 3           auto iter = std::find_if(ws_extensions.begin(), ws_extensions.end(), [](const HeaderValue& cur) {
32 3           return cur.name == extension_name;
33 91 50         });
34 88 100         if (iter != ws_extensions.end()) {
35 3           return;
36             }
37              
38 173           HeaderValueParams params;
39 85 50         params.emplace(PARAM_SERVER_MAX_WINDOW_BITS, panda::to_string(cfg.server_max_window_bits));
    50          
40 85 50         params.emplace(PARAM_CLIENT_MAX_WINDOW_BITS, panda::to_string(cfg.client_max_window_bits));
    50          
41 85 100         if(cfg.server_no_context_takeover) params.emplace(PARAM_SERVER_NO_CONTEXT_TAKEOVER, "");
    50          
42 85 100         if(cfg.client_no_context_takeover) params.emplace(PARAM_CLIENT_NO_CONTEXT_TAKEOVER, "");
    50          
43              
44 170           string name{extension_name};
45 170 50         HeaderValue hv {name, std::move(params)};
46 85 50         ws_extensions.emplace_back(std::move(hv));
47             }
48              
49              
50 263           static bool get_window_bits(const string& value, std::uint8_t& bits) {
51 263 50         auto res = from_chars(value.data(), value.data() + value.size(), bits, 10);
52 263 100         return !res.ec && (bits >= 9) && (bits <= 15);
    100          
    100          
53             }
54              
55 157           DeflateExt::EffectiveConfig DeflateExt::select(const HeaderValues& values, const Config& cfg, Role role) {
56 167 100         for(auto& header: values) {
57 155 50         if (header.name == extension_name) {
    100          
58 151           EffectiveConfig ecfg(cfg, EffectiveConfig::NegotiationsResult::ERROR);
59 151           bool params_correct = true;
60 430 100         for(auto it = begin(header.params); params_correct && it != end(header.params); ++it) {
    100          
    100          
    100          
61 279           auto& param_name = it->first;
62 279           auto& param_value = it->second;
63 279 50         if (param_name == PARAM_SERVER_NO_CONTEXT_TAKEOVER) {
    100          
64 5           ecfg.flags |= EffectiveConfig::HAS_SERVER_NO_CONTEXT_TAKEOVER;
65 5           ecfg.cfg.server_no_context_takeover = true;
66             }
67 274 50         else if (param_name == PARAM_CLIENT_NO_CONTEXT_TAKEOVER) {
    100          
68 5           ecfg.flags |= EffectiveConfig::HAS_CLIENT_NO_CONTEXT_TAKEOVER;
69 5           ecfg.cfg.client_no_context_takeover = true;
70             }
71 269 50         else if (param_name == PARAM_SERVER_MAX_WINDOW_BITS) {
    100          
72 131           ecfg.flags |= EffectiveConfig::HAS_SERVER_MAX_WINDOW_BITS;
73             std::uint8_t bits;
74 131 50         params_correct = get_window_bits(param_value, bits);
75 131 100         if (params_correct) {
76 129           ecfg.cfg.server_max_window_bits = bits;
77 129 100         if (role == Role::CLIENT) {
78 61           params_correct = bits == cfg.server_max_window_bits;
79             } else {
80 131           params_correct = bits <= cfg.server_max_window_bits;
81             }
82             }
83             }
84 138 50         else if (param_name == PARAM_CLIENT_MAX_WINDOW_BITS) {
    100          
85 136           ecfg.flags |= EffectiveConfig::HAS_CLIENT_MAX_WINDOW_BITS;
86             std::uint8_t bits;
87             // value is optional
88 136 100         if (param_value) {
89 132 50         params_correct = get_window_bits(param_value, bits);
90 132           ecfg.cfg.client_max_window_bits = bits;
91 132 100         params_correct = params_correct && (
    100          
    50          
    50          
92 64           (role == Role::CLIENT) ? bits == cfg.client_max_window_bits
93 63           : bits <= cfg.client_max_window_bits
94 132           );
95             } else {
96 4           ecfg.cfg.client_max_window_bits = 15;
97             // the value must be supplied in server response, otherwise (for client) it is invalid
98 136           params_correct = role == Role::SERVER;
99             }
100 2           } else { params_correct = false; } // unknown parameter
101             }
102 151 100         if (params_correct) {
103             // first best match wins (for server & client)
104 141           ecfg.result = EffectiveConfig::NegotiationsResult::SUCCESS;
105 145           return ecfg;
106             }
107 10 100         else if (role == Role::CLIENT) {
108             // first fail (and terminate connection)
109 10           return ecfg;
110             }
111              
112             }
113             }
114 157           return EffectiveConfig(EffectiveConfig::NegotiationsResult::NOT_FOUND);
115             }
116              
117 141           DeflateExt* DeflateExt::uplift(const EffectiveConfig& ecfg, HeaderValues& extensions, Role role) {
118 282           HeaderValueParams params;
119 141 100         if (ecfg.flags & EffectiveConfig::HAS_SERVER_NO_CONTEXT_TAKEOVER) {
120 5 50         params.emplace(PARAM_SERVER_NO_CONTEXT_TAKEOVER, "");
121             }
122 141 100         if (ecfg.flags & EffectiveConfig::HAS_CLIENT_NO_CONTEXT_TAKEOVER) {
123 5 50         params.emplace(PARAM_CLIENT_NO_CONTEXT_TAKEOVER, "");
124             }
125 141 100         if (ecfg.flags & EffectiveConfig::HAS_SERVER_MAX_WINDOW_BITS) {
126 124 50         params.emplace(PARAM_SERVER_MAX_WINDOW_BITS, to_string(ecfg.cfg.server_max_window_bits));
    50          
127             }
128 141 100         if (ecfg.flags & EffectiveConfig::HAS_CLIENT_MAX_WINDOW_BITS) {
129 128 50         params.emplace(PARAM_CLIENT_MAX_WINDOW_BITS, to_string(ecfg.cfg.client_max_window_bits));
    50          
130             }
131 141 50         extensions.emplace_back(HeaderValue{string(extension_name), params});
    50          
132 282 50         return new DeflateExt(ecfg.cfg, role);
    50          
133             }
134              
135              
136 141           DeflateExt::DeflateExt(const DeflateExt::Config& cfg, Role role): effective_cfg{cfg}, message_size{0}, max_message_size{cfg.max_message_size} {
137 141 100         auto rx_window = role == Role::CLIENT ? cfg.server_max_window_bits : cfg.client_max_window_bits;
138 141 100         auto tx_window = role == Role::CLIENT ? cfg.client_max_window_bits : cfg.server_max_window_bits;
139              
140 141           rx_stream.next_in = Z_NULL;
141 141           rx_stream.avail_in = 0;
142 141           rx_stream.zalloc = Z_NULL;
143 141           rx_stream.zfree = Z_NULL;
144 141           rx_stream.opaque = Z_NULL;
145              
146             // -1 is used as "raw deflate", i.e. do not emit header/trailers
147 141           auto r = inflateInit2(&rx_stream, -1 * rx_window);
148 141 50         if (r != Z_OK) {
149 0           panda::string err = "zlib::inflateInit2 error";
150 0 0         if (rx_stream.msg) err.append(panda::string(" : ") + rx_stream.msg);
    0          
    0          
151 0           throw Error(err);
152             }
153              
154 141           tx_stream.next_in = Z_NULL;
155 141           tx_stream.avail_in = 0;
156 141           tx_stream.zalloc = Z_NULL;
157 141           tx_stream.zfree = Z_NULL;
158 141           tx_stream.opaque = Z_NULL;
159              
160             // -1 is used as "raw deflate", i.e. do not emit header/trailers
161 141           r = deflateInit2(&tx_stream, cfg.compression_level, Z_DEFLATED, -1 * tx_window , cfg.mem_level, cfg.strategy);
162 141 50         if (r != Z_OK) {
163 0           panda::string err = "zlib::deflateInit2 error";
164 0 0         if (rx_stream.msg) err.append(panda::string(" : ") + rx_stream.msg);
    0          
    0          
165 0           throw Error(err);
166             }
167              
168             reset_after_tx =
169 61 100         (role == Role::CLIENT && cfg.client_no_context_takeover)
170 202 100         || (role == Role::SERVER && cfg.server_no_context_takeover);
    100          
    100          
171             reset_after_rx =
172 61 100         (role == Role::CLIENT && cfg.server_no_context_takeover)
173 202 100         || (role == Role::SERVER && cfg.client_no_context_takeover);
    100          
    100          
174 141           }
175              
176 8           void DeflateExt::reset_tx() {
177 8 100         if (!tx_stream.next_in) return;
178 6           tx_stream.next_in = Z_NULL;
179              
180 6 50         if (deflateReset(&tx_stream) != Z_OK) {
181 0           panda::string err = panda::string("zlib::deflateEnd error ");
182 0 0         if (tx_stream.msg) {
183 0 0         err += tx_stream.msg;
184             }
185 8           throw Error(err);
186             }
187             }
188              
189 15           void DeflateExt::reset_rx() {
190 15 50         if (!rx_stream.next_in) return;
191 15           rx_stream.next_in = Z_NULL;
192              
193 15 50         if (inflateReset(&rx_stream) != Z_OK) {
194 15           panda::string err = panda::string("zlib::inflateEnd error ");
195 0 0         if(rx_stream.msg) {
196 0 0         err += rx_stream.msg;
197             }
198             }
199             }
200              
201              
202 141           DeflateExt::~DeflateExt(){
203 141 50         if (deflateEnd(&tx_stream) != Z_OK) {
204 282           panda::string err = panda::string("zlib::deflateEnd error ");
205 141 50         if (tx_stream.msg) {
206 0           err += tx_stream.msg;
207             }
208 141 50         assert(err.c_str());
209             }
210 141 50         if (inflateEnd(&rx_stream) != Z_OK) {
211 0           panda::string err = panda::string("zlib::inflateEnd error ");
212 0 0         if(rx_stream.msg) {
213 0           err += rx_stream.msg;
214             }
215 0 0         assert(err.c_str());
216             }
217 141           }
218              
219              
220 22           string& DeflateExt::compress(string& str, bool final) {
221 44 50         string in = str;
222 22           tx_stream.next_in = (Bytef*)(in.data());
223 22           tx_stream.avail_in = static_cast(in.length());
224 22 50         str = string(in.length() * COMPRESS_PREALLOCATE_RATIO); // detach and realloc for result here
    50          
225 22 50         tx_stream.next_out = reinterpret_cast(str.buf()); // buf would not detach, we just created new string and refcnt == 1
226 22 50         auto sz = str.capacity();
227 22           str.length(sz);
228 22           tx_stream.avail_out = static_cast(sz);
229              
230 1           deflate_iteration(Z_SYNC_FLUSH, [&](){
231 1           sz += reserve_for_trailer(str);
232 22 50         });
233              
234 22           sz -= tx_stream.avail_out;
235              
236 22 100         if (final) {
237 16           sz -= TRAILER_SIZE; // remove tail empty-frame 0x00 0x00 0xff 0xff for final messages only
238 16 50         if (reset_after_tx) reset_tx();
    0          
239             }
240 22           str.length(sz);
241              
242 22           return str;
243             }
244              
245 50           bool DeflateExt::uncompress(Frame& frame) {
246             bool r;
247 50 100         if (frame.payload_length() == 0) r = true;
248 47           else r = uncompress_impl(frame);
249             // reset stream in case of a) error and b) when it was last frame of message
250             // and there was setting to do not use
251 50 100         if(!r || (frame.final() && reset_after_rx)) reset_rx();
    100          
    100          
    100          
252 50 100         if (frame.final()) message_size = 0;
253 50           return r;
254             }
255              
256 9           bool DeflateExt::uncompress_check_overflow(Frame& frame, const string& acc) {
257 9           auto unpacked_frame_size = acc.capacity() - rx_stream.avail_out;
258 9           auto unpacked_message_size = message_size + unpacked_frame_size;
259 9 100         if (unpacked_message_size > max_message_size) {
260 2           frame.error = errc::max_message_size;
261 2           return false;
262             }
263 7           return true;
264             }
265              
266 86           void DeflateExt::rx_increase_buffer(string& acc) {
267 86           auto prev_sz = acc.capacity();
268 86           size_t new_sz = prev_sz * GROW_RATIO;
269 86           acc.length(prev_sz);
270 86           acc.reserve(new_sz);
271 86           rx_stream.next_out = reinterpret_cast(acc.buf() + prev_sz);
272 86           rx_stream.avail_out = static_cast(new_sz - prev_sz);
273 86           }
274              
275              
276 47           bool DeflateExt::uncompress_impl(Frame& frame) {
277             using It = decltype(frame.payload)::iterator;
278              
279 47           bool final = frame.final();
280 47           It it_in = frame.payload.begin();
281 47           It end = frame.payload.end();
282              
283 94           string acc;
284 47 50         acc.reserve(frame.payload_length() * UNCOMPRESS_PREALLOCATE_RATIO);
285              
286 47 50         rx_stream.next_out = reinterpret_cast(acc.buf());
287 47 50         rx_stream.avail_out = static_cast(acc.capacity());
288              
289 43 50         do {
290 47           string& chunk_in = *it_in;
291 47           It it_next = ++it_in;
292 47 50         if (it_next == end && final) {
    100          
    100          
293             // append empty-frame 0x00 0x00 0xff 0xff
294 31           unsigned char trailer[TRAILER_SIZE] = { 0x00, 0x00, 0xFF, 0xFF };
295 31 50         chunk_in.append(reinterpret_cast(trailer), TRAILER_SIZE);
296 31           rx_stream.avail_in += TRAILER_SIZE;
297             }
298             // std::cout << "[debug] b64 payload: " << encode::encode_base64(chunk_in) << "\n";
299 47 50         rx_stream.next_in = reinterpret_cast(chunk_in.buf());
300 47           rx_stream.avail_in = static_cast(chunk_in.length());
301 47 50         auto flush = (it_next == end) ? Z_SYNC_FLUSH : Z_NO_FLUSH;
302 47           bool has_more_output = true;
303 129 100         do {
304 133           has_more_output = !rx_stream.avail_out;
305 133 50         auto r = inflate(&rx_stream, flush);
306 133           switch (r) {
307             case Z_OK:
308 133 100         if (max_message_size && !uncompress_check_overflow(frame, acc)) return false;
    50          
    100          
    100          
309 129 100         if (!rx_stream.avail_out) {
310 86 50         rx_increase_buffer(acc);
311 86           has_more_output = true;
312             } else {
313 43           has_more_output = false;
314             }
315 129           break;
316             case Z_BUF_ERROR:
317             /* it is non-fatal error. It is unavoidable, if we unpacked the payload which
318             * fits into accumulator acc exactly, i.e. on the previous iteration it was
319             * rx_stream.avail_out == 0 and it is not known, whether there is still some
320             * output or no. If there is no output, than this error code is returned
321             */
322 0           has_more_output = false;
323 0           break;
324             default:
325 4           string err = "zlib::inflate error ";
326 2 50         if (rx_stream.msg) err += rx_stream.msg;
    50          
327 0 0         else err += to_string(r);
    0          
328 2 50         panda_log_info(err);
    0          
    50          
    0          
    0          
    0          
329 2           frame.error = errc::inflate_error;
330 2           return false;
331             }
332             } while(has_more_output);
333 43           it_in = it_next;
334             } while(it_in != end);
335              
336 43 50         acc.length(acc.capacity() - rx_stream.avail_out);
337 43           message_size += acc.length();
338              
339 43 100         if (acc) {
340 39 50         frame.payload.resize(1);
341 39 50         frame.payload[0] = std::move(acc);
342             }
343 4           else frame.payload.clear(); // remove empty string from payload if no data
344              
345 43           return true;
346             }
347              
348             }}}