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