• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * libwebsockets - small server side websockets and web server implementation
3  *
4  * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22  * IN THE SOFTWARE.
23  */
24 
25 #include "private-lib-core.h"
26 #include "extension-permessage-deflate.h"
27 #include <stdio.h>
28 #include <string.h>
29 #include <assert.h>
30 
31 #define LWS_ZLIB_MEMLEVEL 8
32 
33 const struct lws_ext_options lws_ext_pm_deflate_options[] = {
34 	/* public RFC7692 settings */
35 	{ "server_no_context_takeover", EXTARG_NONE },
36 	{ "client_no_context_takeover", EXTARG_NONE },
37 	{ "server_max_window_bits",	EXTARG_OPT_DEC },
38 	{ "client_max_window_bits",	EXTARG_OPT_DEC },
39 	/* ones only user code can set */
40 	{ "rx_buf_size",		EXTARG_DEC },
41 	{ "tx_buf_size",		EXTARG_DEC },
42 	{ "compression_level",		EXTARG_DEC },
43 	{ "mem_level",			EXTARG_DEC },
44 	{ NULL, 0 }, /* sentinel */
45 };
46 
47 static void
lws_extension_pmdeflate_restrict_args(struct lws * wsi,struct lws_ext_pm_deflate_priv * priv)48 lws_extension_pmdeflate_restrict_args(struct lws *wsi,
49 				      struct lws_ext_pm_deflate_priv *priv)
50 {
51 	int n, extra;
52 
53 	/* cap the RX buf at the nearest power of 2 to protocol rx buf */
54 
55 	n = (int)wsi->a.context->pt_serv_buf_size;
56 	if (wsi->a.protocol->rx_buffer_size)
57 		n = (int)wsi->a.protocol->rx_buffer_size;
58 
59 	extra = 7;
60 	while (n >= 1 << (extra + 1))
61 		extra++;
62 
63 	if (extra < priv->args[PMD_RX_BUF_PWR2]) {
64 		priv->args[PMD_RX_BUF_PWR2] = (unsigned char)extra;
65 		lwsl_wsi_info(wsi, " Capping pmd rx to %d", 1 << extra);
66 	}
67 }
68 
69 static unsigned char trail[] = { 0, 0, 0xff, 0xff };
70 
71 LWS_VISIBLE int
lws_extension_callback_pm_deflate(struct lws_context * context,const struct lws_extension * ext,struct lws * wsi,enum lws_extension_callback_reasons reason,void * user,void * in,size_t len)72 lws_extension_callback_pm_deflate(struct lws_context *context,
73 				  const struct lws_extension *ext,
74 				  struct lws *wsi,
75 				  enum lws_extension_callback_reasons reason,
76 				  void *user, void *in, size_t len)
77 {
78 	struct lws_ext_pm_deflate_priv *priv =
79 				     (struct lws_ext_pm_deflate_priv *)user;
80 	struct lws_ext_pm_deflate_rx_ebufs *pmdrx =
81 				(struct lws_ext_pm_deflate_rx_ebufs *)in;
82 	struct lws_ext_option_arg *oa;
83 	int n, ret = 0, was_fin = 0, m;
84 	unsigned int pen = 0;
85 	int penbits = 0;
86 
87 	switch (reason) {
88 	case LWS_EXT_CB_NAMED_OPTION_SET:
89 		oa = in;
90 		if (!oa->option_name)
91 			break;
92 		lwsl_wsi_ext(wsi, "named option set: %s", oa->option_name);
93 		for (n = 0; n < (int)LWS_ARRAY_SIZE(lws_ext_pm_deflate_options);
94 		     n++)
95 			if (!strcmp(lws_ext_pm_deflate_options[n].name,
96 				    oa->option_name))
97 				break;
98 
99 		if (n == (int)LWS_ARRAY_SIZE(lws_ext_pm_deflate_options))
100 			break;
101 		oa->option_index = n;
102 
103 		/* fallthru */
104 
105 	case LWS_EXT_CB_OPTION_SET:
106 		oa = in;
107 		lwsl_wsi_ext(wsi, "option set: idx %d, %s, len %d",
108 			 oa->option_index, oa->start, oa->len);
109 		if (oa->start)
110 			priv->args[oa->option_index] = (unsigned char)atoi(oa->start);
111 		else
112 			priv->args[oa->option_index] = 1;
113 
114 		if (priv->args[PMD_CLIENT_MAX_WINDOW_BITS] == 8)
115 			priv->args[PMD_CLIENT_MAX_WINDOW_BITS] = 9;
116 
117 		lws_extension_pmdeflate_restrict_args(wsi, priv);
118 		break;
119 
120 	case LWS_EXT_CB_OPTION_CONFIRM:
121 		if (priv->args[PMD_SERVER_MAX_WINDOW_BITS] < 8 ||
122 		    priv->args[PMD_SERVER_MAX_WINDOW_BITS] > 15 ||
123 		    priv->args[PMD_CLIENT_MAX_WINDOW_BITS] < 8 ||
124 		    priv->args[PMD_CLIENT_MAX_WINDOW_BITS] > 15)
125 			return -1;
126 		break;
127 
128 	case LWS_EXT_CB_CLIENT_CONSTRUCT:
129 	case LWS_EXT_CB_CONSTRUCT:
130 
131 		n = (int)context->pt_serv_buf_size;
132 		if (wsi->a.protocol->rx_buffer_size)
133 			n = (int)wsi->a.protocol->rx_buffer_size;
134 
135 		if (n < 128) {
136 			lwsl_wsi_info(wsi, " permessage-deflate requires the protocol "
137 				  "(%s) to have an RX buffer >= 128",
138 				  wsi->a.protocol->name);
139 			return -1;
140 		}
141 
142 		/* fill in **user */
143 		priv = lws_zalloc(sizeof(*priv), "pmd priv");
144 		*((void **)user) = priv;
145 		lwsl_wsi_ext(wsi, "LWS_EXT_CB_*CONSTRUCT");
146 		memset(priv, 0, sizeof(*priv));
147 
148 		/* fill in pointer to options list */
149 		if (in)
150 			*((const struct lws_ext_options **)in) =
151 					lws_ext_pm_deflate_options;
152 
153 		/* fallthru */
154 
155 	case LWS_EXT_CB_OPTION_DEFAULT:
156 
157 		/* set the public, RFC7692 defaults... */
158 
159 		priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER] = 0,
160 		priv->args[PMD_CLIENT_NO_CONTEXT_TAKEOVER] = 0;
161 		priv->args[PMD_SERVER_MAX_WINDOW_BITS] = 15;
162 		priv->args[PMD_CLIENT_MAX_WINDOW_BITS] = 15;
163 
164 		/* ...and the ones the user code can override */
165 
166 		priv->args[PMD_RX_BUF_PWR2] = 10; /* ie, 1024 */
167 		priv->args[PMD_TX_BUF_PWR2] = 10; /* ie, 1024 */
168 		priv->args[PMD_COMP_LEVEL] = 1;
169 		priv->args[PMD_MEM_LEVEL] = 8;
170 
171 		lws_extension_pmdeflate_restrict_args(wsi, priv);
172 		break;
173 
174 	case LWS_EXT_CB_DESTROY:
175 		lwsl_wsi_ext(wsi, "LWS_EXT_CB_DESTROY");
176 		lws_free(priv->buf_rx_inflated);
177 		lws_free(priv->buf_tx_deflated);
178 		if (priv->rx_init)
179 			(void)inflateEnd(&priv->rx);
180 		if (priv->tx_init)
181 			(void)deflateEnd(&priv->tx);
182 		lws_free(priv);
183 
184 		return ret;
185 
186 
187 	case LWS_EXT_CB_PAYLOAD_RX:
188 		/*
189 		 * ie, we are INFLATING
190 		 */
191 		lwsl_wsi_ext(wsi, " LWS_EXT_CB_PAYLOAD_RX: in %d, existing in %d",
192 			 pmdrx->eb_in.len, priv->rx.avail_in);
193 
194 		/*
195 		 * If this frame is not marked as compressed,
196 		 * there is nothing we should do with it
197 		 */
198 
199 		if (!(wsi->ws->rsv_first_msg & 0x40) || (wsi->ws->opcode & 8))
200 			/*
201 			 * This is a bit different than DID_NOTHING... we have
202 			 * identified using ext-private bits in the packet, or
203 			 * by it being a control fragment that we SHOULD not do
204 			 * anything to it, parent should continue as if we
205 			 * processed it
206 			 */
207 			return PMDR_NOTHING_WE_SHOULD_DO;
208 
209 		/*
210 		 * we shouldn't come back in here if we already applied the
211 		 * trailer for this compressed packet
212 		 */
213 		if (!wsi->ws->pmd_trailer_application)
214 			return PMDR_DID_NOTHING;
215 
216 		pmdrx->eb_out.len = 0;
217 
218 		lwsl_wsi_ext(wsi, "LWS_EXT_CB_PAYLOAD_RX: in %d, "
219 			 "existing avail in %d, pkt fin: %d",
220 			 pmdrx->eb_in.len, priv->rx.avail_in, wsi->ws->final);
221 
222 		/* if needed, initialize the inflator */
223 
224 		if (!priv->rx_init) {
225 			if (inflateInit2(&priv->rx,
226 			     -priv->args[PMD_SERVER_MAX_WINDOW_BITS]) != Z_OK) {
227 				lwsl_wsi_err(wsi, "iniflateInit failed");
228 				return PMDR_FAILED;
229 			}
230 			priv->rx_init = 1;
231 			if (!priv->buf_rx_inflated)
232 				priv->buf_rx_inflated = lws_malloc(
233 					(unsigned int)(LWS_PRE + 7 + 5 +
234 					    (1 << priv->args[PMD_RX_BUF_PWR2])),
235 					    "pmd rx inflate buf");
236 			if (!priv->buf_rx_inflated) {
237 				lwsl_wsi_err(wsi, "OOM");
238 				return PMDR_FAILED;
239 			}
240 		}
241 
242 #if 0
243 		/*
244 		 * don't give us new input while we still work through
245 		 * the last input
246 		 */
247 
248 		if (priv->rx.avail_in && pmdrx->eb_in.token &&
249 					 pmdrx->eb_in.len) {
250 			lwsl_wsi_warn(wsi, "priv->rx.avail_in %d while getting new in",
251 					priv->rx.avail_in);
252 	//		assert(0);
253 		}
254 #endif
255 		if (!priv->rx.avail_in && pmdrx->eb_in.token && pmdrx->eb_in.len) {
256 			priv->rx.next_in = (unsigned char *)pmdrx->eb_in.token;
257 			priv->rx.avail_in = (uInt)pmdrx->eb_in.len;
258 		}
259 
260 		priv->rx.next_out = priv->buf_rx_inflated + LWS_PRE;
261 		pmdrx->eb_out.token = priv->rx.next_out;
262 		priv->rx.avail_out = (uInt)(1 << priv->args[PMD_RX_BUF_PWR2]);
263 
264 		/* so... if...
265 		 *
266 		 *  - he has no remaining input content for this message, and
267 		 *
268 		 *  - and this is the final fragment, and
269 		 *
270 		 *  - we used everything that could be drained on the input side
271 		 *
272 		 * ...then put back the 00 00 FF FF the sender stripped as our
273 		 * input to zlib
274 		 */
275 		if (!priv->rx.avail_in &&
276 		    wsi->ws->final &&
277 		    !wsi->ws->rx_packet_length &&
278 		    wsi->ws->pmd_trailer_application) {
279 			lwsl_wsi_ext(wsi, "trailer apply 1");
280 			was_fin = 1;
281 			wsi->ws->pmd_trailer_application = 0;
282 			priv->rx.next_in = trail;
283 			priv->rx.avail_in = sizeof(trail);
284 		}
285 
286 		/*
287 		 * if after all that there's nothing pending and nothing to give
288 		 * him right now, bail without having done anything
289 		 */
290 
291 		if (!priv->rx.avail_in)
292 			return PMDR_DID_NOTHING;
293 
294 		n = inflate(&priv->rx, was_fin ? Z_SYNC_FLUSH : Z_NO_FLUSH);
295 		lwsl_wsi_ext(wsi, "inflate ret %d, avi %d, avo %d, wsifinal %d", n,
296 			 priv->rx.avail_in, priv->rx.avail_out, wsi->ws->final);
297 		switch (n) {
298 		case Z_NEED_DICT:
299 		case Z_STREAM_ERROR:
300 		case Z_DATA_ERROR:
301 		case Z_MEM_ERROR:
302 			lwsl_wsi_err(wsi, "zlib error inflate %d: \"%s\"",
303 				  n, priv->rx.msg);
304 			return PMDR_FAILED;
305 		}
306 
307 		/*
308 		 * track how much input was used, and advance it
309 		 */
310 
311 		/* COV says we can overflow if "eb_in.len == 0 and rx->avail_in == 4" */
312 		if ((unsigned int)priv->rx.avail_in > (unsigned int)pmdrx->eb_in.len) {
313 			lwsl_wsi_err(wsi, "rx buffer underflow");
314 			return PMDR_FAILED;
315 		}
316 
317 		pmdrx->eb_in.token = pmdrx->eb_in.token +
318 				         ((unsigned int)pmdrx->eb_in.len - (unsigned int)priv->rx.avail_in);
319 		pmdrx->eb_in.len = (int)priv->rx.avail_in;
320 
321 		lwsl_wsi_debug(wsi, "%d %d %d %d %d",
322 				priv->rx.avail_in,
323 				wsi->ws->final,
324 				(int)wsi->ws->rx_packet_length,
325 				was_fin,
326 				wsi->ws->pmd_trailer_application);
327 
328 		if (!priv->rx.avail_in &&
329 		    wsi->ws->final &&
330 		    !wsi->ws->rx_packet_length &&
331 		    !was_fin &&
332 		    wsi->ws->pmd_trailer_application) {
333 			lwsl_wsi_ext(wsi, "RX trailer apply 2");
334 
335 			/* we overallocated just for this situation where
336 			 * we might issue something */
337 			priv->rx.avail_out += 5;
338 
339 			was_fin = 1;
340 			wsi->ws->pmd_trailer_application = 0;
341 			priv->rx.next_in = trail;
342 			priv->rx.avail_in = sizeof(trail);
343 			n = inflate(&priv->rx, Z_SYNC_FLUSH);
344 			lwsl_wsi_ext(wsi, "RX trailer infl ret %d, avi %d, avo %d",
345 				 n, priv->rx.avail_in, priv->rx.avail_out);
346 			switch (n) {
347 			case Z_NEED_DICT:
348 			case Z_STREAM_ERROR:
349 			case Z_DATA_ERROR:
350 			case Z_MEM_ERROR:
351 				lwsl_wsi_info(wsi, "zlib error inflate %d: %s",
352 					  n, priv->rx.msg);
353 				return -1;
354 			}
355 
356 			assert(priv->rx.avail_out);
357 		}
358 
359 		pmdrx->eb_out.len = lws_ptr_diff(priv->rx.next_out,
360 						 pmdrx->eb_out.token);
361 		priv->count_rx_between_fin = priv->count_rx_between_fin + (size_t)pmdrx->eb_out.len;
362 
363 		lwsl_wsi_ext(wsi, "  RX leaving with new effbuff len %d, "
364 			 "rx.avail_in=%d, TOTAL RX since FIN %lu",
365 			 pmdrx->eb_out.len, priv->rx.avail_in,
366 			 (unsigned long)priv->count_rx_between_fin);
367 
368 		if (was_fin) {
369 			lwsl_wsi_ext(wsi, "was_fin");
370 			priv->count_rx_between_fin = 0;
371 			if (priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER]) {
372 				lwsl_wsi_ext(wsi, "PMD_SERVER_NO_CONTEXT_TAKEOVER");
373 				(void)inflateEnd(&priv->rx);
374 				priv->rx_init = 0;
375 			}
376 
377 			return PMDR_EMPTY_FINAL;
378 		}
379 
380 		if (priv->rx.avail_in)
381 			return PMDR_HAS_PENDING;
382 
383 		return PMDR_EMPTY_NONFINAL;
384 
385 	case LWS_EXT_CB_PAYLOAD_TX:
386 
387 		/*
388 		 * ie, we are DEFLATING
389 		 *
390 		 * initialize us if needed
391 		 */
392 
393 		if (!priv->tx_init) {
394 			n = deflateInit2(&priv->tx, priv->args[PMD_COMP_LEVEL],
395 					 Z_DEFLATED,
396 					 -priv->args[PMD_SERVER_MAX_WINDOW_BITS +
397 						(wsi->a.vhost->listen_port <= 0)],
398 					 priv->args[PMD_MEM_LEVEL],
399 					 Z_DEFAULT_STRATEGY);
400 			if (n != Z_OK) {
401 				lwsl_wsi_ext(wsi, "inflateInit2 failed %d", n);
402 				return PMDR_FAILED;
403 			}
404 			priv->tx_init = 1;
405 		}
406 
407 		if (!priv->buf_tx_deflated)
408 			priv->buf_tx_deflated = lws_malloc((unsigned int)(LWS_PRE + 7 + 5 +
409 					    (1 << priv->args[PMD_TX_BUF_PWR2])),
410 					    "pmd tx deflate buf");
411 		if (!priv->buf_tx_deflated) {
412 			lwsl_wsi_err(wsi, "OOM");
413 			return PMDR_FAILED;
414 		}
415 
416 		/* hook us up with any deflated input that the caller has */
417 
418 		if (pmdrx->eb_in.token) {
419 
420 			assert(!priv->tx.avail_in);
421 
422 			priv->count_tx_between_fin = priv->count_tx_between_fin + (size_t)pmdrx->eb_in.len;
423 			lwsl_wsi_ext(wsi, "TX: eb_in length %d, "
424 				    "TOTAL TX since FIN: %d",
425 				    pmdrx->eb_in.len,
426 				    (int)priv->count_tx_between_fin);
427 			priv->tx.next_in = (unsigned char *)pmdrx->eb_in.token;
428 			priv->tx.avail_in = (uInt)pmdrx->eb_in.len;
429 		}
430 
431 		priv->tx.next_out = priv->buf_tx_deflated + LWS_PRE + 5;
432 		pmdrx->eb_out.token = priv->tx.next_out;
433 		priv->tx.avail_out = (uInt)(1 << priv->args[PMD_TX_BUF_PWR2]);
434 
435 		pen = 0;
436 		penbits = 0;
437 		deflatePending(&priv->tx, &pen, &penbits);
438 		pen = pen | (unsigned int)penbits;
439 
440 		if (!priv->tx.avail_in && (len & LWS_WRITE_NO_FIN)) {
441 			lwsl_wsi_ext(wsi, "no available in, pen: %u", pen);
442 
443 			if (!pen)
444 				return PMDR_DID_NOTHING;
445 		}
446 
447 		m = Z_NO_FLUSH;
448 		if (!(len & LWS_WRITE_NO_FIN)) {
449 			lwsl_wsi_ext(wsi, "deflate with SYNC_FLUSH, pkt len %d",
450 					(int)wsi->ws->rx_packet_length);
451 			m = Z_SYNC_FLUSH;
452 		}
453 
454 		n = deflate(&priv->tx, m);
455 		if (n == Z_STREAM_ERROR) {
456 			lwsl_wsi_notice(wsi, "Z_STREAM_ERROR");
457 			return PMDR_FAILED;
458 		}
459 
460 		pen = (!priv->tx.avail_out) && n != Z_STREAM_END;
461 
462 		lwsl_wsi_ext(wsi, "deflate ret %d, len 0x%x", n,
463 				(unsigned int)len);
464 
465 		if ((len & 0xf) == LWS_WRITE_TEXT)
466 			priv->tx_first_frame_type = LWSWSOPC_TEXT_FRAME;
467 		if ((len & 0xf) == LWS_WRITE_BINARY)
468 			priv->tx_first_frame_type = LWSWSOPC_BINARY_FRAME;
469 
470 		pmdrx->eb_out.len = lws_ptr_diff(priv->tx.next_out,
471 						 pmdrx->eb_out.token);
472 
473 		if (m == Z_SYNC_FLUSH && !(len & LWS_WRITE_NO_FIN) && !pen &&
474 		    pmdrx->eb_out.len < 4) {
475 			lwsl_wsi_err(wsi, "FAIL want to trim out length %d",
476 					(int)pmdrx->eb_out.len);
477 			assert(0);
478 		}
479 
480 		if (!(len & LWS_WRITE_NO_FIN) &&
481 		    m == Z_SYNC_FLUSH &&
482 		    !pen &&
483 		    pmdrx->eb_out.len >= 4) {
484 			// lwsl_wsi_err(wsi, "Trimming 4 from end of write");
485 			priv->tx.next_out -= 4;
486 			priv->tx.avail_out += 4;
487 			priv->count_tx_between_fin = 0;
488 
489 			assert(priv->tx.next_out[0] == 0x00 &&
490 			       priv->tx.next_out[1] == 0x00 &&
491 			       priv->tx.next_out[2] == 0xff &&
492 			       priv->tx.next_out[3] == 0xff);
493 		}
494 
495 
496 		/*
497 		 * track how much input was used and advance it
498 		 */
499 
500 		pmdrx->eb_in.token = pmdrx->eb_in.token +
501 					((unsigned int)pmdrx->eb_in.len - (unsigned int)priv->tx.avail_in);
502 		pmdrx->eb_in.len = (int)priv->tx.avail_in;
503 
504 		priv->compressed_out = 1;
505 		pmdrx->eb_out.len = lws_ptr_diff(priv->tx.next_out,
506 						 pmdrx->eb_out.token);
507 
508 		lwsl_wsi_ext(wsi, "  TX rewritten with new eb_in len %d, "
509 				"eb_out len %d, deflatePending %d",
510 				pmdrx->eb_in.len, pmdrx->eb_out.len, pen);
511 
512 		if (pmdrx->eb_in.len || pen)
513 			return PMDR_HAS_PENDING;
514 
515 		if (!(len & LWS_WRITE_NO_FIN))
516 			return PMDR_EMPTY_FINAL;
517 
518 		return PMDR_EMPTY_NONFINAL;
519 
520 	case LWS_EXT_CB_PACKET_TX_PRESEND:
521 		if (!priv->compressed_out)
522 			break;
523 		priv->compressed_out = 0;
524 
525 		/*
526 		 * we may have not produced any output for the actual "first"
527 		 * write... in that case, we need to fix up the inappropriate
528 		 * use of CONTINUATION when the first real write does come.
529 		 */
530 		if (priv->tx_first_frame_type & 0xf) {
531 			*pmdrx->eb_in.token = (unsigned char)((((unsigned char)*pmdrx->eb_in.token) & (unsigned char)~0xf) |
532 				((unsigned char)priv->tx_first_frame_type & (unsigned char)0xf));
533 			/*
534 			 * We have now written the "first" fragment, only
535 			 * do that once
536 			 */
537 			priv->tx_first_frame_type = 0;
538 		}
539 
540 		n = *(pmdrx->eb_in.token) & 15;
541 
542 		/* set RSV1, but not on CONTINUATION */
543 		if (n == LWSWSOPC_TEXT_FRAME || n == LWSWSOPC_BINARY_FRAME)
544 			*pmdrx->eb_in.token |= 0x40;
545 
546 		lwsl_wsi_ext(wsi, "PRESEND compressed: ws frame 0x%02X, len %d",
547 			    ((*pmdrx->eb_in.token) & 0xff),
548 			    pmdrx->eb_in.len);
549 
550 		if (((*pmdrx->eb_in.token) & 0x80) &&	/* fin */
551 		    priv->args[PMD_CLIENT_NO_CONTEXT_TAKEOVER]) {
552 			lwsl_wsi_debug(wsi, "PMD_CLIENT_NO_CONTEXT_TAKEOVER");
553 			(void)deflateEnd(&priv->tx);
554 			priv->tx_init = 0;
555 		}
556 
557 		break;
558 
559 	default:
560 		break;
561 	}
562 
563 	return 0;
564 }
565 
566