• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
3 //
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)
6 //
7 // Official repository: https://github.com/boostorg/beast
8 //
9 
10 #ifndef BOOST_BEAST_WEBSOCKET_DETAIL_IMPL_BASE_HPP
11 #define BOOST_BEAST_WEBSOCKET_DETAIL_IMPL_BASE_HPP
12 
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>
27 #include <cstdint>
28 #include <memory>
29 #include <stdexcept>
30 
31 namespace boost {
32 namespace beast {
33 namespace websocket {
34 namespace detail {
35 
36 //------------------------------------------------------------------------------
37 
38 template<bool deflateSupported>
39 struct impl_base;
40 
41 template<>
42 struct impl_base<true>
43 {
44     // State information for the permessage-deflate extension
45     struct pmd_type
46     {
47         // `true` if current read message is compressed
48         bool rd_set = false;
49 
50         zlib::deflate_stream zo;
51         zlib::inflate_stream zi;
52     };
53 
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)
57 
58     // return `true` if current message is deflated
59     bool
rd_deflatedboost::beast::websocket::detail::impl_base60     rd_deflated() const
61     {
62         return pmd_ && pmd_->rd_set;
63     }
64 
65     // set whether current message is deflated
66     // returns `false` on protocol violation
67     bool
rd_deflatedboost::beast::websocket::detail::impl_base68     rd_deflated(bool rsv1)
69     {
70         if(pmd_)
71         {
72             pmd_->rd_set = rsv1;
73             return true;
74         }
75         return ! rsv1; // pmd not negotiated
76     }
77 
78     // Compress a buffer sequence
79     // Returns: `true` if more calls are needed
80     //
81     template<class ConstBufferSequence>
82     bool
deflateboost::beast::websocket::detail::impl_base83     deflate(
84         net::mutable_buffer& out,
85         buffers_suffix<ConstBufferSequence>& cb,
86         bool fin,
87         std::size_t& total_in,
88         error_code& ec)
89     {
90         BOOST_ASSERT(out.size() >= 6);
91         auto& zo = this->pmd_->zo;
92         zlib::z_params zs;
93         zs.avail_in = 0;
94         zs.next_in = nullptr;
95         zs.avail_out = out.size();
96         zs.next_out = out.data();
97         for(auto in : beast::buffers_range_ref(cb))
98         {
99             zs.avail_in = in.size();
100             if(zs.avail_in == 0)
101                 continue;
102             zs.next_in = in.data();
103             zo.write(zs, zlib::Flush::none, ec);
104             if(ec)
105             {
106                 if(ec != zlib::error::need_buffers)
107                     return false;
108                 BOOST_ASSERT(zs.avail_out == 0);
109                 BOOST_ASSERT(zs.total_out == out.size());
110                 ec = {};
111                 break;
112             }
113             if(zs.avail_out == 0)
114             {
115                 BOOST_ASSERT(zs.total_out == out.size());
116                 break;
117             }
118             BOOST_ASSERT(zs.avail_in == 0);
119         }
120         total_in = zs.total_in;
121         cb.consume(zs.total_in);
122         if(zs.avail_out > 0 && fin)
123         {
124             auto const remain = buffer_bytes(cb);
125             if(remain == 0)
126             {
127                 // Inspired by Mark Adler
128                 // https://github.com/madler/zlib/issues/149
129                 //
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)
135                     ec = {};
136                 if(ec)
137                     return false;
138                 if(zs.avail_out >= 6)
139                 {
140                     zo.write(zs, zlib::Flush::full, ec);
141                     BOOST_ASSERT(! ec);
142                     // remove flush marker
143                     zs.total_out -= 4;
144                     out = net::buffer(out.data(), zs.total_out);
145                     return false;
146                 }
147             }
148         }
149         ec = {};
150         out = net::buffer(out.data(), zs.total_out);
151         return true;
152     }
153 
154     void
do_context_takeover_writeboost::beast::websocket::detail::impl_base155     do_context_takeover_write(role_type role)
156     {
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))
161         {
162             this->pmd_->zo.reset();
163         }
164     }
165 
166     void
inflateboost::beast::websocket::detail::impl_base167     inflate(
168         zlib::z_params& zs,
169         zlib::Flush flush,
170         error_code& ec)
171     {
172         pmd_->zi.write(zs, flush, ec);
173     }
174 
175     void
do_context_takeover_readboost::beast::websocket::detail::impl_base176     do_context_takeover_read(role_type role)
177     {
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))
182         {
183             pmd_->zi.clear();
184         }
185     }
186 
187     template<class Body, class Allocator>
188     void
189     build_response_pmd(
190         http::response<http::string_body>& res,
191         http::request<Body,
192             http::basic_fields<Allocator>> const& req);
193 
194     void
on_response_pmdboost::beast::websocket::detail::impl_base195     on_response_pmd(
196         http::response<http::string_body> const& res)
197     {
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
203     }
204 
205     template<class Allocator>
206     void
do_pmd_configboost::beast::websocket::detail::impl_base207     do_pmd_config(
208         http::basic_fields<Allocator> const& h)
209     {
210         detail::pmd_read(pmd_config_, h);
211     }
212 
213     void
set_option_pmdboost::beast::websocket::detail::impl_base214     set_option_pmd(permessage_deflate const& o)
215     {
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 ||
225             o.compLevel > 9)
226             BOOST_THROW_EXCEPTION(std::invalid_argument{
227                 "invalid compLevel"});
228         if( o.memLevel < 1 ||
229             o.memLevel > 9)
230             BOOST_THROW_EXCEPTION(std::invalid_argument{
231                 "invalid memLevel"});
232         pmd_opts_ = o;
233     }
234 
235     void
get_option_pmdboost::beast::websocket::detail::impl_base236     get_option_pmd(permessage_deflate& o)
237     {
238         o = pmd_opts_;
239     }
240 
241 
242     void
build_request_pmdboost::beast::websocket::detail::impl_base243     build_request_pmd(http::request<http::empty_body>& req)
244     {
245         if(pmd_opts_.client_enable)
246         {
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);
258         }
259     }
260 
261     void
open_pmdboost::beast::websocket::detail::impl_base262     open_pmd(role_type role)
263     {
264         if(((role == role_type::client &&
265                 pmd_opts_.client_enable) ||
266             (role == role_type::server &&
267                 pmd_opts_.server_enable)) &&
268             pmd_config_.accept)
269         {
270             detail::pmd_normalize(pmd_config_);
271             pmd_.reset(::new pmd_type);
272             if(role == role_type::client)
273             {
274                 pmd_->zi.reset(
275                     pmd_config_.server_max_window_bits);
276                 pmd_->zo.reset(
277                     pmd_opts_.compLevel,
278                     pmd_config_.client_max_window_bits,
279                     pmd_opts_.memLevel,
280                     zlib::Strategy::normal);
281             }
282             else
283             {
284                 pmd_->zi.reset(
285                     pmd_config_.client_max_window_bits);
286                 pmd_->zo.reset(
287                     pmd_opts_.compLevel,
288                     pmd_config_.server_max_window_bits,
289                     pmd_opts_.memLevel,
290                     zlib::Strategy::normal);
291             }
292         }
293     }
294 
close_pmdboost::beast::websocket::detail::impl_base295     void close_pmd()
296     {
297         pmd_.reset();
298     }
299 
pmd_enabledboost::beast::websocket::detail::impl_base300     bool pmd_enabled() const
301     {
302         return pmd_ != nullptr;
303     }
304 
305     std::size_t
read_size_hint_pmdboost::beast::websocket::detail::impl_base306     read_size_hint_pmd(
307         std::size_t initial_size,
308         bool rd_done,
309         std::uint64_t rd_remain,
310         detail::frame_header const& rd_fh) const
311     {
312         using beast::detail::clamp;
313         std::size_t result;
314         BOOST_ASSERT(initial_size > 0);
315         if(! pmd_ || (! rd_done && ! pmd_->rd_set))
316         {
317             // current message is uncompressed
318 
319             if(rd_done)
320             {
321                 // first message frame
322                 result = initial_size;
323                 goto done;
324             }
325             else if(rd_fh.fin)
326             {
327                 // last message frame
328                 BOOST_ASSERT(rd_remain > 0);
329                 result = clamp(rd_remain);
330                 goto done;
331             }
332         }
333         result = (std::max)(
334             initial_size, clamp(rd_remain));
335     done:
336         BOOST_ASSERT(result != 0);
337         return result;
338     }
339 };
340 
341 //------------------------------------------------------------------------------
342 
343 template<>
344 struct impl_base<false>
345 {
346     // These stubs are for avoiding linking in the zlib
347     // code when permessage-deflate is not enabled.
348 
349     bool
rd_deflatedboost::beast::websocket::detail::impl_base350     rd_deflated() const
351     {
352         return false;
353     }
354 
355     bool
rd_deflatedboost::beast::websocket::detail::impl_base356     rd_deflated(bool rsv1)
357     {
358         return ! rsv1;
359     }
360 
361     template<class ConstBufferSequence>
362     bool
deflateboost::beast::websocket::detail::impl_base363     deflate(
364         net::mutable_buffer&,
365         buffers_suffix<ConstBufferSequence>&,
366         bool,
367         std::size_t&,
368         error_code&)
369     {
370         return false;
371     }
372 
373     void
do_context_takeover_writeboost::beast::websocket::detail::impl_base374     do_context_takeover_write(role_type)
375     {
376     }
377 
378     void
inflateboost::beast::websocket::detail::impl_base379     inflate(
380         zlib::z_params&,
381         zlib::Flush,
382         error_code&)
383     {
384     }
385 
386     void
do_context_takeover_readboost::beast::websocket::detail::impl_base387     do_context_takeover_read(role_type)
388     {
389     }
390 
391     template<class Body, class Allocator>
392     void
393     build_response_pmd(
394         http::response<http::string_body>&,
395         http::request<Body,
396             http::basic_fields<Allocator>> const&);
397 
398     void
on_response_pmdboost::beast::websocket::detail::impl_base399     on_response_pmd(
400         http::response<http::string_body> const&)
401     {
402     }
403 
404     template<class Allocator>
405     void
do_pmd_configboost::beast::websocket::detail::impl_base406     do_pmd_config(http::basic_fields<Allocator> const&)
407     {
408     }
409 
410     void
set_option_pmdboost::beast::websocket::detail::impl_base411     set_option_pmd(permessage_deflate const& o)
412     {
413         if(o.client_enable || o.server_enable)
414         {
415             // Can't enable permessage-deflate
416             // when deflateSupported == false.
417             //
418             BOOST_THROW_EXCEPTION(std::invalid_argument{
419                 "deflateSupported == false"});
420         }
421     }
422 
423     void
get_option_pmdboost::beast::websocket::detail::impl_base424     get_option_pmd(permessage_deflate& o)
425     {
426         o = {};
427         o.client_enable = false;
428         o.server_enable = false;
429     }
430 
431     void
build_request_pmdboost::beast::websocket::detail::impl_base432     build_request_pmd(
433         http::request<http::empty_body>&)
434     {
435     }
436 
open_pmdboost::beast::websocket::detail::impl_base437     void open_pmd(role_type)
438     {
439     }
440 
close_pmdboost::beast::websocket::detail::impl_base441     void close_pmd()
442     {
443     }
444 
pmd_enabledboost::beast::websocket::detail::impl_base445     bool pmd_enabled() const
446     {
447         return false;
448     }
449 
450     std::size_t
read_size_hint_pmdboost::beast::websocket::detail::impl_base451     read_size_hint_pmd(
452         std::size_t initial_size,
453         bool rd_done,
454         std::uint64_t rd_remain,
455         frame_header const& rd_fh) const
456     {
457         using beast::detail::clamp;
458         std::size_t result;
459         BOOST_ASSERT(initial_size > 0);
460         // compression is not supported
461         if(rd_done)
462         {
463             // first message frame
464             result = initial_size;
465         }
466         else if(rd_fh.fin)
467         {
468             // last message frame
469             BOOST_ASSERT(rd_remain > 0);
470             result = clamp(rd_remain);
471         }
472         else
473         {
474             result = (std::max)(
475                 initial_size, clamp(rd_remain));
476         }
477         BOOST_ASSERT(result != 0);
478         return result;
479     }
480 };
481 
482 } // detail
483 } // websocket
484 } // beast
485 } // boost
486 
487 #endif
488