diff --git a/test/extension/SConscript b/test/extension/SConscript index 8651171a08..ed5f6f7986 100644 --- a/test/extension/SConscript +++ b/test/extension/SConscript @@ -13,11 +13,15 @@ env_cpp11 = env_cpp11.Clone () BOOST_LIBS = boostlibs(['unit_test_framework','system','regex'],env) + [platform_libs] objs = env.Object('extension_boost.o', ["extension.cpp"], LIBS = BOOST_LIBS) +objs += env.Object('permessage_deflate_boost.o', ["permessage_deflate.cpp"], LIBS = BOOST_LIBS) prgs = env.Program('test_extension_boost', ["extension_boost.o"], LIBS = BOOST_LIBS) +prgs += env.Program('test_permessage_deflate_boost', ["permessage_deflate_boost.o"], LIBS = BOOST_LIBS) if env_cpp11.has_key('WSPP_CPP11_ENABLED'): BOOST_LIBS_CPP11 = boostlibs(['unit_test_framework'],env_cpp11) + [platform_libs] + [polyfill_libs] objs += env_cpp11.Object('extension_stl.o', ["extension.cpp"], LIBS = BOOST_LIBS_CPP11) + objs += env_cpp11.Object('permessage_deflate_stl.o', ["permessage_deflate.cpp"], LIBS = BOOST_LIBS_CPP11) prgs += env_cpp11.Program('test_extension_stl', ["extension_stl.o"], LIBS = BOOST_LIBS_CPP11) + prgs += env_cpp11.Program('test_permessage_deflate_stl', ["permessage_deflate_stl.o"], LIBS = BOOST_LIBS_CPP11) Return('prgs') diff --git a/test/extension/permessage_deflate.cpp b/test/extension/permessage_deflate.cpp new file mode 100644 index 0000000000..607d1380ec --- /dev/null +++ b/test/extension/permessage_deflate.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2011, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +//#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE permessage_deflate +#include + +#include + +#include +#include +#include + +#include + +class config {}; + +typedef websocketpp::extensions::permessage_deflate::enabled enabled_type; +typedef websocketpp::extensions::permessage_deflate::disabled disabled_type; + +// Ensure the disabled extension behaves appropriately disabled + +BOOST_AUTO_TEST_CASE( disabled_is_disabled ) { + disabled_type ext; + BOOST_CHECK( !ext.is_implemented() ); +} + +BOOST_AUTO_TEST_CASE( disabled_is_off ) { + disabled_type ext; + BOOST_CHECK( !ext.is_enabled() ); +} + +// Ensure the enabled version actually works + +BOOST_AUTO_TEST_CASE( enabled_is_enabled ) { + enabled_type ext; + BOOST_CHECK( ext.is_implemented() ); +} + +BOOST_AUTO_TEST_CASE( enabled_starts_disabled ) { + enabled_type ext; + BOOST_CHECK( !ext.is_enabled() ); +} + +// Tests for various negotiations diff --git a/websocketpp/extensions/permessage_deflate/disabled.hpp b/websocketpp/extensions/permessage_deflate/disabled.hpp index e07ab3fc8b..8b1e6cb57f 100644 --- a/websocketpp/extensions/permessage_deflate/disabled.hpp +++ b/websocketpp/extensions/permessage_deflate/disabled.hpp @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -49,11 +50,10 @@ namespace permessage_deflate { */ template class disabled { - typedef typename config::request_type::attribute_list attribute_list; typedef std::pair err_str_pair; public: - err_str_pair negotiate(const attribute_list& attributes) { + err_str_pair negotiate(http::attribute_list const & attributes) { return make_pair(make_error_code(error::disabled),std::string()); } @@ -69,17 +69,17 @@ public: return false; } - lib::error_code compress(const std::string in, std::string &out) { + lib::error_code compress(std::string const & in, std::string & out) { return make_error_code(error::disabled); } - lib::error_code decompress(const uint8_t * buf, size_t len, - std::string &out) + lib::error_code decompress(uint8_t const * buf, size_t len, + std::string & out) { return make_error_code(error::disabled); } - lib::error_code decompress(const std::string in, std::string &out) { + lib::error_code decompress(std::string const & in, std::string & out) { return make_error_code(error::disabled); } }; diff --git a/websocketpp/extensions/permessage_deflate/enabled.hpp b/websocketpp/extensions/permessage_deflate/enabled.hpp index 4511cf6e46..b8bb172914 100644 --- a/websocketpp/extensions/permessage_deflate/enabled.hpp +++ b/websocketpp/extensions/permessage_deflate/enabled.hpp @@ -29,8 +29,9 @@ #define WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP #include -#include #include +#include +#include #include @@ -41,8 +42,36 @@ namespace websocketpp { namespace extensions { + +/// Implimentation of the draft permessage-deflate WebSocket extension +/** + * ### permessage-deflate interface + * + * **is_implimented**\n + * `bool is_implimented()`\n + * Returns whether or not the object impliments the extension or not + * + * **is_enabled**\n + * `bool is_enabled()`\n + * Returns whether or not the extension was negotiated for the current + * connection + * + * **negotiate**\n + * `err_str_pair negotiate(http::attribute_list const & attributes)`\n + * Negotiate the parameters of extension use + * + * **compress**\n + * `lib::error_code compress(std::string const & in, std::string & out)`\n + * Compress the bytes in `in` and append them to `out` + * + * **decompress**\n + * `lib::error_code decompress(uint8_t const * buf, size_t len, std::string & + * out)`\n + * Decompress `len` bytes from `buf` and append them to string `out` + */ namespace permessage_deflate { - + +/// Permessage deflate error values namespace error { enum value { /// Catch all @@ -67,11 +96,12 @@ enum value { uninitialized, }; +/// Permessage-deflate error category class category : public lib::error_category { public: category() {} - const char *name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { + char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ { return "websocketpp.extension.permessage-deflate"; } @@ -97,11 +127,13 @@ public: } }; -const lib::error_category& get_category() { +/// Get a reference to a static copy of the permessage-deflate error category +lib::error_category const & get_category() { static category instance; return instance; } +/// Create an error code in the permessage-deflate category lib::error_code make_error_code(error::value e) { return lib::error_code(static_cast(e), get_category()); } @@ -115,7 +147,7 @@ _WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_ template<> struct is_error_code_enum { - static const bool value = true; + static bool const value = true; }; _WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_ namespace websocketpp { @@ -123,452 +155,88 @@ namespace extensions { namespace permessage_deflate { template -class method { -public: - typedef typename config::request_type::attribute_list attribute_list; - - virtual const char* name() const = 0; - virtual lib::error_code init(const attribute_list& attributes) = 0; - virtual lib::error_code compress(const std::string& in, std::string& out); - virtual lib::error_code decompress(const uint8_t * buf, size_t len, - std::string& out); - virtual lib::error_code decompress(const std::string& in, std::string& out); -}; - -template -class deflate_method : public method { -public: - typedef method base; - typedef typename base::attribute_list attribute_list; - - deflate_method(bool is_server) - : m_is_server(is_server) - , s2c_no_context_takeover(false) - , c2s_no_context_takeover(false) - , s2c_max_window_bits(15) - , c2s_max_window_bits(15) {} - - const char* name() const { - return "deflate"; - } - - lib::error_code init(const attribute_list& attributes) { - typename attribute_list::const_iterator it; - - for (it = attributes.begin(); it != attributes.end(); ++it) { - if (it->first == "s2c_no_context_takeover") { - s2c_no_context_takeover = true; - } else if (it->first == "c2s_no_context_takeover") { - c2s_no_context_takeover = true; - } else if (it->first == "s2c_max_window_bits") { - std::istringstream ss(it->second); - int temp; - if ((ss >> temp).fail() || temp > 15 || temp < 8) { - return make_error_code(error::invalid_algorithm_settings); - } - s2c_max_window_bits = temp; - } else if (it->first == "c2s_max_window_bits") { - if (m_is_server) { - // we are allowed to control the max window size of the - // client by sending back this parameter with a value. - } else { - // The server requested that we limit our outgoing window - // size - std::istringstream ss(it->second); - int temp; - if ((ss >> temp).fail() || temp > 15 || temp < 8) { - return make_error_code(error::invalid_algorithm_settings); - } - c2s_max_window_bits = temp; - } - - } else { - return make_error_code(error::unknown_method_parameter); - } - } - - // initialize zlib - m_zstream.zalloc = Z_NULL; - m_zstream.zfree = Z_NULL; - m_zstream.opaque = Z_NULL; - - int ret = deflateInit2( - &m_zstream, - // This is a CPU vs space tradeoff, TODO: make configurable - Z_DEFAULT_COMPRESSION, - Z_DEFLATED, - // A smaller value here may be chosen to reduce memory usage - (m_is_server ? s2c_max_window_bits : c2s_max_window_bits), - // memLevel (1-9), TODO: make configurable - 8, - // strategy. maybe make configurable? - Z_DEFAULT_STRATEGY - ); - - if (ret != Z_OK) { - return make_error_code(error::zlib_error); - } - - return lib::error_code(); - } - -private: - const bool m_is_server; - - bool s2c_no_context_takeover; - bool c2s_no_context_takeover; - uint8_t s2c_max_window_bits; - uint8_t c2s_max_window_bits; - - - z_stream m_zstream; -}; - - -class deflate_engine { -public: - deflate_engine() - : m_valid(false) - { - m_dstate.zalloc = Z_NULL; - m_dstate.zfree = Z_NULL; - m_dstate.opaque = Z_NULL; - - m_istate.zalloc = Z_NULL; - m_istate.zfree = Z_NULL; - m_istate.opaque = Z_NULL; - m_istate.avail_in = 0; - m_istate.next_in = Z_NULL; - } - - ~deflate_engine() { - if (!m_valid) { - // init was never successfully called, nothing to clean up. - return; - } - - int ret = deflateEnd(&m_dstate); - - if (ret != Z_OK) { - std::cout << "error cleaning up zlib compression state" - << std::endl; - } - - ret = inflateEnd(&m_istate); - - if (ret != Z_OK) { - std::cout << "error cleaning up zlib decompression state" - << std::endl; - } - } - - // TODO: delete copy constructor and assignment operator - - /// Initialize zlib state - /** - * @param window_bits Sliding window size. Value range from 8-15. Higher - * values use more memory but provide better compression. - * - * @param compress_level How much compression to apply. Values range from 0 - * to 9. 1 is fastest, 9 is best compression. 0 provides no compression. The - * default is ~6. - * - * @param mem_level How much memory to use for internal compression state - * Values range from 1 to 9. 1 is lowest memory, 9 is best compression. - * Default is 8. - * - * @param strategy Passes through strategy tuning parameter. See zlib - * documentation for more information. - * - * @return A status code. 0 on success, non-zero otherwise. - */ - lib::error_code init( - int compress_window_bits = 15, int decompress_window_bits = 15, - bool reset_compress = false, bool reset_decompress = false, - int compress_level = Z_DEFAULT_COMPRESSION, int mem_level = 8, - int strategy = Z_DEFAULT_STRATEGY, size_t compress_buffer = 16384) - { - int ret = deflateInit2( - &m_dstate, - compress_level, - Z_DEFLATED, - -1*compress_window_bits, - mem_level, - strategy - ); - - if (ret != Z_OK) { - return make_error_code(error::zlib_error); - } - - ret = inflateInit2( - &m_istate, - -1*decompress_window_bits - ); - - if (ret != Z_OK) { - return make_error_code(error::zlib_error); - } - - m_compress_buffer.reset(new unsigned char[compress_buffer]); - m_compress_buffer_size = compress_buffer; - - m_valid = true; - return lib::error_code(); - } - - /// Compress a string in one chunk - /** - * - * Bytes input must be unmasked, output bytes are also unmasked. - * - * TODO: potential optimization to use copy+mask rather than copy - * is this a bad idea? probably. - * - */ - lib::error_code compress(const std::string& in, std::string& out) { - if (!m_valid) { - return make_error_code(error::uninitialized); - } - - size_t output; - int ret; - - // Read from input string - m_dstate.avail_in = in.size(); - m_dstate.next_in = (unsigned char *)(const_cast(in.data())); - - do { - // Output to local buffer - m_dstate.avail_out = m_compress_buffer_size; - m_dstate.next_out = m_compress_buffer.get(); - - ret = deflate(&m_dstate,Z_SYNC_FLUSH); - // assert(ret != Z_STREAM_ERROR); - // - output = m_compress_buffer_size - m_dstate.avail_out; - - out.append((char *)(m_compress_buffer.get()),output); - } while (m_dstate.avail_out == 0); - // assert(m_zlib_state.avail_in == 0); // all input should be used - - return lib::error_code(); - } - - lib::error_code decompress(const std::string& in, std::string& out) { - return this->decompress( - reinterpret_cast(in.data()), - in.size(), - out - ); - } - - lib::error_code decompress(const uint8_t * buf, size_t len, - std::string & out) - { - if (!m_valid) { - return make_error_code(error::uninitialized); - } - - int ret; - - m_istate.avail_in = len; - m_istate.next_in = const_cast(buf); - - do { - m_istate.avail_out = m_compress_buffer_size; - m_istate.next_out = m_compress_buffer.get(); - - ret = inflate(&m_istate,Z_SYNC_FLUSH); - - if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) { - return make_error_code(error::zlib_error); - } - - out.append( - reinterpret_cast(m_compress_buffer.get()), - m_compress_buffer_size - m_istate.avail_out - ); - } while (m_istate.avail_out == 0); - - return lib::error_code(); - } -private: - bool m_valid; - - lib::unique_ptr_uchar_array m_compress_buffer; - - size_t m_compress_buffer_size; - - z_stream m_dstate; // deflate state - z_stream m_istate; // inflate state -}; - -/// Impliments the permessage_deflate extension interface -/** - * - * Template parameter econfig defines compile time types, constants, and - * settings. It should be a struct with the following members: - * - * request_type (type) A type that implements the http::request interface - * - * allow_disabling_context_takeover (static const bool) whether or not to - * disable context takeover when the other endpoint requests it. - * - * - * Parses the attribute list and determines if there are any errors based on - * permessage_compress spec. If there are, init will fail. init - * - * - * - * Methods: - * permessage_deflate::enabled does not define or implement any methods - * itself. It uses the attribute list to determine - * - * - * - * - */ -template class enabled { - typedef std::map string_map; - - typedef typename econfig::request_type::attribute_list attribute_list; - typedef typename attribute_list::const_iterator attribute_iterator; - typedef lib::shared_ptr< method > method_ptr; - - typedef typename econfig::request_type request_type; - - typedef std::pair err_str_pair; - public: - enabled() : m_enabled(false) {} - - /// Attempt to negotiate the permessage_deflate extension + enabled() + : m_enabled(false) + , m_c2s_no_context_takeover(false) + , m_s2c_no_context_takeover(false) + , m_c2s_max_window_bits(15) + , m_s2c_max_window_bits(15) {} + + /// Test if this object impliments the permessage-deflate specification /** - * Parses the attribute list for this extension and attempts to negotiate - * the extension. Returns a pair. On success - * the error code is zero and the string contains the negotated parameters - * to return in the handshake response. On error + * Because this object does impliment it, it will always return true. * - * @param attributes A list of attributes extracted from the - * 'permessage_deflate' extension parameter from the original handshake. - * - * @return A pair containing a status code - * and a value whose interpretation is dependent on the status code. + * @return Whether or not this object impliments permessage-deflate */ - err_str_pair negotiate(const string_map& attributes) { - err_str_pair ret; - - std::cout << "foo: " << attributes.size() << std::endl; - - string_map::const_iterator it; - - for (it = attributes.begin(); it != attributes.end(); ++it) { - std::cout << it->first << ": " << it->second << std::endl; - } - - // start by not accepting any parameters - if (attributes.size() == 0) { - ret.second = "permessage-deflate"; - } else { - ret.first = make_error_code(error::invalid_parameters); - } - - /* - * - * Sec-WebSocket-Extensions: foo; bar; baz=5, foo2; bar2; baz2=5 - * - * map> - * - * vector>>> - * - */ - - - /* - // Exactly one parameter is required - if (attributes.size() != 1) { - ret.first = make_error_code(error::invalid_parameters); - return ret; - } - - attribute_iterator method = attributes.find("method"); - - // That one parameter must be 'method' - if (method == attributes.end()) { - ret.first = make_error_code(error::invalid_parameters); - return ret; - } - - // It's value must be a valid parameter list - request_type parameter_parser; - typename request_type::parameter_list plist; - - if (parameter_parser.parse_parameter_list(method->second,plist)) { - ret.first = make_error_code(error::invalid_parameters); - return ret; - } - - // This represents a list of potential parameter sets that we could use - // in order of client preference. We need to validate the options to - // make sure none are illegal. Then one of these needs to be chosen as - // the actual value set to be used for the connection. - - typename request_type::parameter_list::const_iterator it; - - for (it = plist.begin(); it != plist.end(); ++it) - { - if (it->first == "deflate") { - // we can do deflate - //method_ptr new_method(new deflate_method(true)); - - //new_method->init(it->second); - } else { - // unsupported algorithm, ignore - } - } - - m_enabled = true;*/ - return ret; - } - - /// Returns true if this object implements permessage_deflate functionality bool is_implemented() const { return true; } - - /// returns true if this object is initialized and ready to provide - /// permessage_deflate functionality. + + /// Test if the extension was negotiated for this connection + /** + * Retrieves whether or not this extension is in use based on the initial + * handshake extension negotiations. + * + * @return Whether or not the extension is in use + */ bool is_enabled() const { return m_enabled; } - - lib::error_code compress(const std::string in, std::string &out) { - if (!m_enabled) { - return make_error_code(error::uninitialized); - } - return m_method->compress(in,out); + + /// Does this extension support resetting its sliding window? + bool no_context_takeover_support() const { + return false; } - lib::error_code decompress(const uint8_t * buf, size_t len, - std::string &out) + /// Does this extension support adjusting its sliding window size + bool max_window_bits_support() const { + return false; + } + + /// Negotiate extension + /** + * Negotiate whether or not to use and parameter values for the extension + * based on the request from the the remote endpoint and the local policy + * + * @param attributes Attribute from remote endpoint's request + * @return Status code and value to return to remote endpoint + */ + err_str_pair negotiate(http::attribute_list const & attributes) { + std::string neg = "permessage-deflate"; + //return make_pair(make_error_code(error::zlib_error),std::string()); + return make_pair(lib::error_code(),neg); + } + + /// Compress bytes + /** + * @param [in] in String to compress + * @param [out] out String to append compressed bytes to + * @return Error or status code + */ + lib::error_code compress(std::string const & in, std::string & out) { + return make_error_code(error::uninitialized); + } + + /// Decompress bytes + /** + * @param buf Byte buffer to decompress + * @param len Length of buf + * @param out String to append decompressed bytes to + * @return Error or status code + */ + lib::error_code decompress(uint8_t const * buf, size_t len, std::string & + out) { - if (!m_enabled) { - return make_error_code(error::uninitialized); - } - return m_method->decompress(buf,len,out); - } - - lib::error_code decompress(const std::string in, std::string &out) { - if (!m_enabled) { - return make_error_code(error::uninitialized); - } - return m_method->decompress(in,out); + return make_error_code(error::uninitialized); } private: bool m_enabled; - method_ptr m_method; + bool m_c2s_no_context_takeover; + bool m_s2c_no_context_takeover; + uint8_t m_c2s_max_window_bits; + uint8_t m_s2c_max_window_bits; }; } // namespace permessage_deflate diff --git a/websocketpp/processors/hybi13.hpp b/websocketpp/processors/hybi13.hpp index b97955ac92..e3ff464967 100644 --- a/websocketpp/processors/hybi13.hpp +++ b/websocketpp/processors/hybi13.hpp @@ -94,7 +94,7 @@ public: return ret; } - typename request_type::parameter_list p; + http::parameter_list p; bool error = req.get_header_as_plist("Sec-WebSocket-Extensions",p); @@ -108,9 +108,9 @@ public: return ret; } - typename request_type::parameter_list::const_iterator it; + http::parameter_list::const_iterator it; - if (m_permessage_deflate.is_implemented()) { + /*if (m_permessage_deflate.is_implemented()) { err_str_pair neg_ret; for (it = p.begin(); it != p.end(); ++it) { // look through each extension, if the key is permessage-deflate @@ -134,7 +134,7 @@ public: } } } - } + }*/ return ret; } @@ -269,10 +269,10 @@ public: std::vector & subprotocol_list) { if (!req.get_header("Sec-WebSocket-Protocol").empty()) { - typename request_type::parameter_list p; + http::parameter_list p; if (!req.get_header_as_plist("Sec-WebSocket-Protocol",p)) { - typename request_type::parameter_list::const_iterator it; + http::parameter_list::const_iterator it; for (it = p.begin(); it != p.end(); ++it) { subprotocol_list.push_back(it->first);