WebSocket++ 0.8.2
C++ websocket client/server library
Loading...
Searching...
No Matches
enabled.hpp
1/*
2 * Copyright (c) 2015, 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/stdint.hpp>
36#include <websocketpp/common/system_error.hpp>
37#include <websocketpp/error.hpp>
38
39#include <websocketpp/extensions/extension.hpp>
40
41#include "zlib.h"
42
43#include <algorithm>
44#include <string>
45#include <vector>
46
47namespace websocketpp {
48namespace extensions {
49
51
88namespace permessage_deflate {
89
91namespace error {
92enum value {
95
98
101
104
107
110
113
116};
117
119class category : public lib::error_category {
120public:
121 category() {}
122
123 char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ {
124 return "websocketpp.extension.permessage-deflate";
125 }
126
127 std::string message(int value) const {
128 switch(value) {
129 case general:
130 return "Generic permessage-compress error";
132 return "Invalid extension attributes";
134 return "Invalid extension attribute value";
135 case invalid_mode:
136 return "Invalid permessage-deflate negotiation mode";
138 return "Unsupported extension attributes";
140 return "Invalid value for max_window_bits";
141 case zlib_error:
142 return "A zlib function returned an error";
143 case uninitialized:
144 return "Deflate extension must be initialized before use";
145 default:
146 return "Unknown permessage-compress error";
147 }
148 }
149};
150
152inline lib::error_category const & get_category() {
153 static category instance;
154 return instance;
155}
156
158inline lib::error_code make_error_code(error::value e) {
159 return lib::error_code(static_cast<int>(e), get_category());
160}
161
162} // namespace error
163} // namespace permessage_deflate
164} // namespace extensions
165} // namespace websocketpp
166
167_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_
168template<> struct is_error_code_enum
169 <websocketpp::extensions::permessage_deflate::error::value>
170{
171 static bool const value = true;
172};
173_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_
174namespace websocketpp {
175namespace extensions {
176namespace permessage_deflate {
177
179static uint8_t const default_server_max_window_bits = 15;
181
187static uint8_t const min_server_max_window_bits = 8;
189static uint8_t const max_server_max_window_bits = 15;
190
192static uint8_t const default_client_max_window_bits = 15;
194
200static uint8_t const min_client_max_window_bits = 8;
202static uint8_t const max_client_max_window_bits = 15;
203
204namespace mode {
205enum value {
207 accept = 1,
209 decline,
211 largest,
213 smallest
214};
215} // namespace mode
216
217template <typename config>
218class enabled {
219public:
220 enabled()
221 : m_enabled(false)
222 , m_server_no_context_takeover(false)
223 , m_client_no_context_takeover(false)
224 , m_server_max_window_bits(15)
225 , m_client_max_window_bits(15)
226 , m_server_max_window_bits_mode(mode::accept)
227 , m_client_max_window_bits_mode(mode::accept)
228 , m_initialized(false)
229 , m_compress_buffer_size(8192)
230 {
231 m_dstate.zalloc = Z_NULL;
232 m_dstate.zfree = Z_NULL;
233 m_dstate.opaque = Z_NULL;
234
235 m_istate.zalloc = Z_NULL;
236 m_istate.zfree = Z_NULL;
237 m_istate.opaque = Z_NULL;
238 m_istate.avail_in = 0;
239 m_istate.next_in = Z_NULL;
240 }
241
242 ~enabled() {
243 if (!m_initialized) {
244 return;
245 }
246
247 int ret = deflateEnd(&m_dstate);
248
249 if (ret != Z_OK) {
250 //std::cout << "error cleaning up zlib compression state"
251 // << std::endl;
252 }
253
254 ret = inflateEnd(&m_istate);
255
256 if (ret != Z_OK) {
257 //std::cout << "error cleaning up zlib decompression state"
258 // << std::endl;
259 }
260 }
261
263
273 lib::error_code init(bool is_server) {
274 uint8_t deflate_bits;
275 uint8_t inflate_bits;
276
277 if (is_server) {
278 deflate_bits = m_server_max_window_bits;
279 inflate_bits = m_client_max_window_bits;
280 } else {
281 deflate_bits = m_client_max_window_bits;
282 inflate_bits = m_server_max_window_bits;
283 }
284
285 int ret = deflateInit2(
286 &m_dstate,
287 Z_DEFAULT_COMPRESSION,
288 Z_DEFLATED,
289 -1*deflate_bits,
290 4, // memory level 1-9
291 Z_DEFAULT_STRATEGY
292 );
293
294 if (ret != Z_OK) {
295 return make_error_code(error::zlib_error);
296 }
297
298 ret = inflateInit2(
299 &m_istate,
300 -1*inflate_bits
301 );
302
303 if (ret != Z_OK) {
304 return make_error_code(error::zlib_error);
305 }
306
307 m_compress_buffer.reset(new unsigned char[m_compress_buffer_size]);
308 m_decompress_buffer.reset(new unsigned char[m_compress_buffer_size]);
309 if ((m_server_no_context_takeover && is_server) ||
310 (m_client_no_context_takeover && !is_server))
311 {
312 m_flush = Z_FULL_FLUSH;
313 } else {
314 m_flush = Z_SYNC_FLUSH;
315 }
316 m_initialized = true;
317 return lib::error_code();
318 }
319
321
326 bool is_implemented() const {
327 return true;
328 }
329
331
337 bool is_enabled() const {
338 return m_enabled;
339 }
340
342
363 m_server_no_context_takeover = true;
364 }
365
367
382 m_client_no_context_takeover = true;
383 }
384
386
415 lib::error_code set_server_max_window_bits(uint8_t bits, mode::value mode) {
416 if (bits < min_server_max_window_bits || bits > max_server_max_window_bits) {
418 }
419
420 // See note in doc comment above about what is happening here
421 if (bits == 8) {
422 bits = 9;
423 }
424
425 m_server_max_window_bits = bits;
426 m_server_max_window_bits_mode = mode;
427
428 return lib::error_code();
429 }
430
432
460 lib::error_code set_client_max_window_bits(uint8_t bits, mode::value mode) {
461 if (bits < min_client_max_window_bits || bits > max_client_max_window_bits) {
463 }
464
465 // See note in doc comment above about what is happening here
466 if (bits == 8) {
467 bits = 9;
468 }
469
470 m_client_max_window_bits = bits;
471 m_client_max_window_bits_mode = mode;
472
473 return lib::error_code();
474 }
475
477
483 std::string generate_offer() const {
484 // TODO: this should be dynamically generated based on user settings
485 return "permessage-deflate; client_no_context_takeover; client_max_window_bits";
486 }
487
489
496 lib::error_code validate_offer(http::attribute_list const &) {
497 return lib::error_code();
498 }
499
501
510 err_str_pair ret;
511
512 http::attribute_list::const_iterator it;
513 for (it = offer.begin(); it != offer.end(); ++it) {
514 if (it->first == "server_no_context_takeover") {
515 negotiate_server_no_context_takeover(it->second,ret.first);
516 } else if (it->first == "client_no_context_takeover") {
517 negotiate_client_no_context_takeover(it->second,ret.first);
518 } else if (it->first == "server_max_window_bits") {
519 negotiate_server_max_window_bits(it->second,ret.first);
520 } else if (it->first == "client_max_window_bits") {
521 negotiate_client_max_window_bits(it->second,ret.first);
522 } else {
523 ret.first = make_error_code(error::invalid_attributes);
524 }
525
526 if (ret.first) {
527 break;
528 }
529 }
530
531 if (ret.first == lib::error_code()) {
532 m_enabled = true;
533 ret.second = generate_response();
534 }
535
536 return ret;
537 }
538
540
548 lib::error_code compress(std::string const & in, std::string & out) {
549 if (!m_initialized) {
550 return make_error_code(error::uninitialized);
551 }
552
553 size_t output;
554
555 if (in.empty()) {
556 uint8_t buf[6] = {0x02, 0x00, 0x00, 0x00, 0xff, 0xff};
557 out.append((char *)(buf),6);
558 return lib::error_code();
559 }
560
561 m_dstate.avail_in = in.size();
562 m_dstate.next_in = (unsigned char *)(const_cast<char *>(in.data()));
563
564 do {
565 // Output to local buffer
566 m_dstate.avail_out = m_compress_buffer_size;
567 m_dstate.next_out = m_compress_buffer.get();
568
569 deflate(&m_dstate, m_flush);
570
571 output = m_compress_buffer_size - m_dstate.avail_out;
572
573 out.append((char *)(m_compress_buffer.get()),output);
574 } while (m_dstate.avail_out == 0);
575
576 return lib::error_code();
577 }
578
580
586 lib::error_code decompress(uint8_t const * buf, size_t len, std::string &
587 out)
588 {
589 if (!m_initialized) {
590 return make_error_code(error::uninitialized);
591 }
592
593 int ret;
594
595 m_istate.avail_in = len;
596 m_istate.next_in = const_cast<unsigned char *>(buf);
597
598 do {
599 m_istate.avail_out = m_compress_buffer_size;
600 m_istate.next_out = m_decompress_buffer.get();
601
602 ret = inflate(&m_istate, Z_SYNC_FLUSH);
603
604 if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
605 return make_error_code(error::zlib_error);
606 }
607
608 out.append(
609 reinterpret_cast<char *>(m_decompress_buffer.get()),
610 m_compress_buffer_size - m_istate.avail_out
611 );
612 } while (m_istate.avail_out == 0);
613
614 return lib::error_code();
615 }
616private:
618
621 std::string generate_response() {
622 std::string ret = "permessage-deflate";
623
624 if (m_server_no_context_takeover) {
625 ret += "; server_no_context_takeover";
626 }
627
628 if (m_client_no_context_takeover) {
629 ret += "; client_no_context_takeover";
630 }
631
632 if (m_server_max_window_bits < default_server_max_window_bits) {
633 std::stringstream s;
634 s << int(m_server_max_window_bits);
635 ret += "; server_max_window_bits="+s.str();
636 }
637
638 if (m_client_max_window_bits < default_client_max_window_bits) {
639 std::stringstream s;
640 s << int(m_client_max_window_bits);
641 ret += "; client_max_window_bits="+s.str();
642 }
643
644 return ret;
645 }
646
648
652 void negotiate_server_no_context_takeover(std::string const & value,
653 lib::error_code & ec)
654 {
655 if (!value.empty()) {
656 ec = make_error_code(error::invalid_attribute_value);
657 return;
658 }
659
660 m_server_no_context_takeover = true;
661 }
662
664
668 void negotiate_client_no_context_takeover(std::string const & value,
669 lib::error_code & ec)
670 {
671 if (!value.empty()) {
672 ec = make_error_code(error::invalid_attribute_value);
673 return;
674 }
675
676 m_client_no_context_takeover = true;
677 }
678
680
701 void negotiate_server_max_window_bits(std::string const & value,
702 lib::error_code & ec)
703 {
704 uint8_t bits = uint8_t(atoi(value.c_str()));
705
706 if (bits < min_server_max_window_bits || bits > max_server_max_window_bits) {
707 ec = make_error_code(error::invalid_attribute_value);
708 m_server_max_window_bits = default_server_max_window_bits;
709 return;
710 }
711
712 switch (m_server_max_window_bits_mode) {
713 case mode::decline:
714 m_server_max_window_bits = default_server_max_window_bits;
715 break;
716 case mode::accept:
717 m_server_max_window_bits = bits;
718 break;
719 case mode::largest:
720 m_server_max_window_bits = std::min(bits,m_server_max_window_bits);
721 break;
722 case mode::smallest:
723 m_server_max_window_bits = min_server_max_window_bits;
724 break;
725 default:
726 ec = make_error_code(error::invalid_mode);
727 m_server_max_window_bits = default_server_max_window_bits;
728 }
729
730 // See note in doc comment
731 if (m_server_max_window_bits == 8) {
732 m_server_max_window_bits = 9;
733 }
734 }
735
737
757 void negotiate_client_max_window_bits(std::string const & value,
758 lib::error_code & ec)
759 {
760 uint8_t bits = uint8_t(atoi(value.c_str()));
761
762 if (value.empty()) {
764 } else if (bits < min_client_max_window_bits ||
766 {
767 ec = make_error_code(error::invalid_attribute_value);
768 m_client_max_window_bits = default_client_max_window_bits;
769 return;
770 }
771
772 switch (m_client_max_window_bits_mode) {
773 case mode::decline:
774 m_client_max_window_bits = default_client_max_window_bits;
775 break;
776 case mode::accept:
777 m_client_max_window_bits = bits;
778 break;
779 case mode::largest:
780 m_client_max_window_bits = std::min(bits,m_client_max_window_bits);
781 break;
782 case mode::smallest:
783 m_client_max_window_bits = min_client_max_window_bits;
784 break;
785 default:
786 ec = make_error_code(error::invalid_mode);
787 m_client_max_window_bits = default_client_max_window_bits;
788 }
789
790 // See note in doc comment
791 if (m_client_max_window_bits == 8) {
792 m_client_max_window_bits = 9;
793 }
794 }
795
796 bool m_enabled;
797 bool m_server_no_context_takeover;
798 bool m_client_no_context_takeover;
799 uint8_t m_server_max_window_bits;
800 uint8_t m_client_max_window_bits;
801 mode::value m_server_max_window_bits_mode;
802 mode::value m_client_max_window_bits_mode;
803
804 bool m_initialized;
805 int m_flush;
806 size_t m_compress_buffer_size;
807 lib::unique_ptr_uchar_array m_compress_buffer;
808 lib::unique_ptr_uchar_array m_decompress_buffer;
809 z_stream m_dstate;
810 z_stream m_istate;
811};
812
813} // namespace permessage_deflate
814} // namespace extensions
815} // namespace websocketpp
816
817#endif // WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP
lib::error_code init(bool is_server)
Initialize zlib state.
Definition enabled.hpp:273
void enable_client_no_context_takeover()
Reset client's outgoing LZ77 sliding window for each new message.
Definition enabled.hpp:381
lib::error_code compress(std::string const &in, std::string &out)
Compress bytes.
Definition enabled.hpp:548
bool is_enabled() const
Test if the extension was negotiated for this connection.
Definition enabled.hpp:337
lib::error_code decompress(uint8_t const *buf, size_t len, std::string &out)
Decompress bytes.
Definition enabled.hpp:586
lib::error_code set_client_max_window_bits(uint8_t bits, mode::value mode)
Limit client LZ77 sliding window size.
Definition enabled.hpp:460
bool is_implemented() const
Test if this object implements the permessage-deflate specification.
Definition enabled.hpp:326
void enable_server_no_context_takeover()
Reset server's outgoing LZ77 sliding window for each new message.
Definition enabled.hpp:362
err_str_pair negotiate(http::attribute_list const &offer)
Negotiate extension.
Definition enabled.hpp:509
lib::error_code set_server_max_window_bits(uint8_t bits, mode::value mode)
Limit server LZ77 sliding window size.
Definition enabled.hpp:415
lib::error_code validate_offer(http::attribute_list const &)
Validate extension response.
Definition enabled.hpp:496
std::string generate_offer() const
Generate extension offer.
Definition enabled.hpp:483
@ unsupported_attributes
Unsupported extension attributes.
Definition enabled.hpp:106
@ invalid_max_window_bits
Invalid value for max_window_bits.
Definition enabled.hpp:109
@ invalid_attribute_value
Invalid extension attribute value.
Definition enabled.hpp:100
@ invalid_attributes
Invalid extension attributes.
Definition enabled.hpp:97
lib::error_category const & get_category()
Get a reference to a static copy of the permessage-deflate error category.
Definition enabled.hpp:152
lib::error_code make_error_code(error::value e)
Create an error code in the permessage-deflate category.
Definition enabled.hpp:158
static uint8_t const default_client_max_window_bits
Default value for client_max_window_bits as defined by RFC 7692.
Definition enabled.hpp:192
static uint8_t const default_server_max_window_bits
Default value for server_max_window_bits as defined by RFC 7692.
Definition enabled.hpp:179
static uint8_t const max_client_max_window_bits
Maximum value for client_max_window_bits as defined by RFC 7692.
Definition enabled.hpp:202
static uint8_t const min_client_max_window_bits
Minimum value for client_max_window_bits as defined by RFC 7692.
Definition enabled.hpp:200
static uint8_t const min_server_max_window_bits
Minimum value for server_max_window_bits as defined by RFC 7692.
Definition enabled.hpp:187
static uint8_t const max_server_max_window_bits
Maximum value for server_max_window_bits as defined by RFC 7692.
Definition enabled.hpp:189
std::map< std::string, std::string > attribute_list
The type of an HTTP attribute list.
Definition constants.hpp:45
Namespace for the WebSocket++ project.
Definition base64.hpp:41
std::pair< lib::error_code, std::string > err_str_pair
Combination error code / string type for returning two values.
Definition error.hpp:41