opal-up 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +209 -0
- data/README.md +81 -28
- data/bin/up_ruby +4 -0
- data/bin/up_ruby_cluster +4 -0
- data/ext/up_ext/App.h +606 -0
- data/ext/up_ext/AsyncSocket.h +355 -0
- data/ext/up_ext/AsyncSocketData.h +87 -0
- data/ext/up_ext/BloomFilter.h +83 -0
- data/ext/up_ext/ChunkedEncoding.h +236 -0
- data/ext/up_ext/ClientApp.h +36 -0
- data/ext/up_ext/HttpContext.h +502 -0
- data/ext/up_ext/HttpContextData.h +56 -0
- data/ext/up_ext/HttpErrors.h +53 -0
- data/ext/up_ext/HttpParser.h +680 -0
- data/ext/up_ext/HttpResponse.h +578 -0
- data/ext/up_ext/HttpResponseData.h +95 -0
- data/ext/up_ext/HttpRouter.h +380 -0
- data/ext/up_ext/Loop.h +204 -0
- data/ext/up_ext/LoopData.h +112 -0
- data/ext/up_ext/MoveOnlyFunction.h +377 -0
- data/ext/up_ext/PerMessageDeflate.h +315 -0
- data/ext/up_ext/ProxyParser.h +163 -0
- data/ext/up_ext/QueryParser.h +120 -0
- data/ext/up_ext/TopicTree.h +363 -0
- data/ext/up_ext/Utilities.h +66 -0
- data/ext/up_ext/WebSocket.h +381 -0
- data/ext/up_ext/WebSocketContext.h +434 -0
- data/ext/up_ext/WebSocketContextData.h +109 -0
- data/ext/up_ext/WebSocketData.h +86 -0
- data/ext/up_ext/WebSocketExtensions.h +256 -0
- data/ext/up_ext/WebSocketHandshake.h +145 -0
- data/ext/up_ext/WebSocketProtocol.h +506 -0
- data/ext/up_ext/bsd.c +767 -0
- data/ext/up_ext/bsd.h +109 -0
- data/ext/up_ext/context.c +524 -0
- data/ext/up_ext/epoll_kqueue.c +458 -0
- data/ext/up_ext/epoll_kqueue.h +67 -0
- data/ext/up_ext/extconf.rb +5 -0
- data/ext/up_ext/internal.h +224 -0
- data/ext/up_ext/libusockets.h +350 -0
- data/ext/up_ext/libuwebsockets.cpp +1374 -0
- data/ext/up_ext/libuwebsockets.h +260 -0
- data/ext/up_ext/loop.c +386 -0
- data/ext/up_ext/loop_data.h +38 -0
- data/ext/up_ext/socket.c +231 -0
- data/ext/up_ext/up_ext.c +278 -0
- data/lib/up/node/rack_env.rb +2 -2
- data/lib/up/ruby/cluster_cli.rb +10 -0
- data/lib/up/ruby/rack_cluster.rb +26 -0
- data/lib/up/ruby/rack_env.rb +97 -0
- data/lib/up/ruby/rack_server.rb +26 -0
- data/lib/up/ruby/server_cli.rb +10 -0
- data/lib/up/u_web_socket/rack_env.rb +1 -1
- data/lib/up/version.rb +1 -1
- metadata +71 -18
- data/.gitignore +0 -5
- data/Gemfile +0 -2
- data/example_rack_app/Gemfile +0 -3
- data/example_rack_app/config.ru +0 -6
- data/example_rack_app/rack_app.rb +0 -5
- data/example_roda_app/Gemfile +0 -6
- data/example_roda_app/config.ru +0 -6
- data/example_roda_app/roda_app.rb +0 -37
- data/example_sinatra_app/Gemfile +0 -6
- data/example_sinatra_app/config.ru +0 -6
- data/example_sinatra_app/sinatra_app.rb +0 -7
- data/opal-up.gemspec +0 -27
- data/up_logo.svg +0 -256
@@ -0,0 +1,377 @@
|
|
1
|
+
/*
|
2
|
+
MIT License
|
3
|
+
|
4
|
+
Copyright (c) 2020 Oleg Fatkhiev
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
8
|
+
in the Software without restriction, including without limitation the rights
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
14
|
+
copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
+
SOFTWARE.
|
23
|
+
*/
|
24
|
+
|
25
|
+
/* Sources fetched from https://github.com/ofats/any_invocable on 2021-02-19. */
|
26
|
+
|
27
|
+
#ifndef _ANY_INVOKABLE_H_
|
28
|
+
#define _ANY_INVOKABLE_H_
|
29
|
+
|
30
|
+
#include <functional>
|
31
|
+
#include <memory>
|
32
|
+
#include <type_traits>
|
33
|
+
|
34
|
+
// clang-format off
|
35
|
+
/*
|
36
|
+
namespace std {
|
37
|
+
template<class Sig> class any_invocable; // never defined
|
38
|
+
|
39
|
+
template<class R, class... ArgTypes>
|
40
|
+
class any_invocable<R(ArgTypes...) cv ref noexcept(noex)> {
|
41
|
+
public:
|
42
|
+
using result_type = R;
|
43
|
+
|
44
|
+
// SECTION.3, construct/copy/destroy
|
45
|
+
any_invocable() noexcept;
|
46
|
+
any_invocable(nullptr_t) noexcept;
|
47
|
+
any_invocable(any_invocable&&) noexcept;
|
48
|
+
template<class F> any_invocable(F&&);
|
49
|
+
|
50
|
+
template<class T, class... Args>
|
51
|
+
explicit any_invocable(in_place_type_t<T>, Args&&...);
|
52
|
+
template<class T, class U, class... Args>
|
53
|
+
explicit any_invocable(in_place_type_t<T>, initializer_list<U>, Args&&...);
|
54
|
+
|
55
|
+
any_invocable& operator=(any_invocable&&) noexcept;
|
56
|
+
any_invocable& operator=(nullptr_t) noexcept;
|
57
|
+
template<class F> any_invocable& operator=(F&&);
|
58
|
+
template<class F> any_invocable& operator=(reference_wrapper<F>) noexcept;
|
59
|
+
|
60
|
+
~any_invocable();
|
61
|
+
|
62
|
+
// SECTION.4, any_invocable modifiers
|
63
|
+
void swap(any_invocable&) noexcept;
|
64
|
+
|
65
|
+
// SECTION.5, any_invocable capacity
|
66
|
+
explicit operator bool() const noexcept;
|
67
|
+
|
68
|
+
// SECTION.6, any_invocable invocation
|
69
|
+
R operator()(ArgTypes...) cv ref noexcept(noex);
|
70
|
+
|
71
|
+
// SECTION.7, null pointer comparisons
|
72
|
+
friend bool operator==(const any_invocable&, nullptr_t) noexcept;
|
73
|
+
|
74
|
+
// SECTION.8, specialized algorithms
|
75
|
+
friend void swap(any_invocable&, any_invocable&) noexcept;
|
76
|
+
};
|
77
|
+
}
|
78
|
+
*/
|
79
|
+
// clang-format on
|
80
|
+
|
81
|
+
namespace ofats {
|
82
|
+
|
83
|
+
namespace any_detail {
|
84
|
+
|
85
|
+
using buffer = std::aligned_storage_t<sizeof(void*) * 2, alignof(void*)>;
|
86
|
+
|
87
|
+
template <class T>
|
88
|
+
inline constexpr bool is_small_object_v =
|
89
|
+
sizeof(T) <= sizeof(buffer) && alignof(buffer) % alignof(T) == 0 &&
|
90
|
+
std::is_nothrow_move_constructible_v<T>;
|
91
|
+
|
92
|
+
union storage {
|
93
|
+
void* ptr_ = nullptr;
|
94
|
+
buffer buf_;
|
95
|
+
};
|
96
|
+
|
97
|
+
enum class action { destroy, move };
|
98
|
+
|
99
|
+
template <class R, class... ArgTypes>
|
100
|
+
struct handler_traits {
|
101
|
+
template <class Derived>
|
102
|
+
struct handler_base {
|
103
|
+
static void handle(action act, storage* current, storage* other = nullptr) {
|
104
|
+
switch (act) {
|
105
|
+
case (action::destroy):
|
106
|
+
Derived::destroy(*current);
|
107
|
+
break;
|
108
|
+
case (action::move):
|
109
|
+
Derived::move(*current, *other);
|
110
|
+
break;
|
111
|
+
}
|
112
|
+
}
|
113
|
+
};
|
114
|
+
|
115
|
+
template <class T>
|
116
|
+
struct small_handler : handler_base<small_handler<T>> {
|
117
|
+
template <class... Args>
|
118
|
+
static void create(storage& s, Args&&... args) {
|
119
|
+
new (static_cast<void*>(&s.buf_)) T(std::forward<Args>(args)...);
|
120
|
+
}
|
121
|
+
|
122
|
+
static void destroy(storage& s) noexcept {
|
123
|
+
T& value = *static_cast<T*>(static_cast<void*>(&s.buf_));
|
124
|
+
value.~T();
|
125
|
+
}
|
126
|
+
|
127
|
+
static void move(storage& dst, storage& src) noexcept {
|
128
|
+
create(dst, std::move(*static_cast<T*>(static_cast<void*>(&src.buf_))));
|
129
|
+
destroy(src);
|
130
|
+
}
|
131
|
+
|
132
|
+
static R call(storage& s, ArgTypes... args) {
|
133
|
+
return std::invoke(*static_cast<T*>(static_cast<void*>(&s.buf_)),
|
134
|
+
std::forward<ArgTypes>(args)...);
|
135
|
+
}
|
136
|
+
};
|
137
|
+
|
138
|
+
template <class T>
|
139
|
+
struct large_handler : handler_base<large_handler<T>> {
|
140
|
+
template <class... Args>
|
141
|
+
static void create(storage& s, Args&&... args) {
|
142
|
+
s.ptr_ = new T(std::forward<Args>(args)...);
|
143
|
+
}
|
144
|
+
|
145
|
+
static void destroy(storage& s) noexcept { delete static_cast<T*>(s.ptr_); }
|
146
|
+
|
147
|
+
static void move(storage& dst, storage& src) noexcept {
|
148
|
+
dst.ptr_ = src.ptr_;
|
149
|
+
}
|
150
|
+
|
151
|
+
static R call(storage& s, ArgTypes... args) {
|
152
|
+
return std::invoke(*static_cast<T*>(s.ptr_),
|
153
|
+
std::forward<ArgTypes>(args)...);
|
154
|
+
}
|
155
|
+
};
|
156
|
+
|
157
|
+
template <class T>
|
158
|
+
using handler = std::conditional_t<is_small_object_v<T>, small_handler<T>,
|
159
|
+
large_handler<T>>;
|
160
|
+
};
|
161
|
+
|
162
|
+
template <class T>
|
163
|
+
struct is_in_place_type : std::false_type {};
|
164
|
+
|
165
|
+
template <class T>
|
166
|
+
struct is_in_place_type<std::in_place_type_t<T>> : std::true_type {};
|
167
|
+
|
168
|
+
template <class T>
|
169
|
+
inline constexpr auto is_in_place_type_v = is_in_place_type<T>::value;
|
170
|
+
|
171
|
+
template <class R, bool is_noexcept, class... ArgTypes>
|
172
|
+
class any_invocable_impl {
|
173
|
+
template <class T>
|
174
|
+
using handler =
|
175
|
+
typename any_detail::handler_traits<R, ArgTypes...>::template handler<T>;
|
176
|
+
|
177
|
+
using storage = any_detail::storage;
|
178
|
+
using action = any_detail::action;
|
179
|
+
using handle_func = void (*)(any_detail::action, any_detail::storage*,
|
180
|
+
any_detail::storage*);
|
181
|
+
using call_func = R (*)(any_detail::storage&, ArgTypes...);
|
182
|
+
|
183
|
+
public:
|
184
|
+
using result_type = R;
|
185
|
+
|
186
|
+
any_invocable_impl() noexcept = default;
|
187
|
+
any_invocable_impl(std::nullptr_t) noexcept {}
|
188
|
+
any_invocable_impl(any_invocable_impl&& rhs) noexcept {
|
189
|
+
if (rhs.handle_) {
|
190
|
+
handle_ = rhs.handle_;
|
191
|
+
handle_(action::move, &storage_, &rhs.storage_);
|
192
|
+
call_ = rhs.call_;
|
193
|
+
rhs.handle_ = nullptr;
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
any_invocable_impl& operator=(any_invocable_impl&& rhs) noexcept {
|
198
|
+
any_invocable_impl{std::move(rhs)}.swap(*this);
|
199
|
+
return *this;
|
200
|
+
}
|
201
|
+
any_invocable_impl& operator=(std::nullptr_t) noexcept {
|
202
|
+
destroy();
|
203
|
+
return *this;
|
204
|
+
}
|
205
|
+
|
206
|
+
~any_invocable_impl() { destroy(); }
|
207
|
+
|
208
|
+
void swap(any_invocable_impl& rhs) noexcept {
|
209
|
+
if (handle_) {
|
210
|
+
if (rhs.handle_) {
|
211
|
+
storage tmp;
|
212
|
+
handle_(action::move, &tmp, &storage_);
|
213
|
+
rhs.handle_(action::move, &storage_, &rhs.storage_);
|
214
|
+
handle_(action::move, &rhs.storage_, &tmp);
|
215
|
+
std::swap(handle_, rhs.handle_);
|
216
|
+
std::swap(call_, rhs.call_);
|
217
|
+
} else {
|
218
|
+
rhs.swap(*this);
|
219
|
+
}
|
220
|
+
} else if (rhs.handle_) {
|
221
|
+
rhs.handle_(action::move, &storage_, &rhs.storage_);
|
222
|
+
handle_ = rhs.handle_;
|
223
|
+
call_ = rhs.call_;
|
224
|
+
rhs.handle_ = nullptr;
|
225
|
+
}
|
226
|
+
}
|
227
|
+
|
228
|
+
explicit operator bool() const noexcept { return handle_ != nullptr; }
|
229
|
+
|
230
|
+
protected:
|
231
|
+
template <class F, class... Args>
|
232
|
+
void create(Args&&... args) {
|
233
|
+
using hdl = handler<F>;
|
234
|
+
hdl::create(storage_, std::forward<Args>(args)...);
|
235
|
+
handle_ = &hdl::handle;
|
236
|
+
call_ = &hdl::call;
|
237
|
+
}
|
238
|
+
|
239
|
+
void destroy() noexcept {
|
240
|
+
if (handle_) {
|
241
|
+
handle_(action::destroy, &storage_, nullptr);
|
242
|
+
handle_ = nullptr;
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
R call(ArgTypes... args) noexcept(is_noexcept) {
|
247
|
+
return call_(storage_, std::forward<ArgTypes>(args)...);
|
248
|
+
}
|
249
|
+
|
250
|
+
friend bool operator==(const any_invocable_impl& f, std::nullptr_t) noexcept {
|
251
|
+
return !f;
|
252
|
+
}
|
253
|
+
friend bool operator==(std::nullptr_t, const any_invocable_impl& f) noexcept {
|
254
|
+
return !f;
|
255
|
+
}
|
256
|
+
friend bool operator!=(const any_invocable_impl& f, std::nullptr_t) noexcept {
|
257
|
+
return static_cast<bool>(f);
|
258
|
+
}
|
259
|
+
friend bool operator!=(std::nullptr_t, const any_invocable_impl& f) noexcept {
|
260
|
+
return static_cast<bool>(f);
|
261
|
+
}
|
262
|
+
|
263
|
+
friend void swap(any_invocable_impl& lhs, any_invocable_impl& rhs) noexcept {
|
264
|
+
lhs.swap(rhs);
|
265
|
+
}
|
266
|
+
|
267
|
+
private:
|
268
|
+
storage storage_;
|
269
|
+
handle_func handle_ = nullptr;
|
270
|
+
call_func call_;
|
271
|
+
};
|
272
|
+
|
273
|
+
template <class T>
|
274
|
+
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
|
275
|
+
|
276
|
+
template <class AI, class F, bool noex, class R, class FCall, class... ArgTypes>
|
277
|
+
using can_convert = std::conjunction<
|
278
|
+
std::negation<std::is_same<remove_cvref_t<F>, AI>>,
|
279
|
+
std::negation<any_detail::is_in_place_type<remove_cvref_t<F>>>,
|
280
|
+
std::is_invocable_r<R, FCall, ArgTypes...>,
|
281
|
+
std::bool_constant<(!noex ||
|
282
|
+
std::is_nothrow_invocable_r_v<R, FCall, ArgTypes...>)>,
|
283
|
+
std::is_constructible<std::decay_t<F>, F>>;
|
284
|
+
|
285
|
+
} // namespace any_detail
|
286
|
+
|
287
|
+
template <class Signature>
|
288
|
+
class any_invocable;
|
289
|
+
|
290
|
+
#define __OFATS_ANY_INVOCABLE(cv, ref, noex, inv_quals) \
|
291
|
+
template <class R, class... ArgTypes> \
|
292
|
+
class any_invocable<R(ArgTypes...) cv ref noexcept(noex)> \
|
293
|
+
: public any_detail::any_invocable_impl<R, noex, ArgTypes...> { \
|
294
|
+
using base_type = any_detail::any_invocable_impl<R, noex, ArgTypes...>; \
|
295
|
+
\
|
296
|
+
public: \
|
297
|
+
using base_type::base_type; \
|
298
|
+
\
|
299
|
+
template < \
|
300
|
+
class F, \
|
301
|
+
class = std::enable_if_t<any_detail::can_convert< \
|
302
|
+
any_invocable, F, noex, R, F inv_quals, ArgTypes...>::value>> \
|
303
|
+
any_invocable(F&& f) { \
|
304
|
+
base_type::template create<std::decay_t<F>>(std::forward<F>(f)); \
|
305
|
+
} \
|
306
|
+
\
|
307
|
+
template <class T, class... Args, class VT = std::decay_t<T>, \
|
308
|
+
class = std::enable_if_t< \
|
309
|
+
std::is_move_constructible_v<VT> && \
|
310
|
+
std::is_constructible_v<VT, Args...> && \
|
311
|
+
std::is_invocable_r_v<R, VT inv_quals, ArgTypes...> && \
|
312
|
+
(!noex || std::is_nothrow_invocable_r_v<R, VT inv_quals, \
|
313
|
+
ArgTypes...>)>> \
|
314
|
+
explicit any_invocable(std::in_place_type_t<T>, Args&&... args) { \
|
315
|
+
base_type::template create<VT>(std::forward<Args>(args)...); \
|
316
|
+
} \
|
317
|
+
\
|
318
|
+
template < \
|
319
|
+
class T, class U, class... Args, class VT = std::decay_t<T>, \
|
320
|
+
class = std::enable_if_t< \
|
321
|
+
std::is_move_constructible_v<VT> && \
|
322
|
+
std::is_constructible_v<VT, std::initializer_list<U>&, Args...> && \
|
323
|
+
std::is_invocable_r_v<R, VT inv_quals, ArgTypes...> && \
|
324
|
+
(!noex || \
|
325
|
+
std::is_nothrow_invocable_r_v<R, VT inv_quals, ArgTypes...>)>> \
|
326
|
+
explicit any_invocable(std::in_place_type_t<T>, \
|
327
|
+
std::initializer_list<U> il, Args&&... args) { \
|
328
|
+
base_type::template create<VT>(il, std::forward<Args>(args)...); \
|
329
|
+
} \
|
330
|
+
\
|
331
|
+
template <class F, class FDec = std::decay_t<F>> \
|
332
|
+
std::enable_if_t<!std::is_same_v<FDec, any_invocable> && \
|
333
|
+
std::is_move_constructible_v<FDec>, \
|
334
|
+
any_invocable&> \
|
335
|
+
operator=(F&& f) { \
|
336
|
+
any_invocable{std::forward<F>(f)}.swap(*this); \
|
337
|
+
return *this; \
|
338
|
+
} \
|
339
|
+
template <class F> \
|
340
|
+
any_invocable& operator=(std::reference_wrapper<F> f) { \
|
341
|
+
any_invocable{f}.swap(*this); \
|
342
|
+
return *this; \
|
343
|
+
} \
|
344
|
+
\
|
345
|
+
R operator()(ArgTypes... args) cv ref noexcept(noex) { \
|
346
|
+
return base_type::call(std::forward<ArgTypes>(args)...); \
|
347
|
+
} \
|
348
|
+
};
|
349
|
+
|
350
|
+
// cv -> {`empty`, const}
|
351
|
+
// ref -> {`empty`, &, &&}
|
352
|
+
// noex -> {true, false}
|
353
|
+
// inv_quals -> (is_empty(ref) ? & : ref)
|
354
|
+
__OFATS_ANY_INVOCABLE(, , false, &) // 000
|
355
|
+
__OFATS_ANY_INVOCABLE(, , true, &) // 001
|
356
|
+
__OFATS_ANY_INVOCABLE(, &, false, &) // 010
|
357
|
+
__OFATS_ANY_INVOCABLE(, &, true, &) // 011
|
358
|
+
__OFATS_ANY_INVOCABLE(, &&, false, &&) // 020
|
359
|
+
__OFATS_ANY_INVOCABLE(, &&, true, &&) // 021
|
360
|
+
__OFATS_ANY_INVOCABLE(const, , false, const&) // 100
|
361
|
+
__OFATS_ANY_INVOCABLE(const, , true, const&) // 101
|
362
|
+
__OFATS_ANY_INVOCABLE(const, &, false, const&) // 110
|
363
|
+
__OFATS_ANY_INVOCABLE(const, &, true, const&) // 111
|
364
|
+
__OFATS_ANY_INVOCABLE(const, &&, false, const&&) // 120
|
365
|
+
__OFATS_ANY_INVOCABLE(const, &&, true, const&&) // 121
|
366
|
+
|
367
|
+
#undef __OFATS_ANY_INVOCABLE
|
368
|
+
|
369
|
+
} // namespace ofats
|
370
|
+
|
371
|
+
/* We, uWebSockets define our own type */
|
372
|
+
namespace uWS {
|
373
|
+
template <class T>
|
374
|
+
using MoveOnlyFunction = ofats::any_invocable<T>;
|
375
|
+
}
|
376
|
+
|
377
|
+
#endif // _ANY_INVOKABLE_H_
|
@@ -0,0 +1,315 @@
|
|
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
|
+
std::optional<std::string_view> inflate(ZlibContext * /*zlibContext*/, std::string_view compressed, size_t maxPayloadLength, bool /*reset*/) {
|
84
|
+
return compressed.substr(0, std::min(maxPayloadLength, compressed.length()));
|
85
|
+
}
|
86
|
+
InflationStream(CompressOptions /*compressOptions*/) {
|
87
|
+
}
|
88
|
+
};
|
89
|
+
struct DeflationStream {
|
90
|
+
std::string_view deflate(ZlibContext * /*zlibContext*/, std::string_view raw, bool /*reset*/) {
|
91
|
+
return raw;
|
92
|
+
}
|
93
|
+
DeflationStream(CompressOptions /*compressOptions*/) {
|
94
|
+
}
|
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
|
+
#ifdef UWS_USE_LIBDEFLATE
|
154
|
+
/* Run a fast path in case of shared_compressor */
|
155
|
+
if (reset) {
|
156
|
+
size_t written = 0;
|
157
|
+
static unsigned char buf[1024 + 1];
|
158
|
+
|
159
|
+
written = libdeflate_deflate_compress(zlibContext->compressor, raw.data(), raw.length(), buf, 1024);
|
160
|
+
|
161
|
+
if (written) {
|
162
|
+
memcpy(&buf[written], "\x00", 1);
|
163
|
+
return std::string_view((char *) buf, written + 1);
|
164
|
+
}
|
165
|
+
}
|
166
|
+
#endif
|
167
|
+
|
168
|
+
/* Odd place to clear this one, fix */
|
169
|
+
zlibContext->dynamicDeflationBuffer.clear();
|
170
|
+
|
171
|
+
deflationStream.next_in = (Bytef *) raw.data();
|
172
|
+
deflationStream.avail_in = (unsigned int) raw.length();
|
173
|
+
|
174
|
+
/* This buffer size has to be at least 6 bytes for Z_SYNC_FLUSH to work */
|
175
|
+
const int DEFLATE_OUTPUT_CHUNK = LARGE_BUFFER_SIZE;
|
176
|
+
|
177
|
+
int err;
|
178
|
+
do {
|
179
|
+
deflationStream.next_out = (Bytef *) zlibContext->deflationBuffer;
|
180
|
+
deflationStream.avail_out = DEFLATE_OUTPUT_CHUNK;
|
181
|
+
|
182
|
+
err = ::deflate(&deflationStream, Z_SYNC_FLUSH);
|
183
|
+
if (Z_OK == err && deflationStream.avail_out == 0) {
|
184
|
+
zlibContext->dynamicDeflationBuffer.append(zlibContext->deflationBuffer, DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out);
|
185
|
+
continue;
|
186
|
+
} else {
|
187
|
+
break;
|
188
|
+
}
|
189
|
+
} while (true);
|
190
|
+
|
191
|
+
/* This must not change avail_out */
|
192
|
+
if (reset) {
|
193
|
+
deflateReset(&deflationStream);
|
194
|
+
}
|
195
|
+
|
196
|
+
if (zlibContext->dynamicDeflationBuffer.length()) {
|
197
|
+
zlibContext->dynamicDeflationBuffer.append(zlibContext->deflationBuffer, DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out);
|
198
|
+
|
199
|
+
return std::string_view((char *) zlibContext->dynamicDeflationBuffer.data(), zlibContext->dynamicDeflationBuffer.length() - 4);
|
200
|
+
}
|
201
|
+
|
202
|
+
/* Note: We will get an interger overflow resulting in heap buffer overflow if Z_BUF_ERROR is returned
|
203
|
+
* from passing 0 as avail_in. Therefore we must not deflate an empty string */
|
204
|
+
return {
|
205
|
+
zlibContext->deflationBuffer,
|
206
|
+
DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out - 4
|
207
|
+
};
|
208
|
+
}
|
209
|
+
|
210
|
+
~DeflationStream() {
|
211
|
+
deflateEnd(&deflationStream);
|
212
|
+
}
|
213
|
+
};
|
214
|
+
|
215
|
+
struct InflationStream {
|
216
|
+
z_stream inflationStream = {};
|
217
|
+
|
218
|
+
InflationStream(CompressOptions compressOptions) {
|
219
|
+
/* Inflation windowBits are the top 8 bits of the 16 bit compressOptions */
|
220
|
+
inflateInit2(&inflationStream, -(compressOptions >> 8));
|
221
|
+
}
|
222
|
+
|
223
|
+
~InflationStream() {
|
224
|
+
inflateEnd(&inflationStream);
|
225
|
+
}
|
226
|
+
|
227
|
+
/* Zero length inflates are possible and valid */
|
228
|
+
std::optional<std::string_view> inflate(ZlibContext *zlibContext, std::string_view compressed, size_t maxPayloadLength, bool reset) {
|
229
|
+
|
230
|
+
#ifdef UWS_USE_LIBDEFLATE
|
231
|
+
/* Try fast path first */
|
232
|
+
size_t written = 0;
|
233
|
+
static char buf[1024];
|
234
|
+
|
235
|
+
/* We have to pad 9 bytes and restore those bytes when done since 9 is more than 6 of next WebSocket message */
|
236
|
+
char tmp[9];
|
237
|
+
memcpy(tmp, (char *) compressed.data() + compressed.length(), 9);
|
238
|
+
memcpy((char *) compressed.data() + compressed.length(), "\x00\x00\xff\xff\x01\x00\x00\xff\xff", 9);
|
239
|
+
libdeflate_result res = libdeflate_deflate_decompress(zlibContext->decompressor, compressed.data(), compressed.length() + 9, buf, 1024, &written);
|
240
|
+
memcpy((char *) compressed.data() + compressed.length(), tmp, 9);
|
241
|
+
|
242
|
+
if (res == 0) {
|
243
|
+
/* Fast path wins */
|
244
|
+
return std::string_view(buf, written);
|
245
|
+
}
|
246
|
+
#endif
|
247
|
+
|
248
|
+
/* Save off the bytes we're about to overwrite */
|
249
|
+
char* tailLocation = (char*)compressed.data() + compressed.length();
|
250
|
+
char preTailBytes[4];
|
251
|
+
memcpy(preTailBytes, tailLocation, 4);
|
252
|
+
|
253
|
+
/* Append tail to chunk */
|
254
|
+
unsigned char tail[4] = {0x00, 0x00, 0xff, 0xff};
|
255
|
+
memcpy(tailLocation, tail, 4);
|
256
|
+
compressed = {compressed.data(), compressed.length() + 4};
|
257
|
+
|
258
|
+
/* We clear this one here, could be done better */
|
259
|
+
zlibContext->dynamicInflationBuffer.clear();
|
260
|
+
|
261
|
+
inflationStream.next_in = (Bytef *) compressed.data();
|
262
|
+
inflationStream.avail_in = (unsigned int) compressed.length();
|
263
|
+
|
264
|
+
int err;
|
265
|
+
do {
|
266
|
+
inflationStream.next_out = (Bytef *) zlibContext->inflationBuffer;
|
267
|
+
inflationStream.avail_out = LARGE_BUFFER_SIZE;
|
268
|
+
|
269
|
+
err = ::inflate(&inflationStream, Z_SYNC_FLUSH);
|
270
|
+
if (err == Z_OK && inflationStream.avail_out) {
|
271
|
+
break;
|
272
|
+
}
|
273
|
+
|
274
|
+
zlibContext->dynamicInflationBuffer.append(zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out);
|
275
|
+
|
276
|
+
|
277
|
+
} while (inflationStream.avail_out == 0 && zlibContext->dynamicInflationBuffer.length() <= maxPayloadLength);
|
278
|
+
|
279
|
+
if (reset) {
|
280
|
+
inflateReset(&inflationStream);
|
281
|
+
}
|
282
|
+
|
283
|
+
/* Restore the bytes we used for the tail */
|
284
|
+
memcpy(tailLocation, preTailBytes, 4);
|
285
|
+
|
286
|
+
if ((err != Z_BUF_ERROR && err != Z_OK) || zlibContext->dynamicInflationBuffer.length() > maxPayloadLength) {
|
287
|
+
return std::nullopt;
|
288
|
+
}
|
289
|
+
|
290
|
+
if (zlibContext->dynamicInflationBuffer.length()) {
|
291
|
+
zlibContext->dynamicInflationBuffer.append(zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out);
|
292
|
+
|
293
|
+
/* Let's be strict about the max size */
|
294
|
+
if (zlibContext->dynamicInflationBuffer.length() > maxPayloadLength) {
|
295
|
+
return std::nullopt;
|
296
|
+
}
|
297
|
+
|
298
|
+
return std::string_view(zlibContext->dynamicInflationBuffer.data(), zlibContext->dynamicInflationBuffer.length());
|
299
|
+
}
|
300
|
+
|
301
|
+
/* Let's be strict about the max size */
|
302
|
+
if ((LARGE_BUFFER_SIZE - inflationStream.avail_out) > maxPayloadLength) {
|
303
|
+
return std::nullopt;
|
304
|
+
}
|
305
|
+
|
306
|
+
return std::string_view(zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out);
|
307
|
+
}
|
308
|
+
|
309
|
+
};
|
310
|
+
|
311
|
+
#endif
|
312
|
+
|
313
|
+
}
|
314
|
+
|
315
|
+
#endif // UWS_PERMESSAGEDEFLATE_H
|