From 3eae7116e757e1d3cc4cbd03d5a9a0f38c0c7c19 Mon Sep 17 00:00:00 2001 From: Alex Hultman Date: Wed, 24 Feb 2021 09:22:04 +0100 Subject: [PATCH] Add WITH_LIBDEFLATE option, pass Autobahn --- .gitmodules | 3 +++ Makefile | 6 +++++ examples/EchoServer.cpp | 2 +- libdeflate | 1 + src/PerMessageDeflate.h | 54 +++++++++++++++++++++++++++++++++++++++++ src/WebSocketContext.h | 6 ++--- 6 files changed, 68 insertions(+), 4 deletions(-) create mode 160000 libdeflate diff --git a/.gitmodules b/.gitmodules index 50d8b70..a411652 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "fuzzing/libEpollFuzzer"] path = fuzzing/libEpollFuzzer url = https://github.com/uNetworking/libEpollFuzzer +[submodule "libdeflate"] + path = libdeflate + url = https://github.com/ebiggers/libdeflate diff --git a/Makefile b/Makefile index 0fcee21..a94f700 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,12 @@ ifeq ($(WITH_PROXY),1) override CXXFLAGS += -DUWS_WITH_PROXY endif +# WITH_LIBDEFLATE=1 enables fast paths for SHARED_COMPRESSOR and inflation +ifeq ($(WITH_LIBDEFLATE),1) + override CXXFLAGS += -I libdeflate -DUWS_USE_LIBDEFLATE + override LDFLAGS += libdeflate/libdeflate.a +endif + # WITH_OPENSSL=1 enables OpenSSL 1.1+ support ifeq ($(WITH_OPENSSL),1) # With problems on macOS, make sure to pass needed LDFLAGS required to find these diff --git a/examples/EchoServer.cpp b/examples/EchoServer.cpp index 66376a4..f7760f1 100644 --- a/examples/EchoServer.cpp +++ b/examples/EchoServer.cpp @@ -20,7 +20,7 @@ int main() { }).ws("/*", { /* Settings */ .compression = uWS::SHARED_COMPRESSOR, - .maxPayloadLength = 16 * 1024, + .maxPayloadLength = 16 * 1024 * 1024, .idleTimeout = 16, .maxBackpressure = 1 * 1024 * 1024, .closeOnBackpressureLimit = false, diff --git a/libdeflate b/libdeflate new file mode 160000 index 0000000..4d3c0f0 --- /dev/null +++ b/libdeflate @@ -0,0 +1 @@ +Subproject commit 4d3c0f00d55c3bfd7abe3f7ac3d99e3bd0f7f96b diff --git a/src/PerMessageDeflate.h b/src/PerMessageDeflate.h index a4148a2..dc5be5d 100644 --- a/src/PerMessageDeflate.h +++ b/src/PerMessageDeflate.h @@ -46,6 +46,11 @@ namespace uWS { #include #include +#ifdef UWS_USE_LIBDEFLATE +#include "libdeflate.h" +#include +#endif + namespace uWS { /* Do not compile this module if we don't want it */ @@ -77,14 +82,29 @@ struct ZlibContext { char *deflationBuffer; char *inflationBuffer; +#ifdef UWS_USE_LIBDEFLATE + libdeflate_decompressor *decompressor; + libdeflate_compressor *compressor; +#endif + ZlibContext() { deflationBuffer = (char *) malloc(LARGE_BUFFER_SIZE); inflationBuffer = (char *) malloc(LARGE_BUFFER_SIZE); + +#ifdef UWS_USE_LIBDEFLATE + decompressor = libdeflate_alloc_decompressor(); + compressor = libdeflate_alloc_compressor(7); +#endif } ~ZlibContext() { free(deflationBuffer); free(inflationBuffer); + +#ifdef UWS_USE_LIBDEFLATE + libdeflate_free_decompressor(decompressor); + libdeflate_free_compressor(compressor); +#endif } }; @@ -105,6 +125,22 @@ struct DeflationStream { /* Deflate and optionally reset. You must not deflate an empty string. */ std::string_view deflate(ZlibContext *zlibContext, std::string_view raw, bool reset) { + +#ifdef UWS_USE_LIBDEFLATE + /* Run a fast path in case of shared_compressor */ + if (reset) { + size_t written = 0; + static unsigned char buf[1024 + 1]; + + written = libdeflate_deflate_compress(zlibContext->compressor, raw.data(), raw.length(), buf, 1024); + + if (written) { + memcpy(&buf[written], "\x00", 1); + return std::string_view((char *) buf, written + 1); + } + } +#endif + /* Odd place to clear this one, fix */ zlibContext->dynamicDeflationBuffer.clear(); @@ -166,6 +202,24 @@ struct InflationStream { /* Zero length inflates are possible and valid */ std::optional inflate(ZlibContext *zlibContext, std::string_view compressed, size_t maxPayloadLength) { +#ifdef UWS_USE_LIBDEFLATE + /* Try fast path first */ + size_t written = 0; + static char buf[1024]; + + /* We have to pad 9 bytes and restore those bytes when done since 9 is more than 6 of next WebSocket message */ + char tmp[9]; + memcpy(tmp, (char *) compressed.data() + compressed.length(), 9); + memcpy((char *) compressed.data() + compressed.length(), "\x00\x00\xff\xff\x01\x00\x00\xff\xff", 9); + libdeflate_result res = libdeflate_deflate_decompress(zlibContext->decompressor, compressed.data(), compressed.length(), buf, 1024, &written); + memcpy((char *) compressed.data() + compressed.length(), tmp, 9); + + if (res == 0) { + /* Fast path wins */ + return std::string_view(buf, written); + } +#endif + /* We clear this one here, could be done better */ zlibContext->dynamicInflationBuffer.clear(); diff --git a/src/WebSocketContext.h b/src/WebSocketContext.h index cdd067b..3a24044 100644 --- a/src/WebSocketContext.h +++ b/src/WebSocketContext.h @@ -115,8 +115,8 @@ private: if (webSocketData->compressionStatus == WebSocketData::CompressionStatus::COMPRESSED_FRAME) { webSocketData->compressionStatus = WebSocketData::CompressionStatus::ENABLED; - // what's really the story here? - webSocketData->fragmentBuffer.append("...."); + /* 9 bytes of padding for libdeflate */ + webSocketData->fragmentBuffer.append("123456789"); LoopData *loopData = (LoopData *) us_loop_ext( us_socket_context_loop(SSL, @@ -124,7 +124,7 @@ private: ) ); - auto inflatedFrame = loopData->inflationStream->inflate(loopData->zlibContext, {webSocketData->fragmentBuffer.data(), webSocketData->fragmentBuffer.length() - 4}, webSocketContextData->maxPayloadLength); + auto inflatedFrame = loopData->inflationStream->inflate(loopData->zlibContext, {webSocketData->fragmentBuffer.data(), webSocketData->fragmentBuffer.length() - 9}, webSocketContextData->maxPayloadLength); if (!inflatedFrame.has_value()) { forceClose(webSocketState, s, ERR_TOO_BIG_MESSAGE_INFLATION); return true;