Experimental: rewrite extensions negotiation
This commit is contained in:
+26
-54
@@ -214,67 +214,39 @@ public:
|
|||||||
writeHeader("Sec-WebSocket-Protocol", secWebSocketProtocol.substr(0, secWebSocketProtocol.find(',')));
|
writeHeader("Sec-WebSocket-Protocol", secWebSocketProtocol.substr(0, secWebSocketProtocol.find(',')));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Negotiate compression, we may use a smaller compression window than we negotiate */
|
/* Negotiate compression */
|
||||||
bool perMessageDeflate = false;
|
bool perMessageDeflate = false;
|
||||||
/* We are always allowed to share compressor, if perMessageDeflate */
|
CompressOptions compressOptions = CompressOptions::DISABLED;
|
||||||
int compressOptions = webSocketContextData->compression & SHARED_COMPRESSOR;
|
if (secWebSocketExtensions.length() && webSocketContextData->compression != DISABLED) {
|
||||||
if (webSocketContextData->compression != DISABLED) {
|
|
||||||
if (secWebSocketExtensions.length()) {
|
|
||||||
/* We never support client context takeover (the client cannot compress with a sliding window). */
|
|
||||||
unsigned int wantedOptions = PERMESSAGE_DEFLATE | CLIENT_NO_CONTEXT_TAKEOVER;
|
|
||||||
|
|
||||||
/* Shared compressor is the default */
|
/* We always want shared inflation */
|
||||||
if (webSocketContextData->compression == SHARED_COMPRESSOR) {
|
int wantedInflationWindow = 0;
|
||||||
/* Disable per-socket compressor */
|
|
||||||
wantedOptions |= SERVER_NO_CONTEXT_TAKEOVER;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* isServer = true */
|
/* Map from selected compressor */
|
||||||
ExtensionsNegotiator<true> extensionsNegotiator(wantedOptions);
|
int wantedCompressionWindow = (webSocketContextData->compression & 0xFF00) >> 8;
|
||||||
extensionsNegotiator.readOffer(secWebSocketExtensions);
|
|
||||||
|
|
||||||
/* Todo: remove these mid string copies */
|
auto [negCompression, negCompressionWindow, negInflationWindow, negResponse] =
|
||||||
std::string offer = extensionsNegotiator.generateOffer();
|
uWS::negotiateCompression(true, wantedCompressionWindow, wantedInflationWindow,
|
||||||
if (offer.length()) {
|
secWebSocketExtensions);
|
||||||
|
|
||||||
/* Todo: this is a quick fix that should be properly moved to ExtensionsNegotiator */
|
if (negCompression) {
|
||||||
if (webSocketContextData->compression & DEDICATED_COMPRESSOR &&
|
perMessageDeflate = true;
|
||||||
webSocketContextData->compression != DEDICATED_COMPRESSOR_256KB) {
|
|
||||||
/* 3kb, 4kb is 9, 256 is 15 (default) */
|
/* Map from windowBits to compressor */
|
||||||
int maxServerWindowBits = 9;
|
if (negCompressionWindow == 0) {
|
||||||
switch (webSocketContextData->compression) {
|
compressOptions = CompressOptions::SHARED_COMPRESSOR;
|
||||||
case DEDICATED_COMPRESSOR_8KB:
|
} else {
|
||||||
maxServerWindowBits = 10;
|
compressOptions = (CompressOptions) ((uint32_t) (negCompressionWindow << 8)
|
||||||
break;
|
| (uint32_t) (negCompressionWindow - 7));
|
||||||
case DEDICATED_COMPRESSOR_16KB:
|
|
||||||
maxServerWindowBits = 11;
|
/* If we are dedicated and have the 3kb then correct any 4kb to 3kb,
|
||||||
break;
|
* (they both share the windowBits = 9) */
|
||||||
case DEDICATED_COMPRESSOR_32KB:
|
if (webSocketContextData->compression == DEDICATED_COMPRESSOR_3KB) {
|
||||||
maxServerWindowBits = 12;
|
compressOptions = DEDICATED_COMPRESSOR_3KB;
|
||||||
break;
|
|
||||||
case DEDICATED_COMPRESSOR_64KB:
|
|
||||||
maxServerWindowBits = 13;
|
|
||||||
break;
|
|
||||||
case DEDICATED_COMPRESSOR_128KB:
|
|
||||||
maxServerWindowBits = 14;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
offer += "; server_max_window_bits=";
|
|
||||||
offer += std::to_string(maxServerWindowBits);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writeHeader("Sec-WebSocket-Extensions", offer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Did we negotiate permessage-deflate? */
|
writeHeader("Sec-WebSocket-Extensions", negResponse);
|
||||||
if (extensionsNegotiator.getNegotiatedOptions() & PERMESSAGE_DEFLATE) {
|
|
||||||
perMessageDeflate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Is the server allowed to compress with a sliding window? */
|
|
||||||
if (!(extensionsNegotiator.getNegotiatedOptions() & SERVER_NO_CONTEXT_TAKEOVER)) {
|
|
||||||
compressOptions = webSocketContextData->compression;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,7 +344,7 @@ public:
|
|||||||
/* Write an HTTP header with unsigned int value */
|
/* Write an HTTP header with unsigned int value */
|
||||||
HttpResponse *writeHeader(std::string_view key, uint64_t value) {
|
HttpResponse *writeHeader(std::string_view key, uint64_t value) {
|
||||||
writeStatus(HTTP_200_OK);
|
writeStatus(HTTP_200_OK);
|
||||||
|
|
||||||
Super::write(key.data(), (int) key.length());
|
Super::write(key.data(), (int) key.length());
|
||||||
Super::write(": ", 2);
|
Super::write(": ", 2);
|
||||||
writeUnsigned64(value);
|
writeUnsigned64(value);
|
||||||
|
|||||||
+16
-45
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Authored by Alex Hultman, 2018-2020.
|
* Authored by Alex Hultman, 2018-2021.
|
||||||
* Intellectual property of third-party.
|
* Intellectual property of third-party.
|
||||||
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -22,23 +22,20 @@
|
|||||||
|
|
||||||
/* We always define these options no matter if ZLIB is enabled or not */
|
/* We always define these options no matter if ZLIB is enabled or not */
|
||||||
namespace uWS {
|
namespace uWS {
|
||||||
/* Compress options (really more like PerMessageDeflateOptions) */
|
/* Compressor mode is HIGH8(windowBits), LOW8(memLevel) */
|
||||||
enum CompressOptions : int {
|
enum CompressOptions : uint32_t {
|
||||||
/* Compression disabled */
|
|
||||||
DISABLED = 0,
|
DISABLED = 0,
|
||||||
/* We compress using a shared non-sliding window. No added memory usage, worse compression. */
|
|
||||||
SHARED_COMPRESSOR = 1,
|
SHARED_COMPRESSOR = 1,
|
||||||
/* We compress using a dedicated sliding window. Major memory usage added, better compression of similarly repeated messages. */
|
DEDICATED_COMPRESSOR_3KB = 9 << 8 | 1,
|
||||||
DEDICATED_COMPRESSOR = 2,
|
DEDICATED_COMPRESSOR_4KB = 9 << 8 | 2,
|
||||||
/* Flags for limiting memory usage of dedicated compressor */
|
DEDICATED_COMPRESSOR_8KB = 10 << 8 | 3,
|
||||||
DEDICATED_COMPRESSOR_3KB = 2 | 4,
|
DEDICATED_COMPRESSOR_16KB = 11 << 8 | 4,
|
||||||
DEDICATED_COMPRESSOR_4KB = 2 | 8,
|
DEDICATED_COMPRESSOR_32KB = 12 << 8 | 5,
|
||||||
DEDICATED_COMPRESSOR_8KB = 2 | 16,
|
DEDICATED_COMPRESSOR_64KB = 13 << 8 | 6,
|
||||||
DEDICATED_COMPRESSOR_16KB = 2 | 32,
|
DEDICATED_COMPRESSOR_128KB = 14 << 8 | 7,
|
||||||
DEDICATED_COMPRESSOR_32KB = 2 | 64,
|
DEDICATED_COMPRESSOR_256KB = 15 << 8 | 8,
|
||||||
DEDICATED_COMPRESSOR_64KB = 2 | 128,
|
/* Same as 256kb */
|
||||||
DEDICATED_COMPRESSOR_128KB = 2 | 256,
|
DEDICATED_COMPRESSOR = 15 << 8 | 8
|
||||||
DEDICATED_COMPRESSOR_256KB = 2 | 512
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,40 +91,14 @@ struct ZlibContext {
|
|||||||
struct DeflationStream {
|
struct DeflationStream {
|
||||||
z_stream deflationStream = {};
|
z_stream deflationStream = {};
|
||||||
|
|
||||||
DeflationStream(int compressOptions) {
|
DeflationStream(CompressOptions compressOptions) {
|
||||||
|
|
||||||
/* Sliding inflator should be about 44kb by default, less than compressor */
|
/* Sliding inflator should be about 44kb by default, less than compressor */
|
||||||
|
|
||||||
/* Memory usage is given by 2 ^ (windowBits + 2) + 2 ^ (memLevel + 9) */
|
/* Memory usage is given by 2 ^ (windowBits + 2) + 2 ^ (memLevel + 9) */
|
||||||
int windowBits = -15, memLevel = 8;
|
int windowBits = -(int) ((compressOptions & 0xFF00) >> 8), memLevel = compressOptions & 0x00FF;
|
||||||
|
|
||||||
if (compressOptions == DEDICATED_COMPRESSOR_3KB) {
|
//printf("windowBits: %d, memLevel: %d\n", windowBits, memLevel);
|
||||||
windowBits = -9;
|
|
||||||
memLevel = 1;
|
|
||||||
} else if (compressOptions == DEDICATED_COMPRESSOR_4KB) {
|
|
||||||
windowBits = -9;
|
|
||||||
memLevel = 2;
|
|
||||||
} else if (compressOptions == DEDICATED_COMPRESSOR_8KB) {
|
|
||||||
windowBits = -10;
|
|
||||||
memLevel = 3;
|
|
||||||
} else if (compressOptions == DEDICATED_COMPRESSOR_16KB) {
|
|
||||||
windowBits = -11;
|
|
||||||
memLevel = 4;
|
|
||||||
} else if (compressOptions == DEDICATED_COMPRESSOR_32KB) {
|
|
||||||
windowBits = -12;
|
|
||||||
memLevel = 5;
|
|
||||||
} else if (compressOptions == DEDICATED_COMPRESSOR_64KB) {
|
|
||||||
windowBits = -13;
|
|
||||||
memLevel = 6;
|
|
||||||
} else if (compressOptions == DEDICATED_COMPRESSOR_128KB) {
|
|
||||||
windowBits = -14;
|
|
||||||
memLevel = 7;
|
|
||||||
} else if (compressOptions == DEDICATED_COMPRESSOR_256KB) {
|
|
||||||
windowBits = -15;
|
|
||||||
memLevel = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* DEDICATED_COMPRESSOR_256KB is the same as DEDICATED_COMPRESSOR */
|
|
||||||
|
|
||||||
deflateInit2(&deflationStream, 1, Z_DEFLATED, windowBits, memLevel, Z_DEFAULT_STRATEGY);
|
deflateInit2(&deflationStream, 1, Z_DEFLATED, windowBits, memLevel, Z_DEFAULT_STRATEGY);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ struct WebSocket : AsyncSocket<SSL> {
|
|||||||
private:
|
private:
|
||||||
typedef AsyncSocket<SSL> Super;
|
typedef AsyncSocket<SSL> Super;
|
||||||
|
|
||||||
void *init(bool perMessageDeflate, int compressOptions, std::string &&backpressure) {
|
void *init(bool perMessageDeflate, CompressOptions compressOptions, std::string &&backpressure) {
|
||||||
new (us_socket_ext(SSL, (us_socket_t *) this)) WebSocketData(perMessageDeflate, compressOptions, std::move(backpressure));
|
new (us_socket_ext(SSL, (us_socket_t *) this)) WebSocketData(perMessageDeflate, compressOptions, std::move(backpressure));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ public:
|
|||||||
unsigned int idleTimeout = 0;
|
unsigned int idleTimeout = 0;
|
||||||
|
|
||||||
/* We do need these for async upgrade */
|
/* We do need these for async upgrade */
|
||||||
int compression;
|
CompressOptions compression;
|
||||||
|
|
||||||
/* There needs to be a maxBackpressure which will force close everything over that limit */
|
/* There needs to be a maxBackpressure which will force close everything over that limit */
|
||||||
size_t maxBackpressure = 0;
|
size_t maxBackpressure = 0;
|
||||||
|
|||||||
+2
-2
@@ -49,11 +49,11 @@ private:
|
|||||||
/* We could be a subscriber */
|
/* We could be a subscriber */
|
||||||
Subscriber *subscriber = nullptr;
|
Subscriber *subscriber = nullptr;
|
||||||
public:
|
public:
|
||||||
WebSocketData(bool perMessageDeflate, int compressOptions, std::string &&backpressure) : AsyncSocketData<false>(std::move(backpressure)), WebSocketState<true>() {
|
WebSocketData(bool perMessageDeflate, CompressOptions compressOptions, std::string &&backpressure) : AsyncSocketData<false>(std::move(backpressure)), WebSocketState<true>() {
|
||||||
compressionStatus = perMessageDeflate ? ENABLED : DISABLED;
|
compressionStatus = perMessageDeflate ? ENABLED : DISABLED;
|
||||||
|
|
||||||
/* Initialize the dedicated sliding window */
|
/* Initialize the dedicated sliding window */
|
||||||
if (perMessageDeflate && (compressOptions & CompressOptions::DEDICATED_COMPRESSOR)) {
|
if (perMessageDeflate && (compressOptions != CompressOptions::SHARED_COMPRESSOR)) {
|
||||||
deflationStream = new DeflationStream(compressOptions);
|
deflationStream = new DeflationStream(compressOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+123
-51
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Authored by Alex Hultman, 2018-2020.
|
* Authored by Alex Hultman, 2018-2021.
|
||||||
* Intellectual property of third-party.
|
* Intellectual property of third-party.
|
||||||
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -18,26 +18,32 @@
|
|||||||
#ifndef UWS_WEBSOCKETEXTENSIONS_H
|
#ifndef UWS_WEBSOCKETEXTENSIONS_H
|
||||||
#define UWS_WEBSOCKETEXTENSIONS_H
|
#define UWS_WEBSOCKETEXTENSIONS_H
|
||||||
|
|
||||||
|
/* There is a new, huge bug scenario that needs to be fixed:
|
||||||
|
* pub/sub does not support being in DEDICATED_COMPRESSOR-mode while having
|
||||||
|
* some clients downgraded to SHARED_COMPRESSOR - we cannot allow the client to
|
||||||
|
* demand a downgrade to SHARED_COMPRESSOR (yet) until we fix that scenario in pub/sub */
|
||||||
|
// #define UWS_ALLOW_SHARED_AND_DEDICATED_COMPRESSOR_MIX
|
||||||
|
|
||||||
#include <climits>
|
#include <climits>
|
||||||
|
#include <cctype>
|
||||||
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
namespace uWS {
|
namespace uWS {
|
||||||
|
|
||||||
enum Options : unsigned int {
|
|
||||||
NO_OPTIONS = 0,
|
|
||||||
PERMESSAGE_DEFLATE = 1,
|
|
||||||
SERVER_NO_CONTEXT_TAKEOVER = 2, // remove this
|
|
||||||
CLIENT_NO_CONTEXT_TAKEOVER = 4, // remove this
|
|
||||||
NO_DELAY = 8,
|
|
||||||
SLIDING_DEFLATE_WINDOW = 16
|
|
||||||
};
|
|
||||||
|
|
||||||
enum ExtensionTokens {
|
enum ExtensionTokens {
|
||||||
|
/* Standard permessage-deflate tokens */
|
||||||
TOK_PERMESSAGE_DEFLATE = 1838,
|
TOK_PERMESSAGE_DEFLATE = 1838,
|
||||||
TOK_SERVER_NO_CONTEXT_TAKEOVER = 2807,
|
TOK_SERVER_NO_CONTEXT_TAKEOVER = 2807,
|
||||||
TOK_CLIENT_NO_CONTEXT_TAKEOVER = 2783,
|
TOK_CLIENT_NO_CONTEXT_TAKEOVER = 2783,
|
||||||
TOK_SERVER_MAX_WINDOW_BITS = 2372,
|
TOK_SERVER_MAX_WINDOW_BITS = 2372,
|
||||||
TOK_CLIENT_MAX_WINDOW_BITS = 2348
|
TOK_CLIENT_MAX_WINDOW_BITS = 2348,
|
||||||
|
/* Non-standard alias for Safari */
|
||||||
|
TOK_X_WEBKIT_DEFLATE_FRAME = 2149,
|
||||||
|
TOK_NO_CONTEXT_TAKEOVER = 2049,
|
||||||
|
TOK_MAX_WINDOW_BITS = 1614
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ExtensionsParser {
|
struct ExtensionsParser {
|
||||||
@@ -45,12 +51,18 @@ private:
|
|||||||
int *lastInteger = nullptr;
|
int *lastInteger = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/* Standard */
|
||||||
bool perMessageDeflate = false;
|
bool perMessageDeflate = false;
|
||||||
bool serverNoContextTakeover = false;
|
bool serverNoContextTakeover = false;
|
||||||
bool clientNoContextTakeover = false;
|
bool clientNoContextTakeover = false;
|
||||||
int serverMaxWindowBits = 0;
|
int serverMaxWindowBits = 0;
|
||||||
int clientMaxWindowBits = 0;
|
int clientMaxWindowBits = 0;
|
||||||
|
|
||||||
|
/* Non-standard Safari */
|
||||||
|
bool xWebKitDeflateFrame = false;
|
||||||
|
bool noContextTakeover = false;
|
||||||
|
int maxWindowBits = 0;
|
||||||
|
|
||||||
int getToken(const char *&in, const char *stop) {
|
int getToken(const char *&in, const char *stop) {
|
||||||
while (in != stop && !isalnum(*in)) {
|
while (in != stop && !isalnum(*in)) {
|
||||||
in++;
|
in++;
|
||||||
@@ -78,12 +90,28 @@ public:
|
|||||||
ExtensionsParser(const char *data, size_t length) {
|
ExtensionsParser(const char *data, size_t length) {
|
||||||
const char *stop = data + length;
|
const char *stop = data + length;
|
||||||
int token = 1;
|
int token = 1;
|
||||||
for (; token && token != TOK_PERMESSAGE_DEFLATE; token = getToken(data, stop));
|
|
||||||
|
|
||||||
|
/* Ignore anything before permessage-deflate or x-webkit-deflate-frame */
|
||||||
|
for (; token && token != TOK_PERMESSAGE_DEFLATE && token != TOK_X_WEBKIT_DEFLATE_FRAME; token = getToken(data, stop));
|
||||||
|
|
||||||
|
/* What protocol are we going to use? */
|
||||||
perMessageDeflate = (token == TOK_PERMESSAGE_DEFLATE);
|
perMessageDeflate = (token == TOK_PERMESSAGE_DEFLATE);
|
||||||
|
xWebKitDeflateFrame = (token == TOK_X_WEBKIT_DEFLATE_FRAME);
|
||||||
|
|
||||||
while ((token = getToken(data, stop))) {
|
while ((token = getToken(data, stop))) {
|
||||||
switch (token) {
|
switch (token) {
|
||||||
|
case TOK_X_WEBKIT_DEFLATE_FRAME:
|
||||||
|
/* Duplicates not allowed/supported */
|
||||||
|
return;
|
||||||
|
case TOK_NO_CONTEXT_TAKEOVER:
|
||||||
|
noContextTakeover = true;
|
||||||
|
break;
|
||||||
|
case TOK_MAX_WINDOW_BITS:
|
||||||
|
maxWindowBits = 1;
|
||||||
|
lastInteger = &maxWindowBits;
|
||||||
|
break;
|
||||||
case TOK_PERMESSAGE_DEFLATE:
|
case TOK_PERMESSAGE_DEFLATE:
|
||||||
|
/* Duplicates not allowed/supported */
|
||||||
return;
|
return;
|
||||||
case TOK_SERVER_NO_CONTEXT_TAKEOVER:
|
case TOK_SERVER_NO_CONTEXT_TAKEOVER:
|
||||||
serverNoContextTakeover = true;
|
serverNoContextTakeover = true;
|
||||||
@@ -109,60 +137,104 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <bool isServer>
|
/* Takes what we (the server) wants, returns what we got */
|
||||||
struct ExtensionsNegotiator {
|
std::tuple<bool, int, int, std::string_view> negotiateCompression(bool wantCompression, int wantedCompressionWindow, int wantedInflationWindow, std::string_view offer) {
|
||||||
protected:
|
|
||||||
unsigned int options;
|
|
||||||
|
|
||||||
public:
|
/* If we don't want compression then we are done here */
|
||||||
ExtensionsNegotiator(unsigned int wantedOptions) {
|
if (!wantCompression) {
|
||||||
options = wantedOptions;
|
return {false, 0, 0, ""};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string generateOffer() {
|
ExtensionsParser ep(offer.data(), offer.length());
|
||||||
std::string extensionsOffer;
|
|
||||||
if (options & Options::PERMESSAGE_DEFLATE) {
|
|
||||||
extensionsOffer += "permessage-deflate";
|
|
||||||
|
|
||||||
if (options & Options::CLIENT_NO_CONTEXT_TAKEOVER) {
|
static thread_local std::string response;
|
||||||
extensionsOffer += "; client_no_context_takeover";
|
response = "";
|
||||||
|
|
||||||
|
int compressionWindow = wantedCompressionWindow;
|
||||||
|
int inflationWindow = wantedInflationWindow;
|
||||||
|
bool compression = false;
|
||||||
|
|
||||||
|
if (ep.xWebKitDeflateFrame) {
|
||||||
|
/* We now have compression */
|
||||||
|
compression = true;
|
||||||
|
response = "x-webkit-deflate-frame";
|
||||||
|
|
||||||
|
/* If the other peer has DEMANDED us no sliding window,
|
||||||
|
* we cannot compress with anything other than shared compressor */
|
||||||
|
if (ep.noContextTakeover) {
|
||||||
|
/* We must fail here right now (fix pub/sub) */
|
||||||
|
#ifndef UWS_ALLOW_SHARED_AND_DEDICATED_COMPRESSOR_MIX
|
||||||
|
if (wantedCompressionWindow != 0) {
|
||||||
|
return {false, 0, 0, ""};
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* It is questionable sending this improves anything */
|
compressionWindow = 0;
|
||||||
/*if (options & Options::SERVER_NO_CONTEXT_TAKEOVER) {
|
|
||||||
extensionsOffer += "; server_no_context_takeover";
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return extensionsOffer;
|
/* If the other peer has DEMANDED us to use a limited sliding window,
|
||||||
}
|
* we have to limit out compression sliding window */
|
||||||
|
if (ep.maxWindowBits && ep.maxWindowBits < compressionWindow) {
|
||||||
|
compressionWindow = ep.maxWindowBits;
|
||||||
|
}
|
||||||
|
|
||||||
void readOffer(std::string_view offer) {
|
/* We decide our own inflation sliding window (and their compression sliding window) */
|
||||||
if (isServer) {
|
if (wantedInflationWindow < 15) {
|
||||||
ExtensionsParser extensionsParser(offer.data(), offer.length());
|
if (!wantedInflationWindow) {
|
||||||
if ((options & PERMESSAGE_DEFLATE) && extensionsParser.perMessageDeflate) {
|
response += "; no_context_takeover";
|
||||||
if (extensionsParser.clientNoContextTakeover || (options & CLIENT_NO_CONTEXT_TAKEOVER)) {
|
|
||||||
options |= CLIENT_NO_CONTEXT_TAKEOVER;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We leave this option for us to read even if the client did not send it */
|
|
||||||
if (extensionsParser.serverNoContextTakeover) {
|
|
||||||
options |= SERVER_NO_CONTEXT_TAKEOVER;
|
|
||||||
}/* else {
|
|
||||||
options &= ~SERVER_NO_CONTEXT_TAKEOVER;
|
|
||||||
}*/
|
|
||||||
} else {
|
} else {
|
||||||
options &= ~PERMESSAGE_DEFLATE;
|
response += "; max_window_bits=" + std::to_string(wantedInflationWindow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (ep.perMessageDeflate) {
|
||||||
|
/* We now have compression */
|
||||||
|
compression = true;
|
||||||
|
response = "permessage-deflate";
|
||||||
|
|
||||||
|
if (ep.clientNoContextTakeover) {
|
||||||
|
inflationWindow = 0;
|
||||||
|
} else if (ep.clientMaxWindowBits && ep.clientMaxWindowBits != 1) {
|
||||||
|
inflationWindow = std::min<int>(ep.clientMaxWindowBits, inflationWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Whatever we have now, write */
|
||||||
|
if (inflationWindow < 15) {
|
||||||
|
if (!inflationWindow || !ep.clientMaxWindowBits) {
|
||||||
|
response += "; client_no_context_takeover";
|
||||||
|
inflationWindow = 0;
|
||||||
|
} else {
|
||||||
|
response += "; client_max_window_bits=" + std::to_string(inflationWindow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This block basically lets the client lower it */
|
||||||
|
if (ep.serverNoContextTakeover) {
|
||||||
|
/* This is an important (temporary) fix since we haven't allowed
|
||||||
|
* these two modes to mix, and pub/sub will not handle this case (yet) */
|
||||||
|
#ifdef UWS_ALLOW_SHARED_AND_DEDICATED_COMPRESSOR_MIX
|
||||||
|
compressionWindow = 0;
|
||||||
|
#endif
|
||||||
|
} else if (ep.serverMaxWindowBits) {
|
||||||
|
compressionWindow = std::min<int>(ep.serverMaxWindowBits, compressionWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Whatever we have now, write */
|
||||||
|
if (compressionWindow < 15) {
|
||||||
|
if (!compressionWindow) {
|
||||||
|
response += "; server_no_context_takeover";
|
||||||
|
} else {
|
||||||
|
response += "; server_max_window_bits=" + std::to_string(compressionWindow);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// todo!
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int getNegotiatedOptions() {
|
/* A final sanity check (this check does not actually catch too high values!) */
|
||||||
return options;
|
if ((compressionWindow && compressionWindow < 8) || compressionWindow > 15 || (inflationWindow && inflationWindow < 8) || inflationWindow > 15) {
|
||||||
|
return {false, 0, 0, ""};
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
return {compression, compressionWindow, inflationWindow, response};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/* This is a temporary fix since we do not support this mode with pub/sub yet */
|
||||||
|
#define UWS_ALLOW_SHARED_AND_DEDICATED_COMPRESSOR_MIX
|
||||||
|
|
||||||
|
#include "../src/WebSocketExtensions.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
void testNegotiation(bool wantCompression, int wantedCompressionWindow, int wantedInflationWindow, std::string_view offer,
|
||||||
|
bool negCompression, int negCompressionWindow, int negInflationWindow, std::string_view negResponse) {
|
||||||
|
|
||||||
|
auto [compression, compressionWindow, inflationWindow, response] = uWS::negotiateCompression(wantCompression, wantedCompressionWindow, wantedInflationWindow, offer);
|
||||||
|
|
||||||
|
if (compression == negCompression && compressionWindow == negCompressionWindow && inflationWindow == negInflationWindow && response == negResponse) {
|
||||||
|
std::cout << "PASS" << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "FAIL: <" << response << "> is not expected <" << negResponse << ">" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
/* Both parties must indicate compression for it to negotiate */
|
||||||
|
testNegotiation(false, 15, 15, "permessage-deflate", false, 0, 0, "");
|
||||||
|
testNegotiation(false, 15, 15, "x-webkit-deflate-frame", false, 0, 0, "");
|
||||||
|
testNegotiation(true, 15, 15, "", false, 15, 15, "");
|
||||||
|
testNegotiation(true, 15, 15, "", false, 15, 15, "");
|
||||||
|
|
||||||
|
/* client_max_window_bits can only be used if the client indicates support */
|
||||||
|
testNegotiation(true, 15, 11, "permessage-deflate; ", true, 15, 0, "permessage-deflate; client_no_context_takeover");
|
||||||
|
testNegotiation(true, 15, 0, "permessage-deflate; ", true, 15, 0, "permessage-deflate; client_no_context_takeover");
|
||||||
|
testNegotiation(true, 15, 11, "permessage-deflate; client_max_window_bits=14", true, 15, 11, "permessage-deflate; client_max_window_bits=11");
|
||||||
|
testNegotiation(true, 15, 11, "permessage-deflate; client_max_window_bits=9", true, 15, 9, "permessage-deflate; client_max_window_bits=9");
|
||||||
|
|
||||||
|
/* server_max_window_bits can always be used */
|
||||||
|
testNegotiation(true, 0, 15, "permessage-deflate; ", true, 0, 15, "permessage-deflate; server_no_context_takeover");
|
||||||
|
testNegotiation(true, 8, 15, "permessage-deflate; ", true, 8, 15, "permessage-deflate; server_max_window_bits=8");
|
||||||
|
testNegotiation(true, 15, 15, "permessage-deflate; server_max_window_bits=8", true, 8, 15, "permessage-deflate; server_max_window_bits=8");
|
||||||
|
testNegotiation(true, 11, 15, "permessage-deflate; server_max_window_bits=14", true, 11, 15, "permessage-deflate; server_max_window_bits=11");
|
||||||
|
|
||||||
|
/* x-webkit-deflate-frame has no particular rules */
|
||||||
|
testNegotiation(true, 11, 15, "x-webkit-deflate-frame; no_context_takeover; max_window_bits=8", true, 0, 15, "x-webkit-deflate-frame");
|
||||||
|
testNegotiation(true, 11, 12, "x-webkit-deflate-frame; no_context_takeover; max_window_bits=8", true, 0, 12, "x-webkit-deflate-frame; max_window_bits=12");
|
||||||
|
testNegotiation(true, 11, 12, "x-webkit-deflate-frame; max_window_bits=8", true, 8, 12, "x-webkit-deflate-frame; max_window_bits=12");
|
||||||
|
testNegotiation(true, 15, 0, "x-webkit-deflate-frame; max_window_bits=15", true, 15, 0, "x-webkit-deflate-frame; no_context_takeover");
|
||||||
|
|
||||||
|
/* Defaults */
|
||||||
|
testNegotiation(true, 15, 15, "x-webkit-deflate-frame", true, 15, 15, "x-webkit-deflate-frame");
|
||||||
|
testNegotiation(true, 15, 15, "permessage-deflate", true, 15, 15, "permessage-deflate");
|
||||||
|
|
||||||
|
/* Fail on invalid values */
|
||||||
|
testNegotiation(true, 15, 15, "x-webkit-deflate-frame; max_window_bits=3", false, 0, 0, "");
|
||||||
|
/* This one doesn't fail, but at least ignores the too high value */
|
||||||
|
testNegotiation(true, 15, 15, "x-webkit-deflate-frame; max_window_bits=16", true, 15, 15, "x-webkit-deflate-frame");
|
||||||
|
|
||||||
|
testNegotiation(true, 15, 15, "permessage-deflate; server_max_window_bits=3", false, 0, 0, "");
|
||||||
|
testNegotiation(true, 15, 15, "permessage-deflate; client_max_window_bits=3", false, 0, 0, "");
|
||||||
|
|
||||||
|
/* Same here; these won't fail but just be ignored */
|
||||||
|
testNegotiation(true, 15, 15, "permessage-deflate; server_max_window_bits=17", true, 15, 15, "permessage-deflate");
|
||||||
|
testNegotiation(true, 15, 15, "permessage-deflate; client_max_window_bits=17", true, 15, 15, "permessage-deflate");
|
||||||
|
|
||||||
|
std::cout << "ALL PASS" << std::endl;
|
||||||
|
}
|
||||||
+3
-1
@@ -4,4 +4,6 @@ default:
|
|||||||
$(CXX) -std=c++17 -fsanitize=address HttpRouter.cpp -o HttpRouter
|
$(CXX) -std=c++17 -fsanitize=address HttpRouter.cpp -o HttpRouter
|
||||||
./HttpRouter
|
./HttpRouter
|
||||||
$(CXX) -std=c++17 -fsanitize=address BloomFilter.cpp -o BloomFilter
|
$(CXX) -std=c++17 -fsanitize=address BloomFilter.cpp -o BloomFilter
|
||||||
./BloomFilter
|
./BloomFilter
|
||||||
|
$(CXX) -std=c++17 -fsanitize=address ExtensionsNegotiator.cpp -o ExtensionsNegotiator
|
||||||
|
./ExtensionsNegotiator
|
||||||
Reference in New Issue
Block a user