websocketpp  0.6.0
C++/Boost Asio based websocket client/server library
enabled.hpp
1 /*
2  * Copyright (c) 2014, Peter Thorson. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  * * Redistributions of source code must retain the above copyright
7  * notice, this list of conditions and the following disclaimer.
8  * * Redistributions in binary form must reproduce the above copyright
9  * notice, this list of conditions and the following disclaimer in the
10  * documentation and/or other materials provided with the distribution.
11  * * Neither the name of the WebSocket++ Project nor the
12  * names of its contributors may be used to endorse or promote products
13  * derived from this software without specific prior written permission.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
19  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *
26  */
27 
28 #ifndef WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP
29 #define WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP
30 
31 
32 #include <websocketpp/common/cpp11.hpp>
33 #include <websocketpp/common/memory.hpp>
34 #include <websocketpp/common/platforms.hpp>
35 #include <websocketpp/common/system_error.hpp>
36 #include <websocketpp/error.hpp>
37 
38 #include <websocketpp/extensions/extension.hpp>
39 
40 #include "zlib.h"
41 
42 #include <algorithm>
43 #include <string>
44 #include <vector>
45 
46 namespace websocketpp {
47 namespace extensions {
48 
50 
83 namespace permessage_deflate {
84 
86 namespace error {
87 enum value {
89  general = 1,
90 
93 
96 
99 
102 
105 
108 
111 };
112 
114 class category : public lib::error_category {
115 public:
116  category() {}
117 
118  char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ {
119  return "websocketpp.extension.permessage-deflate";
120  }
121 
122  std::string message(int value) const {
123  switch(value) {
124  case general:
125  return "Generic permessage-compress error";
126  case invalid_attributes:
127  return "Invalid extension attributes";
129  return "Invalid extension attribute value";
130  case invalid_mode:
131  return "Invalid permessage-deflate negotiation mode";
133  return "Unsupported extension attributes";
135  return "Invalid value for max_window_bits";
136  case zlib_error:
137  return "A zlib function returned an error";
138  case uninitialized:
139  return "Object must be initialized before use";
140  default:
141  return "Unknown permessage-compress error";
142  }
143  }
144 };
145 
147 lib::error_category const & get_category() {
148  static category instance;
149  return instance;
150 }
151 
153 lib::error_code make_error_code(error::value e) {
154  return lib::error_code(static_cast<int>(e), get_category());
155 }
156 
157 } // namespace error
158 } // namespace permessage_deflate
159 } // namespace extensions
160 } // namespace websocketpp
161 
162 _WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_
163 template<> struct is_error_code_enum
165 {
166  static bool const value = true;
167 };
168 _WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_
169 namespace websocketpp {
170 namespace extensions {
171 namespace permessage_deflate {
172 
174 static uint8_t const default_s2c_max_window_bits = 15;
176 static uint8_t const min_s2c_max_window_bits = 8;
178 static uint8_t const max_s2c_max_window_bits = 15;
179 
181 static uint8_t const default_c2s_max_window_bits = 15;
183 static uint8_t const min_c2s_max_window_bits = 8;
185 static uint8_t const max_c2s_max_window_bits = 15;
186 
187 namespace mode {
188 enum value {
190  accept = 1,
192  decline,
194  largest,
196  smallest
197 };
198 } // namespace mode
199 
200 template <typename config>
201 class enabled {
202 public:
203  enabled()
204  : m_enabled(false)
205  , m_s2c_no_context_takeover(false)
206  , m_c2s_no_context_takeover(false)
207  , m_s2c_max_window_bits(15)
208  , m_c2s_max_window_bits(15)
209  , m_s2c_max_window_bits_mode(mode::accept)
210  , m_c2s_max_window_bits_mode(mode::accept)
211  , m_initialized(false)
212  , m_compress_buffer_size(16384)
213  {
214  m_dstate.zalloc = Z_NULL;
215  m_dstate.zfree = Z_NULL;
216  m_dstate.opaque = Z_NULL;
217 
218  m_istate.zalloc = Z_NULL;
219  m_istate.zfree = Z_NULL;
220  m_istate.opaque = Z_NULL;
221  m_istate.avail_in = 0;
222  m_istate.next_in = Z_NULL;
223  }
224 
225  ~enabled() {
226  if (!m_initialized) {
227  return;
228  }
229 
230  int ret = deflateEnd(&m_dstate);
231 
232  if (ret != Z_OK) {
233  //std::cout << "error cleaning up zlib compression state"
234  // << std::endl;
235  }
236 
237  ret = inflateEnd(&m_istate);
238 
239  if (ret != Z_OK) {
240  //std::cout << "error cleaning up zlib decompression state"
241  // << std::endl;
242  }
243  }
244 
246 
251  lib::error_code init() {
252  uint8_t deflate_bits;
253  uint8_t inflate_bits;
254 
255  if (true /*is_server*/) {
256  deflate_bits = m_s2c_max_window_bits;
257  inflate_bits = m_c2s_max_window_bits;
258  } else {
259  deflate_bits = m_c2s_max_window_bits;
260  inflate_bits = m_s2c_max_window_bits;
261  }
262 
263  int ret = deflateInit2(
264  &m_dstate,
265  Z_DEFAULT_COMPRESSION,
266  Z_DEFLATED,
267  -1*deflate_bits,
268  8, // memory level 1-9
269  /*Z_DEFAULT_STRATEGY*/Z_FIXED
270  );
271 
272  if (ret != Z_OK) {
273  return make_error_code(error::zlib_error);
274  }
275 
276  ret = inflateInit2(
277  &m_istate,
278  -1*inflate_bits
279  );
280 
281  if (ret != Z_OK) {
282  return make_error_code(error::zlib_error);
283  }
284 
285  m_compress_buffer.reset(new unsigned char[m_compress_buffer_size]);
286  m_initialized = true;
287  return lib::error_code();
288  }
289 
291 
296  bool is_implemented() const {
297  return true;
298  }
299 
301 
307  bool is_enabled() const {
308  return m_enabled;
309  }
310 
312 
333  m_s2c_no_context_takeover = true;
334  }
335 
337 
352  m_c2s_no_context_takeover = true;
353  }
354 
356 
377  lib::error_code set_s2c_max_window_bits(uint8_t bits, mode::value mode) {
378  if (bits < min_s2c_max_window_bits || bits > max_s2c_max_window_bits) {
380  }
381  m_s2c_max_window_bits = bits;
382  m_s2c_max_window_bits_mode = mode;
383 
384  return lib::error_code();
385  }
386 
388 
408  lib::error_code set_c2s_max_window_bits(uint8_t bits, mode::value mode) {
409  if (bits < min_c2s_max_window_bits || bits > max_c2s_max_window_bits) {
411  }
412  m_c2s_max_window_bits = bits;
413  m_c2s_max_window_bits_mode = mode;
414 
415  return lib::error_code();
416  }
417 
419 
425  std::string generate_offer() const {
426  return "";
427  }
428 
430 
437  lib::error_code validate_offer(http::attribute_list const &) {
438  return make_error_code(error::general);
439  }
440 
442 
451  err_str_pair ret;
452 
453  http::attribute_list::const_iterator it;
454  for (it = offer.begin(); it != offer.end(); ++it) {
455  if (it->first == "s2c_no_context_takeover") {
456  negotiate_s2c_no_context_takeover(it->second,ret.first);
457  } else if (it->first == "c2s_no_context_takeover") {
458  negotiate_c2s_no_context_takeover(it->second,ret.first);
459  } else if (it->first == "s2c_max_window_bits") {
460  negotiate_s2c_max_window_bits(it->second,ret.first);
461  } else if (it->first == "c2s_max_window_bits") {
462  negotiate_c2s_max_window_bits(it->second,ret.first);
463  } else {
464  ret.first = make_error_code(error::invalid_attributes);
465  }
466 
467  if (ret.first) {
468  break;
469  }
470  }
471 
472  if (ret.first == lib::error_code()) {
473  m_enabled = true;
474  ret.second = generate_response();
475  }
476 
477  return ret;
478  }
479 
481 
486  lib::error_code compress(std::string const & in, std::string & out) {
487  if (!m_initialized) {
488  return make_error_code(error::uninitialized);
489  }
490 
491  size_t output;
492 
493  m_dstate.avail_out = m_compress_buffer_size;
494  m_dstate.next_in = (unsigned char *)(const_cast<char *>(in.data()));
495 
496  do {
497  // Output to local buffer
498  m_dstate.avail_out = m_compress_buffer_size;
499  m_dstate.next_out = m_compress_buffer.get();
500 
501  deflate(&m_dstate, Z_SYNC_FLUSH);
502 
503  output = m_compress_buffer_size - m_dstate.avail_out;
504 
505  out.append((char *)(m_compress_buffer.get()),output);
506  } while (m_dstate.avail_out == 0);
507 
508  return lib::error_code();
509  }
510 
512 
518  lib::error_code decompress(uint8_t const * buf, size_t len, std::string &
519  out)
520  {
521  if (!m_initialized) {
522  return make_error_code(error::uninitialized);
523  }
524 
525  int ret;
526 
527  m_istate.avail_in = len;
528  m_istate.next_in = const_cast<unsigned char *>(buf);
529 
530  do {
531  m_istate.avail_out = m_compress_buffer_size;
532  m_istate.next_out = m_compress_buffer.get();
533 
534  ret = inflate(&m_istate, Z_SYNC_FLUSH);
535 
536  if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
537  return make_error_code(error::zlib_error);
538  }
539 
540  out.append(
541  reinterpret_cast<char *>(m_compress_buffer.get()),
542  m_compress_buffer_size - m_istate.avail_out
543  );
544  } while (m_istate.avail_out == 0);
545 
546  return lib::error_code();
547  }
548 private:
550 
553  std::string generate_response() {
554  std::string ret = "permessage-deflate";
555 
556  if (m_s2c_no_context_takeover) {
557  ret += "; s2c_no_context_takeover";
558  }
559 
560  if (m_c2s_no_context_takeover) {
561  ret += "; c2s_no_context_takeover";
562  }
563 
564  if (m_s2c_max_window_bits < default_s2c_max_window_bits) {
565  std::stringstream s;
566  s << int(m_s2c_max_window_bits);
567  ret += "; s2c_max_window_bits="+s.str();
568  }
569 
570  if (m_c2s_max_window_bits < default_c2s_max_window_bits) {
571  std::stringstream s;
572  s << int(m_c2s_max_window_bits);
573  ret += "; c2s_max_window_bits="+s.str();
574  }
575 
576  return ret;
577  }
578 
580 
584  void negotiate_s2c_no_context_takeover(std::string const & value,
585  lib::error_code & ec)
586  {
587  if (!value.empty()) {
588  ec = make_error_code(error::invalid_attribute_value);
589  return;
590  }
591 
592  m_s2c_no_context_takeover = true;
593  }
594 
596 
600  void negotiate_c2s_no_context_takeover(std::string const & value,
601  lib::error_code & ec)
602  {
603  if (!value.empty()) {
604  ec = make_error_code(error::invalid_attribute_value);
605  return;
606  }
607 
608  m_c2s_no_context_takeover = true;
609  }
610 
612 
627  void negotiate_s2c_max_window_bits(std::string const & value,
628  lib::error_code & ec)
629  {
630  uint8_t bits = uint8_t(atoi(value.c_str()));
631 
632  if (bits < min_s2c_max_window_bits || bits > max_s2c_max_window_bits) {
633  ec = make_error_code(error::invalid_attribute_value);
634  m_s2c_max_window_bits = default_s2c_max_window_bits;
635  return;
636  }
637 
638  switch (m_s2c_max_window_bits_mode) {
639  case mode::decline:
640  m_s2c_max_window_bits = default_s2c_max_window_bits;
641  break;
642  case mode::accept:
643  m_s2c_max_window_bits = bits;
644  break;
645  case mode::largest:
646  m_s2c_max_window_bits = (std::min)(bits,m_s2c_max_window_bits);
647  break;
648  case mode::smallest:
649  m_s2c_max_window_bits = min_s2c_max_window_bits;
650  break;
651  default:
652  ec = make_error_code(error::invalid_mode);
653  m_s2c_max_window_bits = default_s2c_max_window_bits;
654  }
655  }
656 
658 
672  void negotiate_c2s_max_window_bits(std::string const & value,
673  lib::error_code & ec)
674  {
675  uint8_t bits = uint8_t(atoi(value.c_str()));
676 
677  if (value.empty()) {
679  } else if (bits < min_c2s_max_window_bits ||
680  bits > max_c2s_max_window_bits)
681  {
682  ec = make_error_code(error::invalid_attribute_value);
683  m_c2s_max_window_bits = default_c2s_max_window_bits;
684  return;
685  }
686 
687  switch (m_c2s_max_window_bits_mode) {
688  case mode::decline:
689  m_c2s_max_window_bits = default_c2s_max_window_bits;
690  break;
691  case mode::accept:
692  m_c2s_max_window_bits = bits;
693  break;
694  case mode::largest:
695  m_c2s_max_window_bits = std::min(bits,m_c2s_max_window_bits);
696  break;
697  case mode::smallest:
698  m_c2s_max_window_bits = min_c2s_max_window_bits;
699  break;
700  default:
701  ec = make_error_code(error::invalid_mode);
702  m_c2s_max_window_bits = default_c2s_max_window_bits;
703  }
704  }
705 
706  bool m_enabled;
707  bool m_s2c_no_context_takeover;
708  bool m_c2s_no_context_takeover;
709  uint8_t m_s2c_max_window_bits;
710  uint8_t m_c2s_max_window_bits;
711  mode::value m_s2c_max_window_bits_mode;
712  mode::value m_c2s_max_window_bits_mode;
713 
714  bool m_initialized;
715  size_t m_compress_buffer_size;
716  lib::unique_ptr_uchar_array m_compress_buffer;
717  z_stream m_dstate;
718  z_stream m_istate;
719 };
720 
721 } // namespace permessage_deflate
722 } // namespace extensions
723 } // namespace websocketpp
724 
725 #endif // WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP
static uint8_t const default_c2s_max_window_bits
Default value for c2s_max_window_bits as defined by RFC6455.
Definition: enabled.hpp:181
uint16_t value
The type of a close code value.
Definition: close.hpp:49
static uint8_t const max_s2c_max_window_bits
Maximum value for s2c_max_window_bits as defined by RFC6455.
Definition: enabled.hpp:178
lib::error_category const & get_category()
Get a reference to a static copy of the permessage-deflate error category.
Definition: enabled.hpp:147
std::pair< lib::error_code, std::string > err_str_pair
Combination error code / string type for returning two values.
Definition: error.hpp:41
lib::error_code init()
Initialize zlib state.
Definition: enabled.hpp:251
lib::error_code set_c2s_max_window_bits(uint8_t bits, mode::value mode)
Limit client LZ77 sliding window size.
Definition: enabled.hpp:408
lib::error_code validate_offer(http::attribute_list const &)
Validate extension response.
Definition: enabled.hpp:437
lib::error_code decompress(uint8_t const *buf, size_t len, std::string &out)
Decompress bytes.
Definition: enabled.hpp:518
lib::error_code make_error_code(error::value e)
Create an error code in the permessage-deflate category.
Definition: enabled.hpp:153
static uint8_t const min_c2s_max_window_bits
Minimum value for c2s_max_window_bits as defined by RFC6455.
Definition: enabled.hpp:183
Namespace for the WebSocket++ project.
Definition: base64.hpp:41
err_str_pair negotiate(http::attribute_list const &offer)
Negotiate extension.
Definition: enabled.hpp:450
static uint8_t const max_c2s_max_window_bits
Maximum value for c2s_max_window_bits as defined by RFC6455.
Definition: enabled.hpp:185
bool is_implemented() const
Test if this object implements the permessage-deflate specification.
Definition: enabled.hpp:296
std::map< std::string, std::string > attribute_list
The type of an HTTP attribute list.
Definition: constants.hpp:45
bool is_enabled() const
Test if the extension was negotiated for this connection.
Definition: enabled.hpp:307
void enable_c2s_no_context_takeover()
Reset client's outgoing LZ77 sliding window for each new message.
Definition: enabled.hpp:351
static uint8_t const min_s2c_max_window_bits
Minimum value for s2c_max_window_bits as defined by RFC6455.
Definition: enabled.hpp:176
static uint8_t const default_s2c_max_window_bits
Default value for s2c_max_window_bits as defined by RFC6455.
Definition: enabled.hpp:174
lib::error_code compress(std::string const &in, std::string &out)
Compress bytes.
Definition: enabled.hpp:486
lib::error_code set_s2c_max_window_bits(uint8_t bits, mode::value mode)
Limit server LZ77 sliding window size.
Definition: enabled.hpp:377
std::string generate_offer() const
Generate extension offer.
Definition: enabled.hpp:425
void enable_s2c_no_context_takeover()
Reset server's outgoing LZ77 sliding window for each new message.
Definition: enabled.hpp:332