diff --git a/src/App.h b/src/App.h index 4da74e1..448332f 100644 --- a/src/App.h +++ b/src/App.h @@ -310,7 +310,7 @@ public: /* Initialize loop's deflate inflate streams */ if (!loopData->zlibContext) { loopData->zlibContext = new ZlibContext; - loopData->inflationStream = new InflationStream; + loopData->inflationStream = new InflationStream(CompressOptions::DEDICATED_DECOMPRESSOR); loopData->deflationStream = new DeflationStream(CompressOptions::DEDICATED_COMPRESSOR); } } diff --git a/src/HttpResponse.h b/src/HttpResponse.h index fd3e74c..bfbccce 100644 --- a/src/HttpResponse.h +++ b/src/HttpResponse.h @@ -236,14 +236,14 @@ public: CompressOptions compressOptions = CompressOptions::DISABLED; if (secWebSocketExtensions.length() && webSocketContextData->compression != DISABLED) { - /* We always want shared inflation, (or the full 15) */ + /* Make sure to map SHARED_DECOMPRESSOR to windowBits = 0, not 1 */ int wantedInflationWindow = 0; - if (webSocketContextData->compression & DEDICATED_DECOMPRESSOR) { - wantedInflationWindow = 15; + if ((webSocketContextData->compression & CompressOptions::_DECOMPRESSOR_MASK) != CompressOptions::SHARED_DECOMPRESSOR) { + wantedInflationWindow = (webSocketContextData->compression & CompressOptions::_DECOMPRESSOR_MASK) >> 12; } - /* Map from selected compressor */ - int wantedCompressionWindow = (webSocketContextData->compression & 0xFF00) >> 8; + /* Map from selected compressor (this automatically maps SHARED_COMPRESSOR to windowBits 0, not 1) */ + int wantedCompressionWindow = (webSocketContextData->compression & CompressOptions::_COMPRESSOR_MASK) >> 8; auto [negCompression, negCompressionWindow, negInflationWindow, negResponse] = negotiateCompression(true, wantedCompressionWindow, wantedInflationWindow, @@ -252,7 +252,7 @@ public: if (negCompression) { perMessageDeflate = true; - /* Map from windowBits to compressor */ + /* Map from negotiated windowBits to compressor and decompressor */ if (negCompressionWindow == 0) { compressOptions = CompressOptions::SHARED_COMPRESSOR; } else { @@ -261,11 +261,18 @@ public: /* If we are dedicated and have the 3kb then correct any 4kb to 3kb, * (they both share the windowBits = 9) */ - if (webSocketContextData->compression == DEDICATED_COMPRESSOR_3KB) { + if (webSocketContextData->compression & DEDICATED_COMPRESSOR_3KB) { compressOptions = DEDICATED_COMPRESSOR_3KB; } } + /* Here we modify the above compression with negotiated decompressor */ + if (negInflationWindow == 0) { + compressOptions = CompressOptions(compressOptions | CompressOptions::SHARED_DECOMPRESSOR); + } else { + compressOptions = CompressOptions(compressOptions | (negInflationWindow << 12)); + } + writeHeader("Sec-WebSocket-Extensions", negResponse); } } diff --git a/src/PerMessageDeflate.h b/src/PerMessageDeflate.h index 8f338e4..8ca82d9 100644 --- a/src/PerMessageDeflate.h +++ b/src/PerMessageDeflate.h @@ -21,17 +21,34 @@ #define UWS_PERMESSAGEDEFLATE_H #include +#include /* We always define these options no matter if ZLIB is enabled or not */ namespace uWS { - /* Compressor mode is 16 low bit where HIGH8(windowBits), LOW8(memLevel) */ - enum CompressOptions : uint32_t { + /* Compressor mode is 12 lowest bits where HIGH4(windowBits), LOW8(memLevel). + * Decompressor mode is 4 highest bits (windowBits). + * If compressor or decompressor bits are 1, then they are shared. + * If everything is just simply 0, then everything is disabled. */ + enum CompressOptions : uint16_t { + /* These are not actual compression options */ + _COMPRESSOR_MASK = 0x0FFF, + _DECOMPRESSOR_MASK = 0xF000, + /* Disabled, shared, shared are "special" values */ DISABLED = 0, - /* Highest bit is shared compressor */ - SHARED_COMPRESSOR = (uint32_t)1 << (uint32_t)31, - /* Second highest bit is DEDICATED_DECOMPRESSOR */ - DEDICATED_DECOMPRESSOR = (uint32_t)1 << (uint32_t)30, - /* Lowest 16 bit describe compressor */ + SHARED_COMPRESSOR = 1, + SHARED_DECOMPRESSOR = 1 << 12, + /* Highest 4 bits describe decompressor */ + DEDICATED_DECOMPRESSOR_32KB = 15 << 12, + DEDICATED_DECOMPRESSOR_16KB = 14 << 12, + DEDICATED_DECOMPRESSOR_8KB = 13 << 12, + DEDICATED_DECOMPRESSOR_4KB = 12 << 12, + DEDICATED_DECOMPRESSOR_2KB = 11 << 12, + DEDICATED_DECOMPRESSOR_1KB = 10 << 12, + DEDICATED_DECOMPRESSOR_512B = 9 << 12, + /* Same as 32kb */ + DEDICATED_DECOMPRESSOR = 15 << 12, + + /* Lowest 12 bit describe compressor */ DEDICATED_COMPRESSOR_3KB = 9 << 8 | 1, DEDICATED_COMPRESSOR_4KB = 9 << 8 | 2, DEDICATED_COMPRESSOR_8KB = 10 << 8 | 3, @@ -66,6 +83,8 @@ struct InflationStream { std::optional inflate(ZlibContext * /*zlibContext*/, std::string_view compressed, size_t maxPayloadLength, bool /*reset*/) { return compressed.substr(0, std::min(maxPayloadLength, compressed.length())); } + InflationStream(CompressOptions /*compressOptions*/) { + } }; struct DeflationStream { std::string_view deflate(ZlibContext * /*zlibContext*/, std::string_view raw, bool /*reset*/) { @@ -196,8 +215,10 @@ struct DeflationStream { struct InflationStream { z_stream inflationStream = {}; - InflationStream() { - inflateInit2(&inflationStream, -15); + InflationStream(CompressOptions compressOptions) { + /* Inflation windowBits are the top 4 bits of the 16 bit compressOptions */ + //printf("%d\n", -(compressOptions >> 12)); + inflateInit2(&inflationStream, -(compressOptions >> 12)); } ~InflationStream() { @@ -225,6 +246,11 @@ struct InflationStream { } #endif + /* Append tail to chunk */ + unsigned char tail[4] = {0x00, 0x00, 0xff, 0xff}; + memcpy((char *)compressed.data() + compressed.length(), tail, 4); + compressed = {compressed.data(), compressed.length() + 4}; + /* We clear this one here, could be done better */ zlibContext->dynamicInflationBuffer.clear(); diff --git a/src/WebSocketContext.h b/src/WebSocketContext.h index f9fad26..c5fd75b 100644 --- a/src/WebSocketContext.h +++ b/src/WebSocketContext.h @@ -122,7 +122,7 @@ private: if (webSocketData->compressionStatus == WebSocketData::CompressionStatus::COMPRESSED_FRAME) { webSocketData->compressionStatus = WebSocketData::CompressionStatus::ENABLED; - /* 9 bytes of padding for libdeflate */ + /* 9 bytes of padding for libdeflate, 4 for zlib */ webSocketData->fragmentBuffer.append("123456789"); LoopData *loopData = (LoopData *) us_loop_ext( diff --git a/src/WebSocketData.h b/src/WebSocketData.h index 1506a51..21e96a7 100644 --- a/src/WebSocketData.h +++ b/src/WebSocketData.h @@ -56,12 +56,13 @@ public: compressionStatus = perMessageDeflate ? ENABLED : DISABLED; /* Initialize the dedicated sliding window(s) */ - if (perMessageDeflate && (0 == (compressOptions & CompressOptions::SHARED_COMPRESSOR))) { - deflationStream = new DeflationStream(compressOptions); - } - - if (perMessageDeflate && (compressOptions & CompressOptions::DEDICATED_DECOMPRESSOR)) { - inflationStream = new InflationStream(); + if (perMessageDeflate) { + if ((compressOptions & CompressOptions::_COMPRESSOR_MASK) != CompressOptions::SHARED_COMPRESSOR) { + deflationStream = new DeflationStream(compressOptions); + } + if ((compressOptions & CompressOptions::_DECOMPRESSOR_MASK) != CompressOptions::SHARED_DECOMPRESSOR) { + inflationStream = new InflationStream(compressOptions); + } } }