2 // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 // Official repository: https://github.com/boostorg/beast
10 #ifndef BOOST_BEAST_WEBSOCKET_DETAIL_IMPL_BASE_HPP
11 #define BOOST_BEAST_WEBSOCKET_DETAIL_IMPL_BASE_HPP
13 #include <boost/beast/websocket/option.hpp>
14 #include <boost/beast/websocket/detail/frame.hpp>
15 #include <boost/beast/websocket/detail/pmd_extension.hpp>
16 #include <boost/beast/core/buffer_traits.hpp>
17 #include <boost/beast/core/role.hpp>
18 #include <boost/beast/http/empty_body.hpp>
19 #include <boost/beast/http/message.hpp>
20 #include <boost/beast/http/string_body.hpp>
21 #include <boost/beast/zlib/deflate_stream.hpp>
22 #include <boost/beast/zlib/inflate_stream.hpp>
23 #include <boost/beast/core/buffers_suffix.hpp>
24 #include <boost/beast/core/error.hpp>
25 #include <boost/beast/core/detail/clamp.hpp>
26 #include <boost/asio/buffer.hpp>
36 //------------------------------------------------------------------------------
38 template<bool deflateSupported>
42 struct impl_base<true>
44 // State information for the permessage-deflate extension
47 // `true` if current read message is compressed
50 zlib::deflate_stream zo;
51 zlib::inflate_stream zi;
54 std::unique_ptr<pmd_type> pmd_; // pmd settings or nullptr
55 permessage_deflate pmd_opts_; // local pmd options
56 detail::pmd_offer pmd_config_; // offer (client) or negotiation (server)
58 // return `true` if current message is deflated
62 return pmd_ && pmd_->rd_set;
65 // set whether current message is deflated
66 // returns `false` on protocol violation
68 rd_deflated(bool rsv1)
75 return ! rsv1; // pmd not negotiated
78 // Compress a buffer sequence
79 // Returns: `true` if more calls are needed
81 template<class ConstBufferSequence>
84 net::mutable_buffer& out,
85 buffers_suffix<ConstBufferSequence>& cb,
87 std::size_t& total_in,
90 BOOST_ASSERT(out.size() >= 6);
91 auto& zo = this->pmd_->zo;
95 zs.avail_out = out.size();
96 zs.next_out = out.data();
97 for(auto in : beast::buffers_range_ref(cb))
99 zs.avail_in = in.size();
102 zs.next_in = in.data();
103 zo.write(zs, zlib::Flush::none, ec);
106 if(ec != zlib::error::need_buffers)
108 BOOST_ASSERT(zs.avail_out == 0);
109 BOOST_ASSERT(zs.total_out == out.size());
113 if(zs.avail_out == 0)
115 BOOST_ASSERT(zs.total_out == out.size());
118 BOOST_ASSERT(zs.avail_in == 0);
120 total_in = zs.total_in;
121 cb.consume(zs.total_in);
122 if(zs.avail_out > 0 && fin)
124 auto const remain = buffer_bytes(cb);
127 // Inspired by Mark Adler
128 // https://github.com/madler/zlib/issues/149
130 // VFALCO We could do this flush twice depending
131 // on how much space is in the output.
132 zo.write(zs, zlib::Flush::block, ec);
133 BOOST_ASSERT(! ec || ec == zlib::error::need_buffers);
134 if(ec == zlib::error::need_buffers)
138 if(zs.avail_out >= 6)
140 zo.write(zs, zlib::Flush::full, ec);
142 // remove flush marker
144 out = net::buffer(out.data(), zs.total_out);
150 out = net::buffer(out.data(), zs.total_out);
155 do_context_takeover_write(role_type role)
157 if((role == role_type::client &&
158 this->pmd_config_.client_no_context_takeover) ||
159 (role == role_type::server &&
160 this->pmd_config_.server_no_context_takeover))
162 this->pmd_->zo.reset();
172 pmd_->zi.write(zs, flush, ec);
176 do_context_takeover_read(role_type role)
178 if((role == role_type::client &&
179 pmd_config_.server_no_context_takeover) ||
180 (role == role_type::server &&
181 pmd_config_.client_no_context_takeover))
187 template<class Body, class Allocator>
190 http::response<http::string_body>& res,
192 http::basic_fields<Allocator>> const& req);
196 http::response<http::string_body> const& res)
198 detail::pmd_offer offer;
199 detail::pmd_read(offer, res);
200 // VFALCO see if offer satisfies pmd_config_,
201 // return an error if not.
202 pmd_config_ = offer; // overwrite for now
205 template<class Allocator>
208 http::basic_fields<Allocator> const& h)
210 detail::pmd_read(pmd_config_, h);
214 set_option_pmd(permessage_deflate const& o)
216 if( o.server_max_window_bits > 15 ||
217 o.server_max_window_bits < 9)
218 BOOST_THROW_EXCEPTION(std::invalid_argument{
219 "invalid server_max_window_bits"});
220 if( o.client_max_window_bits > 15 ||
221 o.client_max_window_bits < 9)
222 BOOST_THROW_EXCEPTION(std::invalid_argument{
223 "invalid client_max_window_bits"});
224 if( o.compLevel < 0 ||
226 BOOST_THROW_EXCEPTION(std::invalid_argument{
227 "invalid compLevel"});
228 if( o.memLevel < 1 ||
230 BOOST_THROW_EXCEPTION(std::invalid_argument{
231 "invalid memLevel"});
236 get_option_pmd(permessage_deflate& o)
243 build_request_pmd(http::request<http::empty_body>& req)
245 if(pmd_opts_.client_enable)
247 detail::pmd_offer config;
248 config.accept = true;
249 config.server_max_window_bits =
250 pmd_opts_.server_max_window_bits;
251 config.client_max_window_bits =
252 pmd_opts_.client_max_window_bits;
253 config.server_no_context_takeover =
254 pmd_opts_.server_no_context_takeover;
255 config.client_no_context_takeover =
256 pmd_opts_.client_no_context_takeover;
257 detail::pmd_write(req, config);
262 open_pmd(role_type role)
264 if(((role == role_type::client &&
265 pmd_opts_.client_enable) ||
266 (role == role_type::server &&
267 pmd_opts_.server_enable)) &&
270 detail::pmd_normalize(pmd_config_);
271 pmd_.reset(::new pmd_type);
272 if(role == role_type::client)
275 pmd_config_.server_max_window_bits);
278 pmd_config_.client_max_window_bits,
280 zlib::Strategy::normal);
285 pmd_config_.client_max_window_bits);
288 pmd_config_.server_max_window_bits,
290 zlib::Strategy::normal);
300 bool pmd_enabled() const
302 return pmd_ != nullptr;
307 std::size_t initial_size,
309 std::uint64_t rd_remain,
310 detail::frame_header const& rd_fh) const
312 using beast::detail::clamp;
314 BOOST_ASSERT(initial_size > 0);
315 if(! pmd_ || (! rd_done && ! pmd_->rd_set))
317 // current message is uncompressed
321 // first message frame
322 result = initial_size;
327 // last message frame
328 BOOST_ASSERT(rd_remain > 0);
329 result = clamp(rd_remain);
334 initial_size, clamp(rd_remain));
336 BOOST_ASSERT(result != 0);
341 //------------------------------------------------------------------------------
344 struct impl_base<false>
346 // These stubs are for avoiding linking in the zlib
347 // code when permessage-deflate is not enabled.
356 rd_deflated(bool rsv1)
361 template<class ConstBufferSequence>
364 net::mutable_buffer&,
365 buffers_suffix<ConstBufferSequence>&,
374 do_context_takeover_write(role_type)
387 do_context_takeover_read(role_type)
391 template<class Body, class Allocator>
394 http::response<http::string_body>&,
396 http::basic_fields<Allocator>> const&);
400 http::response<http::string_body> const&)
404 template<class Allocator>
406 do_pmd_config(http::basic_fields<Allocator> const&)
411 set_option_pmd(permessage_deflate const& o)
413 if(o.client_enable || o.server_enable)
415 // Can't enable permessage-deflate
416 // when deflateSupported == false.
418 BOOST_THROW_EXCEPTION(std::invalid_argument{
419 "deflateSupported == false"});
424 get_option_pmd(permessage_deflate& o)
427 o.client_enable = false;
428 o.server_enable = false;
433 http::request<http::empty_body>&)
437 void open_pmd(role_type)
445 bool pmd_enabled() const
452 std::size_t initial_size,
454 std::uint64_t rd_remain,
455 frame_header const& rd_fh) const
457 using beast::detail::clamp;
459 BOOST_ASSERT(initial_size > 0);
460 // compression is not supported
463 // first message frame
464 result = initial_size;
468 // last message frame
469 BOOST_ASSERT(rd_remain > 0);
470 result = clamp(rd_remain);
475 initial_size, clamp(rd_remain));
477 BOOST_ASSERT(result != 0);