1
0
mirror of https://github.com/VCMP-SqMod/SqMod.git synced 2025-01-20 20:47:15 +01:00

818 lines
29 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2015, 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.
*
*/
#ifndef WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP
#define WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP
#include <websocketpp/common/cpp11.hpp>
#include <websocketpp/common/memory.hpp>
#include <websocketpp/common/platforms.hpp>
#include <websocketpp/common/stdint.hpp>
#include <websocketpp/common/system_error.hpp>
#include <websocketpp/error.hpp>
#include <websocketpp/extensions/extension.hpp>
#include "zlib.h"
#include <algorithm>
#include <string>
#include <vector>
namespace websocketpp {
namespace extensions {
/// Implementation of RFC 7692, the permessage-deflate WebSocket extension
/**
* ### permessage-deflate interface
*
* **init**\n
* `lib::error_code init(bool is_server)`\n
* Performs initialization
*
* **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
*
* **generate_offer**\n
* `std::string generate_offer() const`\n
* Create an extension offer string based on local policy
*
* **validate_response**\n
* `lib::error_code validate_response(http::attribute_list const & response)`\n
* Negotiate the parameters of extension use
*
* **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
general = 1,
/// Invalid extension attributes
invalid_attributes,
/// Invalid extension attribute value
invalid_attribute_value,
/// Invalid megotiation mode
invalid_mode,
/// Unsupported extension attributes
unsupported_attributes,
/// Invalid value for max_window_bits
invalid_max_window_bits,
/// ZLib Error
zlib_error,
/// Uninitialized
uninitialized,
};
/// Permessage-deflate error category
class category : public lib::error_category {
public:
category() {}
char const * name() const _WEBSOCKETPP_NOEXCEPT_TOKEN_ {
return "websocketpp.extension.permessage-deflate";
}
std::string message(int value) const {
switch(value) {
case general:
return "Generic permessage-compress error";
case invalid_attributes:
return "Invalid extension attributes";
case invalid_attribute_value:
return "Invalid extension attribute value";
case invalid_mode:
return "Invalid permessage-deflate negotiation mode";
case unsupported_attributes:
return "Unsupported extension attributes";
case invalid_max_window_bits:
return "Invalid value for max_window_bits";
case zlib_error:
return "A zlib function returned an error";
case uninitialized:
return "Deflate extension must be initialized before use";
default:
return "Unknown permessage-compress error";
}
}
};
/// Get a reference to a static copy of the permessage-deflate error category
inline lib::error_category const & get_category() {
static category instance;
return instance;
}
/// Create an error code in the permessage-deflate category
inline lib::error_code make_error_code(error::value e) {
return lib::error_code(static_cast<int>(e), get_category());
}
} // namespace error
} // namespace permessage_deflate
} // namespace extensions
} // namespace websocketpp
_WEBSOCKETPP_ERROR_CODE_ENUM_NS_START_
template<> struct is_error_code_enum
<websocketpp::extensions::permessage_deflate::error::value>
{
static bool const value = true;
};
_WEBSOCKETPP_ERROR_CODE_ENUM_NS_END_
namespace websocketpp {
namespace extensions {
namespace permessage_deflate {
/// Default value for server_max_window_bits as defined by RFC 7692
static uint8_t const default_server_max_window_bits = 15;
/// Minimum value for server_max_window_bits as defined by RFC 7692
/**
* NOTE: A value of 8 is not actually supported by zlib, the deflate
* library that WebSocket++ uses. To preserve backwards compatibility
* with RFC 7692 and previous versions of the library a value of 8
* is accepted by the library but will always be negotiated as 9.
*/
static uint8_t const min_server_max_window_bits = 8;
/// Maximum value for server_max_window_bits as defined by RFC 7692
static uint8_t const max_server_max_window_bits = 15;
/// Default value for client_max_window_bits as defined by RFC 7692
static uint8_t const default_client_max_window_bits = 15;
/// Minimum value for client_max_window_bits as defined by RFC 7692
/**
* NOTE: A value of 8 is not actually supported by zlib, the deflate
* library that WebSocket++ uses. To preserve backwards compatibility
* with RFC 7692 and previous versions of the library a value of 8
* is accepted by the library but will always be negotiated as 9.
*/
static uint8_t const min_client_max_window_bits = 8;
/// Maximum value for client_max_window_bits as defined by RFC 7692
static uint8_t const max_client_max_window_bits = 15;
namespace mode {
enum value {
/// Accept any value the remote endpoint offers
accept = 1,
/// Decline any value the remote endpoint offers. Insist on defaults.
decline,
/// Use the largest value common to both offers
largest,
/// Use the smallest value common to both offers
smallest
};
} // namespace mode
template <typename config>
class enabled {
public:
enabled()
: m_enabled(false)
, m_server_no_context_takeover(false)
, m_client_no_context_takeover(false)
, m_server_max_window_bits(15)
, m_client_max_window_bits(15)
, m_server_max_window_bits_mode(mode::accept)
, m_client_max_window_bits_mode(mode::accept)
, m_initialized(false)
, m_compress_buffer_size(8192)
{
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;
}
~enabled() {
if (!m_initialized) {
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;
}
}
/// Initialize zlib state
/**
* Note: this should be called *after* the negotiation methods. It will use
* information from the negotiation to determine how to initialize the zlib
* data structures.
*
* @todo memory level, strategy, etc are hardcoded
*
* @param is_server True to initialize as a server, false for a client.
* @return A code representing the error that occurred, if any
*/
lib::error_code init(bool is_server) {
uint8_t deflate_bits;
uint8_t inflate_bits;
if (is_server) {
deflate_bits = m_server_max_window_bits;
inflate_bits = m_client_max_window_bits;
} else {
deflate_bits = m_client_max_window_bits;
inflate_bits = m_server_max_window_bits;
}
int ret = deflateInit2(
&m_dstate,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
-1*deflate_bits,
4, // memory level 1-9
Z_DEFAULT_STRATEGY
);
if (ret != Z_OK) {
return make_error_code(error::zlib_error);
}
ret = inflateInit2(
&m_istate,
-1*inflate_bits
);
if (ret != Z_OK) {
return make_error_code(error::zlib_error);
}
m_compress_buffer.reset(new unsigned char[m_compress_buffer_size]);
m_decompress_buffer.reset(new unsigned char[m_compress_buffer_size]);
if ((m_server_no_context_takeover && is_server) ||
(m_client_no_context_takeover && !is_server))
{
m_flush = Z_FULL_FLUSH;
} else {
m_flush = Z_SYNC_FLUSH;
}
m_initialized = true;
return lib::error_code();
}
/// Test if this object implements the permessage-deflate specification
/**
* Because this object does implieent it, it will always return true.
*
* @return Whether or not this object implements permessage-deflate
*/
bool is_implemented() const {
return true;
}
/// 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;
}
/// Reset server's outgoing LZ77 sliding window for each new message
/**
* Enabling this setting will cause the server's compressor to reset the
* compression state (the LZ77 sliding window) for every message. This
* means that the compressor will not look back to patterns in previous
* messages to improve compression. This will reduce the compression
* efficiency for large messages somewhat and small messages drastically.
*
* This option may reduce server compressor memory usage and client
* decompressor memory usage.
* @todo Document to what extent memory usage will be reduced
*
* For clients, this option is dependent on server support. Enabling it
* via this method does not guarantee that it will be successfully
* negotiated, only that it will be requested.
*
* For servers, no client support is required. Enabling this option on a
* server will result in its use. The server will signal to clients that
* the option will be in use so they can optimize resource usage if they
* are able.
*/
void enable_server_no_context_takeover() {
m_server_no_context_takeover = true;
}
/// Reset client's outgoing LZ77 sliding window for each new message
/**
* Enabling this setting will cause the client's compressor to reset the
* compression state (the LZ77 sliding window) for every message. This
* means that the compressor will not look back to patterns in previous
* messages to improve compression. This will reduce the compression
* efficiency for large messages somewhat and small messages drastically.
*
* This option may reduce client compressor memory usage and server
* decompressor memory usage.
* @todo Document to what extent memory usage will be reduced
*
* This option is supported by all compliant clients and servers. Enabling
* it via either endpoint should be sufficient to ensure it is used.
*/
void enable_client_no_context_takeover() {
m_client_no_context_takeover = true;
}
/// Limit server LZ77 sliding window size
/**
* The bits setting is the base 2 logarithm of the maximum window size that
* the server must use to compress outgoing messages. The permitted range
* is 9 to 15 inclusive. 9 represents a 512 byte window and 15 a 32KiB
* window. The default setting is 15.
*
* Mode Options:
* - accept: Accept whatever the remote endpoint offers.
* - decline: Decline any offers to deviate from the defaults
* - largest: Accept largest window size acceptable to both endpoints
* - smallest: Accept smallest window size acceptiable to both endpoints
*
* This setting is dependent on server support. A client requesting this
* setting may be rejected by the server or have the exact value used
* adjusted by the server. A server may unilaterally set this value without
* client support.
*
* NOTE: The permessage-deflate spec specifies that a value of 8 is allowed.
* Prior to version 0.8.0 a value of 8 was also allowed by this library.
* zlib, the deflate compression library that WebSocket++ uses has always
* silently adjusted a value of 8 to 9. In recent versions of zlib (1.2.9
* and greater) a value of 8 is now explicitly rejected. WebSocket++ 0.8.0
* continues to perform the 8->9 conversion for backwards compatibility
* purposes but this should be considered deprecated functionality.
*
* @param bits The size to request for the outgoing window size
* @param mode The mode to use for negotiating this parameter
* @return A status code
*/
lib::error_code set_server_max_window_bits(uint8_t bits, mode::value mode) {
if (bits < min_server_max_window_bits || bits > max_server_max_window_bits) {
return error::make_error_code(error::invalid_max_window_bits);
}
// See note in doc comment above about what is happening here
if (bits == 8) {
bits = 9;
}
m_server_max_window_bits = bits;
m_server_max_window_bits_mode = mode;
return lib::error_code();
}
/// Limit client LZ77 sliding window size
/**
* The bits setting is the base 2 logarithm of the window size that the
* client must use to compress outgoing messages. The permitted range is 9
* to 15 inclusive. 9 represents a 512 byte window and 15 a 32KiB window.
* The default setting is 15.
*
* Mode Options:
* - accept: Accept whatever the remote endpoint offers.
* - decline: Decline any offers to deviate from the defaults
* - largest: Accept largest window size acceptable to both endpoints
* - smallest: Accept smallest window size acceptiable to both endpoints
*
* This setting is dependent on client support. A client may limit its own
* outgoing window size unilaterally. A server may only limit the client's
* window size if the remote client supports that feature.
*
* NOTE: The permessage-deflate spec specifies that a value of 8 is allowed.
* Prior to version 0.8.0 a value of 8 was also allowed by this library.
* zlib, the deflate compression library that WebSocket++ uses has always
* silently adjusted a value of 8 to 9. In recent versions of zlib (1.2.9
* and greater) a value of 8 is now explicitly rejected. WebSocket++ 0.8.0
* continues to perform the 8->9 conversion for backwards compatibility
* purposes but this should be considered deprecated functionality.
*
* @param bits The size to request for the outgoing window size
* @param mode The mode to use for negotiating this parameter
* @return A status code
*/
lib::error_code set_client_max_window_bits(uint8_t bits, mode::value mode) {
if (bits < min_client_max_window_bits || bits > max_client_max_window_bits) {
return error::make_error_code(error::invalid_max_window_bits);
}
// See note in doc comment above about what is happening here
if (bits == 8) {
bits = 9;
}
m_client_max_window_bits = bits;
m_client_max_window_bits_mode = mode;
return lib::error_code();
}
/// Generate extension offer
/**
* Creates an offer string to include in the Sec-WebSocket-Extensions
* header of outgoing client requests.
*
* @return A WebSocket extension offer string for this extension
*/
std::string generate_offer() const {
// TODO: this should be dynamically generated based on user settings
return "permessage-deflate; client_no_context_takeover; client_max_window_bits";
}
/// Validate extension response
/**
* Confirm that the server has negotiated settings compatible with our
* original offer and apply those settings to the extension state.
*
* @param response The server response attribute list to validate
* @return Validation error or 0 on success
*/
lib::error_code validate_offer(http::attribute_list const &) {
return lib::error_code();
}
/// Negotiate extension
/**
* Confirm that the client's extension negotiation offer has settings
* compatible with local policy. If so, generate a reply and apply those
* settings to the extension state.
*
* @param offer Attribute from client's offer
* @return Status code and value to return to remote endpoint
*/
err_str_pair negotiate(http::attribute_list const & offer) {
err_str_pair ret;
http::attribute_list::const_iterator it;
for (it = offer.begin(); it != offer.end(); ++it) {
if (it->first == "server_no_context_takeover") {
negotiate_server_no_context_takeover(it->second,ret.first);
} else if (it->first == "client_no_context_takeover") {
negotiate_client_no_context_takeover(it->second,ret.first);
} else if (it->first == "server_max_window_bits") {
negotiate_server_max_window_bits(it->second,ret.first);
} else if (it->first == "client_max_window_bits") {
negotiate_client_max_window_bits(it->second,ret.first);
} else {
ret.first = make_error_code(error::invalid_attributes);
}
if (ret.first) {
break;
}
}
if (ret.first == lib::error_code()) {
m_enabled = true;
ret.second = generate_response();
}
return ret;
}
/// Compress bytes
/**
* @todo: avail_in/out is 32 bit, need to fix for cases of >32 bit frames
* on 64 bit machines.
*
* @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) {
if (!m_initialized) {
return make_error_code(error::uninitialized);
}
size_t output;
if (in.empty()) {
uint8_t buf[6] = {0x02, 0x00, 0x00, 0x00, 0xff, 0xff};
out.append((char *)(buf),6);
return lib::error_code();
}
m_dstate.avail_in = in.size();
m_dstate.next_in = (unsigned char *)(const_cast<char *>(in.data()));
do {
// Output to local buffer
m_dstate.avail_out = m_compress_buffer_size;
m_dstate.next_out = m_compress_buffer.get();
deflate(&m_dstate, m_flush);
output = m_compress_buffer_size - m_dstate.avail_out;
out.append((char *)(m_compress_buffer.get()),output);
} while (m_dstate.avail_out == 0);
return lib::error_code();
}
/// 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_initialized) {
return make_error_code(error::uninitialized);
}
int ret;
m_istate.avail_in = len;
m_istate.next_in = const_cast<unsigned char *>(buf);
do {
m_istate.avail_out = m_compress_buffer_size;
m_istate.next_out = m_decompress_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<char *>(m_decompress_buffer.get()),
m_compress_buffer_size - m_istate.avail_out
);
} while (m_istate.avail_out == 0);
return lib::error_code();
}
private:
/// Generate negotiation response
/**
* @return Generate extension negotiation reponse string to send to client
*/
std::string generate_response() {
std::string ret = "permessage-deflate";
if (m_server_no_context_takeover) {
ret += "; server_no_context_takeover";
}
if (m_client_no_context_takeover) {
ret += "; client_no_context_takeover";
}
if (m_server_max_window_bits < default_server_max_window_bits) {
std::stringstream s;
s << int(m_server_max_window_bits);
ret += "; server_max_window_bits="+s.str();
}
if (m_client_max_window_bits < default_client_max_window_bits) {
std::stringstream s;
s << int(m_client_max_window_bits);
ret += "; client_max_window_bits="+s.str();
}
return ret;
}
/// Negotiate server_no_context_takeover attribute
/**
* @param [in] value The value of the attribute from the offer
* @param [out] ec A reference to the error code to return errors via
*/
void negotiate_server_no_context_takeover(std::string const & value,
lib::error_code & ec)
{
if (!value.empty()) {
ec = make_error_code(error::invalid_attribute_value);
return;
}
m_server_no_context_takeover = true;
}
/// Negotiate client_no_context_takeover attribute
/**
* @param [in] value The value of the attribute from the offer
* @param [out] ec A reference to the error code to return errors via
*/
void negotiate_client_no_context_takeover(std::string const & value,
lib::error_code & ec)
{
if (!value.empty()) {
ec = make_error_code(error::invalid_attribute_value);
return;
}
m_client_no_context_takeover = true;
}
/// Negotiate server_max_window_bits attribute
/**
* When this method starts, m_server_max_window_bits will contain the server's
* preferred value and m_server_max_window_bits_mode will contain the mode the
* server wants to use to for negotiation. `value` contains the value the
* client requested that we use.
*
* options:
* - decline (ignore value, offer our default instead)
* - accept (use the value requested by the client)
* - largest (use largest value acceptable to both)
* - smallest (use smallest possible value)
*
* NOTE: As a value of 8 is no longer explicitly supported by zlib but might
* be requested for negotiation by an older client/server, if the result of
* the negotiation would be to send a value of 8, a value of 9 is offered
* instead. This ensures that WebSocket++ will only ever negotiate connections
* with compression settings explicitly supported by zlib.
*
* @param [in] value The value of the attribute from the offer
* @param [out] ec A reference to the error code to return errors via
*/
void negotiate_server_max_window_bits(std::string const & value,
lib::error_code & ec)
{
uint8_t bits = uint8_t(atoi(value.c_str()));
if (bits < min_server_max_window_bits || bits > max_server_max_window_bits) {
ec = make_error_code(error::invalid_attribute_value);
m_server_max_window_bits = default_server_max_window_bits;
return;
}
switch (m_server_max_window_bits_mode) {
case mode::decline:
m_server_max_window_bits = default_server_max_window_bits;
break;
case mode::accept:
m_server_max_window_bits = bits;
break;
case mode::largest:
m_server_max_window_bits = std::min(bits,m_server_max_window_bits);
break;
case mode::smallest:
m_server_max_window_bits = min_server_max_window_bits;
break;
default:
ec = make_error_code(error::invalid_mode);
m_server_max_window_bits = default_server_max_window_bits;
}
// See note in doc comment
if (m_server_max_window_bits == 8) {
m_server_max_window_bits = 9;
}
}
/// Negotiate client_max_window_bits attribute
/**
* When this method starts, m_client_max_window_bits and m_c2s_max_window_mode
* will contain the server's preferred values for window size and
* negotiation mode.
*
* options:
* - decline (ignore value, offer our default instead)
* - accept (use the value requested by the client)
* - largest (use largest value acceptable to both)
* - smallest (use smallest possible value)
*
* NOTE: As a value of 8 is no longer explicitly supported by zlib but might
* be requested for negotiation by an older client/server, if the result of
* the negotiation would be to send a value of 8, a value of 9 is offered
* instead. This ensures that WebSocket++ will only ever negotiate connections
* with compression settings explicitly supported by zlib.
*
* @param [in] value The value of the attribute from the offer
* @param [out] ec A reference to the error code to return errors via
*/
void negotiate_client_max_window_bits(std::string const & value,
lib::error_code & ec)
{
uint8_t bits = uint8_t(atoi(value.c_str()));
if (value.empty()) {
bits = default_client_max_window_bits;
} else if (bits < min_client_max_window_bits ||
bits > max_client_max_window_bits)
{
ec = make_error_code(error::invalid_attribute_value);
m_client_max_window_bits = default_client_max_window_bits;
return;
}
switch (m_client_max_window_bits_mode) {
case mode::decline:
m_client_max_window_bits = default_client_max_window_bits;
break;
case mode::accept:
m_client_max_window_bits = bits;
break;
case mode::largest:
m_client_max_window_bits = std::min(bits,m_client_max_window_bits);
break;
case mode::smallest:
m_client_max_window_bits = min_client_max_window_bits;
break;
default:
ec = make_error_code(error::invalid_mode);
m_client_max_window_bits = default_client_max_window_bits;
}
// See note in doc comment
if (m_client_max_window_bits == 8) {
m_client_max_window_bits = 9;
}
}
bool m_enabled;
bool m_server_no_context_takeover;
bool m_client_no_context_takeover;
uint8_t m_server_max_window_bits;
uint8_t m_client_max_window_bits;
mode::value m_server_max_window_bits_mode;
mode::value m_client_max_window_bits_mode;
bool m_initialized;
int m_flush;
size_t m_compress_buffer_size;
lib::unique_ptr_uchar_array m_compress_buffer;
lib::unique_ptr_uchar_array m_decompress_buffer;
z_stream m_dstate;
z_stream m_istate;
};
} // namespace permessage_deflate
} // namespace extensions
} // namespace websocketpp
#endif // WEBSOCKETPP_PROCESSOR_EXTENSION_PERMESSAGEDEFLATE_HPP