@@ -24,6 +24,7 @@ static const char *TAG = "transport_ws";
2424
2525#define WS_BUFFER_SIZE CONFIG_WS_BUFFER_SIZE
2626#define WS_FIN 0x80
27+ #define WS_COMPRESSED 0x40
2728#define WS_OPCODE_CONT 0x00
2829#define WS_OPCODE_TEXT 0x01
2930#define WS_OPCODE_BINARY 0x02
@@ -56,6 +57,7 @@ typedef struct {
5657 int payload_len ; /*!< Total length of the payload */
5758 int bytes_remaining ; /*!< Bytes left to read of the payload */
5859 bool header_received ; /*!< Flag to indicate that a new message header was received */
60+ bool compressed ; /*!< Per-message deflate compress flag (RSV1) */
5961} ws_transport_frame_state_t ;
6062
6163typedef struct {
@@ -75,6 +77,11 @@ typedef struct {
7577 char * redir_host ;
7678 char * response_header ;
7779 size_t response_header_len ;
80+ bool per_msg_compress ;
81+ int per_msg_client_deflate_window_bit ;
82+ int per_msg_server_deflate_window_bit ;
83+ bool per_msg_server_no_ctx_takeover ;
84+ bool per_msg_client_no_ctx_takeover ;
7885} transport_ws_t ;
7986
8087/**
@@ -201,6 +208,32 @@ static int ws_connect(esp_transport_handle_t t, const char *host, int port, int
201208#endif
202209
203210 size_t outlen = 0 ;
211+ char extension_header [160 ] = { 0 };
212+ if (ws -> per_msg_compress ) {
213+ int offset = snprintf (extension_header , sizeof (extension_header ), "Sec-WebSocket-Extensions: permessage-deflate" );
214+ if (ws -> per_msg_client_no_ctx_takeover ) {
215+ offset += snprintf (extension_header + offset , sizeof (extension_header ) - offset , "; client_no_context_takeover" );
216+ }
217+
218+ if (ws -> per_msg_server_no_ctx_takeover ) {
219+ offset += snprintf (extension_header + offset , sizeof (extension_header ) - offset , "; server_no_context_takeover" );
220+ }
221+
222+ // If this is 0 then it means to let server decide the client window bit
223+ if (ws -> per_msg_client_deflate_window_bit != 0 ) {
224+ offset += snprintf (extension_header + offset , sizeof (extension_header ) - offset , "; client_max_window_bits=%d" , ws -> per_msg_client_deflate_window_bit );
225+ } else {
226+ offset += snprintf (extension_header + offset , sizeof (extension_header ) - offset , "; client_max_window_bits" );
227+ }
228+
229+ // If this is 0 then it means to let server decide the server window bit
230+ if (ws -> per_msg_server_deflate_window_bit != 0 ) {
231+ offset += snprintf (extension_header + offset , sizeof (extension_header ) - offset , "; server_max_window_bits=%d" , ws -> per_msg_server_deflate_window_bit );
232+ }
233+
234+ snprintf (extension_header + offset , sizeof (extension_header ) - offset , "\r\n" );
235+ }
236+
204237 esp_crypto_base64_encode (client_key , sizeof (client_key ), & outlen , random_key , sizeof (random_key ));
205238 int len = snprintf (ws -> buffer , WS_BUFFER_SIZE ,
206239 "GET %s HTTP/1.1\r\n"
@@ -209,10 +242,12 @@ static int ws_connect(esp_transport_handle_t t, const char *host, int port, int
209242 "User-Agent: %s\r\n"
210243 "Upgrade: websocket\r\n"
211244 "Sec-WebSocket-Version: 13\r\n"
212- "Sec-WebSocket-Key: %s\r\n" ,
245+ "Sec-WebSocket-Key: %s\r\n"
246+ "%s" , // For "Sec-WebSocket-Extensions"
213247 ws -> path ,
214248 host , port , user_agent_ptr ,
215- client_key );
249+ client_key ,
250+ extension_header );
216251 if (len <= 0 || len >= WS_BUFFER_SIZE ) {
217252 ESP_LOGE (TAG , "Error in request generation, desired request len: %d, buffer size: %d" , len , WS_BUFFER_SIZE );
218253 return -1 ;
@@ -306,6 +341,9 @@ static int ws_connect(esp_transport_handle_t t, const char *host, int port, int
306341 }
307342 header_cursor += strlen ("\r\n" );
308343
344+ // If compression was requested, we need to check server response
345+ bool pmd_negotiated = false;
346+
309347 while (header_cursor < delim_ptr ){
310348 const char * end_of_line = strnstr (header_cursor , "\r\n" , header_len - (header_cursor - ws -> buffer ));
311349 if (!end_of_line ){
@@ -332,6 +370,53 @@ static int ws_connect(esp_transport_handle_t t, const char *host, int port, int
332370 server_key = header_cursor + header_sec_websocket_accept_len ;
333371 server_key_len = line_len - header_sec_websocket_accept_len ;
334372 }
373+ // Check for Sec-WebSocket-Extensions header
374+ else if (ws -> per_msg_compress && line_len >= strlen ("Sec-WebSocket-Extensions: " ) && !strncasecmp (header_cursor , "Sec-WebSocket-Extensions: " , strlen ("Sec-WebSocket-Extensions: " ))) {
375+ const char * ext_params = header_cursor + strlen ("Sec-WebSocket-Extensions: " );
376+ int ext_params_len = line_len - strlen ("Sec-WebSocket-Extensions: " );
377+ ESP_LOGD (TAG , "Found Sec-WebSocket-Extensions: %.*s" , ext_params_len , ext_params );
378+
379+ if (strcasestr (ext_params , "permessage-deflate" )) {
380+ pmd_negotiated = true;
381+
382+ // Server must agree to context takeover settings
383+ if (!strcasestr (ext_params , "server_no_context_takeover" )) {
384+ ws -> per_msg_server_no_ctx_takeover = false;
385+ }
386+ if (!strcasestr (ext_params , "client_no_context_takeover" )) {
387+ ws -> per_msg_client_no_ctx_takeover = false;
388+ }
389+
390+ const char * smwb_str = "server_max_window_bits=" ;
391+ const char * found = strcasestr (ext_params , smwb_str );
392+ if (found ) {
393+ char * endptr ;
394+ long smwb = strtol (found + strlen (smwb_str ), & endptr , 10 );
395+ if (smwb < 8 || smwb > 15 ) {
396+ ESP_LOGE (TAG , "compression: Server Max Window Bits is invalid: %ld" , smwb );
397+ return -1 ;
398+ }
399+
400+ ws -> per_msg_server_deflate_window_bit = (int )smwb ;
401+ } else {
402+ ws -> per_msg_server_deflate_window_bit = 15 ;
403+ }
404+
405+ const char * cmwb_str = "client_max_window_bits=" ;
406+ found = strcasestr (ext_params , cmwb_str );
407+ if (found ) {
408+ char * endptr ;
409+ long cmwb = strtol (found + strlen (cmwb_str ), & endptr , 10 );
410+
411+ if (cmwb < 8 || cmwb > 15 ) {
412+ ESP_LOGE (TAG , "compression: Client Max Window Bits is invalid: %ld" , cmwb );
413+ return -1 ;
414+ }
415+
416+ ws -> per_msg_client_deflate_window_bit = (int )cmwb ;
417+ }
418+ }
419+ }
335420 else if (ws -> header_hook ) {
336421 ws -> header_hook (ws -> header_user_context , header_cursor , line_len );
337422 }
@@ -349,6 +434,10 @@ static int ws_connect(esp_transport_handle_t t, const char *host, int port, int
349434 header_cursor += strlen ("\r\n" );
350435 }
351436
437+ if (ws -> per_msg_compress && !pmd_negotiated ) {
438+ ws -> per_msg_compress = false;
439+ }
440+
352441 if (WS_HTTP_TEMPORARY_REDIRECT (ws -> http_status_code ) || WS_HTTP_PERMANENT_REDIRECT (ws -> http_status_code )) {
353442 if (location == NULL || location_len <= 0 ) {
354443 ESP_LOGE (TAG , "Location header not found" );
@@ -575,6 +664,7 @@ static int ws_read_header(esp_transport_handle_t t, char *buffer, int len, int t
575664 ws -> frame_state .header_received = true;
576665 ws -> frame_state .fin = (* data_ptr & 0x80 ) != 0 ;
577666 ws -> frame_state .opcode = (* data_ptr & 0x0F );
667+ ws -> frame_state .compressed = (* data_ptr & 0x40 ) != 0 ; // RSV1 bit in the header
578668 data_ptr ++ ;
579669 mask = ((* data_ptr >> 7 ) & 0x01 );
580670 payload_len = (* data_ptr & 0x7F );
@@ -979,14 +1069,65 @@ esp_err_t esp_transport_ws_set_config(esp_transport_handle_t t, const esp_transp
9791069 }
9801070
9811071 ws -> propagate_control_frames = config -> propagate_control_frames ;
1072+ ws -> per_msg_compress = config -> per_msg_compress ;
1073+ ws -> per_msg_client_no_ctx_takeover = config -> per_msg_client_no_ctx_takeover ;
1074+ ws -> per_msg_server_no_ctx_takeover = config -> per_msg_server_no_ctx_takeover ;
1075+
1076+ if (config -> per_msg_client_deflate_window_bit < 8 || config -> per_msg_client_deflate_window_bit > 15 ) {
1077+ ws -> per_msg_client_deflate_window_bit = 0 ;
1078+ } else {
1079+ ws -> per_msg_client_deflate_window_bit = config -> per_msg_client_deflate_window_bit ;
1080+ }
1081+
1082+ if (config -> per_msg_server_deflate_window_bit < 8 || config -> per_msg_server_deflate_window_bit > 15 ) {
1083+ ws -> per_msg_server_deflate_window_bit = 0 ;
1084+ } else {
1085+ ws -> per_msg_server_deflate_window_bit = config -> per_msg_server_deflate_window_bit ;
1086+ }
9821087
9831088 return err ;
9841089}
9851090
9861091bool esp_transport_ws_get_fin_flag (esp_transport_handle_t t )
9871092{
988- transport_ws_t * ws = esp_transport_get_context_data (t );
989- return ws -> frame_state .fin ;
1093+ transport_ws_t * ws = esp_transport_get_context_data (t );
1094+ return ws -> frame_state .fin ;
1095+ }
1096+
1097+ bool esp_transport_ws_get_rsv1_flag (esp_transport_handle_t t )
1098+ {
1099+ transport_ws_t * ws = esp_transport_get_context_data (t );
1100+ return ws -> frame_state .compressed ;
1101+ }
1102+
1103+ bool esp_transport_ws_get_per_msg_compress (esp_transport_handle_t t )
1104+ {
1105+ transport_ws_t * ws = esp_transport_get_context_data (t );
1106+ return ws -> per_msg_compress ;
1107+ }
1108+
1109+ int esp_transport_ws_get_per_msg_client_deflate_window_bit (esp_transport_handle_t t )
1110+ {
1111+ transport_ws_t * ws = esp_transport_get_context_data (t );
1112+ return ws -> per_msg_client_deflate_window_bit ;
1113+ }
1114+
1115+ int esp_transport_ws_get_per_msg_server_deflate_window_bit (esp_transport_handle_t t )
1116+ {
1117+ transport_ws_t * ws = esp_transport_get_context_data (t );
1118+ return ws -> per_msg_server_deflate_window_bit ;
1119+ }
1120+
1121+ bool esp_transport_ws_get_per_msg_server_no_ctx_takeover (esp_transport_handle_t t )
1122+ {
1123+ transport_ws_t * ws = esp_transport_get_context_data (t );
1124+ return ws -> per_msg_server_no_ctx_takeover && ws -> per_msg_compress ;
1125+ }
1126+
1127+ bool esp_transport_ws_get_per_msg_client_no_ctx_takeover (esp_transport_handle_t t )
1128+ {
1129+ transport_ws_t * ws = esp_transport_get_context_data (t );
1130+ return ws -> per_msg_client_no_ctx_takeover && ws -> per_msg_compress ;
9901131}
9911132
9921133int esp_transport_ws_get_upgrade_request_status (esp_transport_handle_t t )
0 commit comments