From b8e4b6f8ba302b87938a792521732d4ca530d9e0 Mon Sep 17 00:00:00 2001 From: jhugard Date: Fri, 3 Apr 2026 10:16:15 -0700 Subject: [PATCH 1/2] Add client_no_context_takeover regression test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/extension/permessage_deflate.cpp | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/extension/permessage_deflate.cpp b/test/extension/permessage_deflate.cpp index 805afcc3c..5f4e38203 100644 --- a/test/extension/permessage_deflate.cpp +++ b/test/extension/permessage_deflate.cpp @@ -54,6 +54,18 @@ struct ext_vars { namespace pmde = websocketpp::extensions::permessage_deflate::error; namespace pmd_mode = websocketpp::extensions::permessage_deflate::mode; +static std::string make_deterministic_message(size_t size) { + std::string message(size, '\0'); + unsigned int state = 0x12345678u; + + for (size_t i = 0; i < size; ++i) { + state = state * 1664525u + 1013904223u; + message[i] = static_cast(state >> 24); + } + + return message; +} + // Ensure the disabled extension behaves appropriately disabled BOOST_AUTO_TEST_CASE( disabled_is_disabled ) { @@ -703,6 +715,57 @@ BOOST_AUTO_TEST_CASE( compress_data_no_context_takeover ) { BOOST_CHECK_EQUAL( compress_out1, compress_out2 ); } +BOOST_AUTO_TEST_CASE( compress_data_client_no_context_takeover_offer_without_response_param ) { + ext_vars v; + enabled_type second_message_server; + + std::string offer = v.extc.generate_offer(); + std::string compress_in = make_deterministic_message(4096); + std::string compress_out1; + std::string compress_out2; + std::string decompress_out1; + std::string decompress_out2; + websocketpp::http::attribute_list response_without_client_no_context_takeover; + + BOOST_CHECK( offer.find("client_no_context_takeover") != std::string::npos ); + + v.ec = v.extc.validate_offer(response_without_client_no_context_takeover); + BOOST_REQUIRE_EQUAL( v.ec, websocketpp::lib::error_code() ); + + v.ec = v.extc.init(false); + BOOST_REQUIRE_EQUAL( v.ec, websocketpp::lib::error_code() ); + + v.ec = v.exts.init(true); + BOOST_REQUIRE_EQUAL( v.ec, websocketpp::lib::error_code() ); + + v.ec = second_message_server.init(true); + BOOST_REQUIRE_EQUAL( v.ec, websocketpp::lib::error_code() ); + + v.ec = v.extc.compress(compress_in,compress_out1); + BOOST_REQUIRE_EQUAL( v.ec, websocketpp::lib::error_code() ); + + v.ec = v.extc.compress(compress_in,compress_out2); + BOOST_REQUIRE_EQUAL( v.ec, websocketpp::lib::error_code() ); + + v.ec = v.exts.decompress( + reinterpret_cast(compress_out1.data()), + compress_out1.size(), + decompress_out1 + ); + BOOST_REQUIRE_EQUAL( v.ec, websocketpp::lib::error_code() ); + BOOST_CHECK( decompress_out1 == compress_in ); + + v.ec = second_message_server.decompress( + reinterpret_cast(compress_out2.data()), + compress_out2.size(), + decompress_out2 + ); + BOOST_CHECK_EQUAL( v.ec, websocketpp::lib::error_code() ); + if (!v.ec) { + BOOST_CHECK( decompress_out2 == compress_in ); + } +} + BOOST_AUTO_TEST_CASE( compress_empty ) { ext_vars v; From 6ed4712244958d8ff8a8a29c11088946929410fb Mon Sep 17 00:00:00 2001 From: jhugard Date: Fri, 3 Apr 2026 10:27:37 -0700 Subject: [PATCH 2/2] Honor client_no_context_takeover offer Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- websocketpp/extensions/permessage_deflate/enabled.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/websocketpp/extensions/permessage_deflate/enabled.hpp b/websocketpp/extensions/permessage_deflate/enabled.hpp index d05403a8a..cf684ad05 100644 --- a/websocketpp/extensions/permessage_deflate/enabled.hpp +++ b/websocketpp/extensions/permessage_deflate/enabled.hpp @@ -221,6 +221,7 @@ class enabled { : m_enabled(false) , m_server_no_context_takeover(false) , m_client_no_context_takeover(false) + , m_client_no_context_takeover_offered(false) , m_server_max_window_bits(15) , m_client_max_window_bits(15) , m_server_max_window_bits_mode(mode::accept) @@ -307,7 +308,8 @@ class enabled { 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_client_no_context_takeover || + m_client_no_context_takeover_offered) && !is_server)) { m_flush = Z_FULL_FLUSH; } else { @@ -481,6 +483,7 @@ class enabled { * @return A WebSocket extension offer string for this extension */ std::string generate_offer() const { + m_client_no_context_takeover_offered = true; // TODO: this should be dynamically generated based on user settings return "permessage-deflate; client_no_context_takeover; client_max_window_bits"; } @@ -796,6 +799,7 @@ class enabled { bool m_enabled; bool m_server_no_context_takeover; bool m_client_no_context_takeover; + mutable bool m_client_no_context_takeover_offered; uint8_t m_server_max_window_bits; uint8_t m_client_max_window_bits; mode::value m_server_max_window_bits_mode;