File Coverage

src/panda/protocol/websocket/DeflateExt.h
Criterion Covered Total %
statement 20 20 100.0
branch 12 18 66.6
condition n/a
subroutine n/a
pod n/a
total 32 38 84.2


line stmt bran cond sub pod time code
1             #pragma once
2             #include "Frame.h"
3             #include "HeaderValueParamsParser.h"
4             #include
5             #include
6             #include
7             #include
8              
9             namespace panda { namespace protocol { namespace websocket {
10              
11             class DeflateExt {
12             private:
13             // tail empty frame 0x00 0x00 0xff 0xff
14             static const constexpr unsigned TRAILER_SIZE = 4;
15              
16             // zlib doc:
17             // In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that avail_out is greater than six
18             // to avoid repeated flush markers due to avail_out == 0 on return.
19             static const constexpr unsigned TRAILER_RESERVED = TRAILER_SIZE + 3;
20              
21             public:
22              
23 473           struct Config {
24             bool client_no_context_takeover = false; // sent is only, when it is true; always parsed
25             bool server_no_context_takeover = false; // sent is only, when it is true; always parsed
26             std::uint8_t server_max_window_bits = 15;
27             std::uint8_t client_max_window_bits = 15;
28              
29             // copied from parser config, no direct usage
30             size_t max_message_size = 0;
31             // non-negotiatiable settings
32             int mem_level = 8;
33             int compression_level = Z_DEFAULT_COMPRESSION;
34             int strategy = Z_DEFAULT_STRATEGY;
35             size_t compression_threshold = 1410; // try to fit into TCP frame
36             };
37              
38             struct EffectiveConfig {
39             static const constexpr int HAS_CLIENT_NO_CONTEXT_TAKEOVER = 1 << 0;
40             static const constexpr int HAS_SERVER_NO_CONTEXT_TAKEOVER = 1 << 1;
41             static const constexpr int HAS_SERVER_MAX_WINDOW_BITS = 1 << 2;
42             static const constexpr int HAS_CLIENT_MAX_WINDOW_BITS = 1 << 3;
43             enum class NegotiationsResult { SUCCESS, NOT_FOUND, ERROR };
44              
45 151           EffectiveConfig(const Config& cfg_, NegotiationsResult result_): cfg{cfg_}, result{result_} {}
46 24           EffectiveConfig(NegotiationsResult result_): result{result_} {}
47              
48             explicit operator bool() const { return result == NegotiationsResult::SUCCESS; }
49              
50             Config cfg;
51             int flags = 0;
52             NegotiationsResult result;
53             };
54              
55             struct NegotiationsResult {
56             enum class Result { SUCCESS, NOT_FOUND, ERROR };
57              
58              
59             Config cfg;
60             int flags = 0;
61             Result result = Result::ERROR;
62             };
63              
64             enum class Role { CLIENT, SERVER };
65              
66             static const char* extension_name;
67              
68             static panda::optional bootstrap();
69              
70             static EffectiveConfig select(const HeaderValues& values, const Config& cfg, Role role);
71             static void request(HeaderValues& ws_extensions, const Config& cfg);
72             static DeflateExt* uplift(const EffectiveConfig& cfg, HeaderValues& extensions, Role role);
73              
74             ~DeflateExt();
75              
76             void reset_tx();
77             void reset() {
78             reset_rx();
79             reset_tx();
80             }
81             string& compress(string& str, bool final);
82              
83             template
84             It compress(It payload_begin, It payload_end, bool final) {
85             // we should not try to compress empty last piece
86             assert(!(final && (payload_begin == payload_end)));
87              
88             It it_in = payload_begin;
89             It it_out = payload_begin;
90             tx_stream.avail_out = 0;
91              
92             string* chunk_out = nullptr;
93             while(it_in != payload_end) {
94             auto it_next = it_in + 1;
95             string chunk_in = *it_in;
96             tx_stream.next_in = (Bytef*)(chunk_in.data());
97             tx_stream.avail_in = static_cast(chunk_in.length());
98             // for last fragment we either complete frame(Z_SYNC_FLUSH) or message(flush_policy())
99             // otherwise no flush it perfromed
100             auto flush = (it_next == payload_end) ? Z_SYNC_FLUSH : Z_NO_FLUSH;
101             auto avail_out = tx_stream.avail_out;
102             do {
103             deflate_iteration(flush, [&](){
104             bool reserve_something = false;
105             // the current chunk is already filled, try the next one
106             if (it_out < it_next) {
107             if (chunk_out) {
108             auto tx_out = avail_out - tx_stream.avail_out;
109             chunk_out->length(chunk_out->length() + tx_out);
110             }
111             chunk_out = &(*it_out++);
112             *chunk_out = string(chunk_out->length());
113             tx_stream.next_out = reinterpret_cast(chunk_out->buf());
114             avail_out = tx_stream.avail_out = static_cast(chunk_out->capacity());
115             reserve_something = avail_out < TRAILER_RESERVED;
116             }
117             else {
118             // there are no more chunks, resize the last/current one
119             reserve_something = true;
120             }
121             if (reserve_something) {
122             avail_out = reserve_for_trailer(*chunk_out);
123             }
124             });
125             } while(tx_stream.avail_in);
126             auto tx_out = avail_out - tx_stream.avail_out;
127             if (chunk_out) { chunk_out->length(chunk_out->length() + tx_out); }
128             it_in = it_next;
129             }
130             if(final) {
131             size_t tail_left = TRAILER_SIZE;
132             while(tail_left){
133             --it_out;
134             string& chunk = *it_out;
135             int delta = chunk.length() - tail_left;
136             if (delta >= 0) {
137             chunk.length(delta);
138             tail_left = 0;
139             }
140             else {
141             tail_left -= chunk.length();
142             chunk.length(0);
143             }
144             }
145             ++it_out;
146             if(reset_after_tx) reset_tx();
147             }
148             return it_out;
149             }
150              
151             bool uncompress(Frame& frame);
152              
153             const Config& effective_config() const { return effective_cfg; }
154              
155             private:
156              
157             template
158 22           inline void deflate_iteration(int flush, F&& drain) {
159 23 100         do {
160 23 100         if (!tx_stream.avail_in && flush == Z_NO_FLUSH) return;
    50          
161 23 50         size_t min_out_sz = flush == Z_SYNC_FLUSH ? TRAILER_RESERVED : 1;
162 23 100         if (tx_stream.avail_out < min_out_sz) drain();
163 23 50         assert(tx_stream.avail_out > 0);
164 23 50         assert(!(flush == Z_SYNC_FLUSH && tx_stream.avail_out < TRAILER_RESERVED));
    50          
165 23           auto r = deflate(&tx_stream, flush);
166             // no known cases, when user input data might lead to the error
167 23 50         assert(r >= 0);
168             (void)r;
169 23           } while (tx_stream.avail_out == 0);
170             }
171              
172 1           unsigned reserve_for_trailer(string &buff) {
173 1           auto length = buff.capacity();
174 1           buff.reserve(length + TRAILER_RESERVED);
175 1           buff.length(length);
176 1           tx_stream.next_out = reinterpret_cast(buff.buf() + length);
177 1           tx_stream.avail_out += TRAILER_RESERVED;
178 1           return TRAILER_RESERVED;
179             }
180              
181              
182             void reset_rx();
183             bool uncompress_impl(Frame& frame);
184             bool uncompress_check_overflow(Frame& frame, const string& acc);
185             void rx_increase_buffer(string& acc);
186              
187             DeflateExt(const Config& cfg, Role role);
188             Config effective_cfg;
189             size_t message_size;
190             size_t max_message_size;
191             z_stream rx_stream;
192             z_stream tx_stream;
193             bool reset_after_tx;
194             bool reset_after_rx;
195             };
196              
197             }}}