/*
 * ./lib/extension-permessage-deflate.c
 *
 *  Copyright (C) 2016 Andy Green <andy@warmcat.com>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation:
 *  version 2.1 of the License.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA  02110-1301  USA
 */

#include "private-libwebsockets.h"
#include "extension-permessage-deflate.h"
#include <stdio.h>
#include <string.h>
#include <assert.h>

#define LWS_ZLIB_MEMLEVEL 8

const struct lws_ext_options lws_ext_pm_deflate_options[] = {
	/* public RFC7692 settings */
	{ "server_no_context_takeover", EXTARG_NONE },
	{ "client_no_context_takeover", EXTARG_NONE },
	{ "server_max_window_bits",	EXTARG_OPT_DEC },
	{ "client_max_window_bits",	EXTARG_OPT_DEC },
	/* ones only user code can set */
	{ "rx_buf_size",		EXTARG_DEC },
	{ "tx_buf_size",		EXTARG_DEC },
	{ "compression_level",		EXTARG_DEC },
	{ "mem_level",			EXTARG_DEC },
	{ NULL, 0 }, /* sentinel */
};

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)
{
	struct lws_ext_pm_deflate_priv *priv =
				     (struct lws_ext_pm_deflate_priv *)user;
	struct lws_tokens *eff_buf = (struct lws_tokens *)in;
	static unsigned char trail[] = { 0, 0, 0xff, 0xff };
	int n, ret = 0, was_fin = 0, extra;
	struct lws_ext_option_arg *oa;

	switch (reason) {
	case LWS_EXT_CB_OPTION_SET:
		oa = in;
		lwsl_info("%s: option set: idx %d, %s, len %d\n", __func__,
			  oa->option_index, oa->start, oa->len);
		if (oa->start)
			priv->args[oa->option_index] = atoi(oa->start);
		else
			priv->args[oa->option_index] = 1;
		break;
	case LWS_EXT_CB_OPTION_CONFIRM:
		if (priv->args[PMD_SERVER_MAX_WINDOW_BITS] < 8 ||
		    priv->args[PMD_SERVER_MAX_WINDOW_BITS] > 15 ||
		    priv->args[PMD_CLIENT_MAX_WINDOW_BITS] < 8 ||
		    priv->args[PMD_CLIENT_MAX_WINDOW_BITS] > 15)
			return -1;
		break;

	case LWS_EXT_CB_CLIENT_CONSTRUCT:
	case LWS_EXT_CB_CONSTRUCT:
		/* fill in **user */
		priv = lws_zalloc(sizeof(*priv));
		*((void **)user) = priv;
		lwsl_ext("%s: LWS_EXT_CB_*CONSTRUCT\n", __func__);
		memset(priv, 0, sizeof(*priv));

		/* fill in pointer to options list */
		if (in)
			*((const struct lws_ext_options **)in) = lws_ext_pm_deflate_options;

		/* fallthru */

	case LWS_EXT_CB_OPTION_DEFAULT:

		/* set the public, RFC7692 defaults... */

		priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER] = 0,
		priv->args[PMD_CLIENT_NO_CONTEXT_TAKEOVER] = 0;
		priv->args[PMD_SERVER_MAX_WINDOW_BITS] = 15;
		priv->args[PMD_CLIENT_MAX_WINDOW_BITS] = 15;

		/* ...and the ones the user code can override */

		priv->args[PMD_RX_BUF_PWR2] = 10; /* ie, 1024 */
		priv->args[PMD_TX_BUF_PWR2] = 10; /* ie, 1024 */
		priv->args[PMD_COMP_LEVEL] = 1;
		priv->args[PMD_MEM_LEVEL] = 8;
		break;

	case LWS_EXT_CB_DESTROY:
		lwsl_ext("%s: LWS_EXT_CB_DESTROY\n", __func__);
		lws_free(priv->buf_rx_inflated);
		lws_free(priv->buf_tx_deflated);
		if (priv->rx_init)
			(void)inflateEnd(&priv->rx);
		if (priv->tx_init)
			(void)deflateEnd(&priv->tx);
		lws_free(priv);
		return ret;

	case LWS_EXT_CB_PAYLOAD_RX:
		lwsl_ext(" %s: LWS_EXT_CB_PAYLOAD_RX: in %d, existing in %d\n",
			 __func__, eff_buf->token_len, priv->rx.avail_in);
		if (!(wsi->u.ws.rsv_first_msg & 0x40))
			return 0;

#if 0
		for (n = 0; n < eff_buf->token_len; n++) {
			printf("%02X ", (unsigned char)eff_buf->token[n]);
			if ((n & 15) == 15)
				printf("\n");
		}
		printf("\n");
#endif
		if (!priv->rx_init)
			if (inflateInit2(&priv->rx, -priv->args[PMD_SERVER_MAX_WINDOW_BITS]) != Z_OK) {
				lwsl_err("%s: iniflateInit failed\n", __func__);
				return -1;
			}
		priv->rx_init = 1;
		if (!priv->buf_rx_inflated)
			priv->buf_rx_inflated = lws_malloc(LWS_PRE + 7 + 5 +
					    (1 << priv->args[PMD_RX_BUF_PWR2]));
		if (!priv->buf_rx_inflated) {
			lwsl_err("%s: OOM\n", __func__);
			return -1;
		}

		/*
		 * We have to leave the input stream alone if we didn't
		 * finish with it yet.  The input stream is held in the wsi
		 * rx buffer by the caller, so this assumption is safe while
		 * we block new rx while draining the existing rx
		 */
		if (eff_buf->token && eff_buf->token_len) {
			priv->rx.next_in = (unsigned char *)eff_buf->token;
			priv->rx.avail_in = eff_buf->token_len;
		}
		priv->rx.next_out = priv->buf_rx_inflated + LWS_PRE;
		eff_buf->token = (char *)priv->rx.next_out;
		priv->rx.avail_out = 1 << priv->args[PMD_RX_BUF_PWR2];

		if (priv->rx_held_valid) {
			lwsl_ext("-- RX piling on held byte --\n");
			*(priv->rx.next_out++) = priv->rx_held;
			priv->rx.avail_out--;
			priv->rx_held_valid = 0;
		}

		/* if...
		 *
		 *  - he has no remaining input content for this message, and
		 *  - and this is the final fragment, and
		 *  - we used everything that could be drained on the input side
		 *
		 * ...then put back the 00 00 FF FF the sender stripped as our
		 * input to zlib
		 */
		if (!priv->rx.avail_in && wsi->u.ws.final &&
		    !wsi->u.ws.rx_packet_length) {
			lwsl_ext("RX APPEND_TRAILER-DO\n");
			was_fin = 1;
			priv->rx.next_in = trail;
			priv->rx.avail_in = sizeof(trail);
		}

		n = inflate(&priv->rx, Z_NO_FLUSH);
		lwsl_ext("inflate ret %d, avi %d, avo %d, wsifinal %d\n", n,
			 priv->rx.avail_in, priv->rx.avail_out, wsi->u.ws.final);
		switch (n) {
		case Z_NEED_DICT:
		case Z_STREAM_ERROR:
		case Z_DATA_ERROR:
		case Z_MEM_ERROR:
			lwsl_info("zlib error inflate %d: %s\n",
				  n, priv->rx.msg);
			return -1;
		}
		/*
		 * If we did not already send in the 00 00 FF FF, and he's
		 * out of input, he did not EXACTLY fill the output buffer
		 * (which is ambiguous and we will force it to go around
		 * again by withholding a byte), and he's otherwise working on
		 * being a FIN fragment, then do the FIN message processing
		 * of faking up the 00 00 FF FF that the sender stripped.
		 */
		if (!priv->rx.avail_in && wsi->u.ws.final &&
		    !wsi->u.ws.rx_packet_length && !was_fin &&
		    priv->rx.avail_out /* ambiguous as to if it is the end */
		) {
			lwsl_ext("RX APPEND_TRAILER-DO\n");
			was_fin = 1;
			priv->rx.next_in = trail;
			priv->rx.avail_in = sizeof(trail);
			n = inflate(&priv->rx, Z_SYNC_FLUSH);
			lwsl_ext("RX trailer inf returned %d, avi %d, avo %d\n", n,
				 priv->rx.avail_in, priv->rx.avail_out);
			switch (n) {
			case Z_NEED_DICT:
			case Z_STREAM_ERROR:
			case Z_DATA_ERROR:
			case Z_MEM_ERROR:
				lwsl_info("zlib error inflate %d: %s\n",
					  n, priv->rx.msg);
				return -1;
			}
		}
		/*
		 * we must announce in our returncode now if there is more
		 * output to be expected from inflate, so we can decide to
		 * set the FIN bit on this bufferload or not.  However zlib
		 * is ambiguous when we exactly filled the inflate buffer.  It
		 * does not give us a clue as to whether we should understand
		 * that to mean he ended on a buffer boundary, or if there is
		 * more in the pipeline.
		 *
		 * So to work around that safely, if it used all output space
		 * exactly, we ALWAYS say there is more coming and we withhold
		 * the last byte of the buffer to guarantee that is true.
		 *
		 * That still leaves us at least one byte to finish with a FIN
		 * on, even if actually nothing more is coming from the next
		 * inflate action itself.
		 */
		if (!priv->rx.avail_out) { /* he used all available out buf */
			lwsl_ext("-- rx grabbing held --\n");
			/* snip the last byte and hold it for next time */
			priv->rx_held = *(--priv->rx.next_out);
			priv->rx_held_valid = 1;
		}

		eff_buf->token_len = (char *)priv->rx.next_out - eff_buf->token;
		priv->count_rx_between_fin += eff_buf->token_len;

		lwsl_ext("  %s: RX leaving with new effbuff len %d, "
			 "ret %d, rx.avail_in=%d, TOTAL RX since FIN %d\n",
			 __func__, eff_buf->token_len, priv->rx_held_valid,
			 priv->rx.avail_in, priv->count_rx_between_fin);

		if (was_fin) {
			priv->count_rx_between_fin = 0;
			if (priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER]) {
				(void)inflateEnd(&priv->rx);
				priv->rx_init = 0;
			}
		}
#if 0
		for (n = 0; n < eff_buf->token_len; n++)
			putchar(eff_buf->token[n]);
		puts("\n");
#endif

		return priv->rx_held_valid;

	case LWS_EXT_CB_PAYLOAD_TX:

		if (!priv->tx_init)
			if (deflateInit2(&priv->tx, priv->args[PMD_COMP_LEVEL],
					 Z_DEFLATED,
					 -priv->args[PMD_CLIENT_MAX_WINDOW_BITS +
						     !wsi->context->listen_port],
					 priv->args[PMD_MEM_LEVEL],
					 Z_DEFAULT_STRATEGY) != Z_OK) {
				lwsl_ext("inflateInit2 failed\n");
				return 1;
			}
		priv->tx_init = 1;
		if (!priv->buf_tx_deflated)
			priv->buf_tx_deflated = lws_malloc(LWS_PRE + 7 + 5 +
					    (1 << priv->args[PMD_TX_BUF_PWR2]));
		if (!priv->buf_tx_deflated) {
			lwsl_err("%s: OOM\n", __func__);
			return -1;
		}

		if (eff_buf->token) {
			lwsl_ext("%s: TX: eff_buf length %d\n", __func__,
				 eff_buf->token_len);
			priv->tx.next_in = (unsigned char *)eff_buf->token;
			priv->tx.avail_in = eff_buf->token_len;
		}

#if 0
		for (n = 0; n < eff_buf->token_len; n++) {
			printf("%02X ", (unsigned char)eff_buf->token[n]);
			if ((n & 15) == 15)
				printf("\n");
		}
		printf("\n");
#endif

		priv->tx.next_out = priv->buf_tx_deflated + LWS_PRE + 5;
		eff_buf->token = (char *)priv->tx.next_out;
		priv->tx.avail_out = 1 << priv->args[PMD_TX_BUF_PWR2];

		n = deflate(&priv->tx, Z_SYNC_FLUSH);
		if (n == Z_STREAM_ERROR) {
			lwsl_ext("%s: Z_STREAM_ERROR\n", __func__);
			return -1;
		}

		if (priv->tx_held_valid) {
			priv->tx_held_valid = 0;
			if (priv->tx.avail_out == 1 << priv->args[PMD_TX_BUF_PWR2])
				/*
				 * we can get a situation he took something in
				 * but did not generate anything out, at the end
				 * of a message (eg, next thing he sends is 80
				 * 00, a zero length FIN, like Authobahn can
				 * send).
				 * If we have come back as a FIN, we must not
				 * place the pending trailer 00 00 FF FF, just
				 * the 1 byte of live data
				 */
				*(--eff_buf->token) = priv->tx_held[0];
			else {
				/* he generated data, prepend whole pending */
				eff_buf->token -= 5;
				for (n = 0; n < 5; n++)
					eff_buf->token[n] = priv->tx_held[n];

			}
		}
		priv->compressed_out = 1;
		eff_buf->token_len = (int)(priv->tx.next_out -
					   (unsigned char *)eff_buf->token);

		/*
		 * we must announce in our returncode now if there is more
		 * output to be expected from inflate, so we can decide to
		 * set the FIN bit on this bufferload or not.  However zlib
		 * is ambiguous when we exactly filled the inflate buffer.  It
		 * does not give us a clue as to whether we should understand
		 * that to mean he ended on a buffer boundary, or if there is
		 * more in the pipeline.
		 *
		 * Worse, the guy providing the stuff we are sending may not
		 * know until after that this was, actually, the last chunk,
		 * that can happen even if we did not fill the output buf, ie
		 * he may send after this a zero-length FIN fragment.
		 *
		 * This is super difficult because we must snip the last 4
		 * bytes in the case this is the last compressed output of the
		 * message.  The only way to deal with it is defer sending the
		 * last 5 bytes of each frame until the next one, when we will
		 * be in a position to understand if that has a FIN or not.
		 */

		extra = !!(len & LWS_WRITE_NO_FIN) || !priv->tx.avail_out;

		if (eff_buf->token_len >= 4 + extra) {
			lwsl_ext("tx held %d\n", 4 + extra);
			priv->tx_held_valid = extra;
			for (n = 3 + extra; n >= 0; n--)
				priv->tx_held[n] = *(--priv->tx.next_out);
			eff_buf->token_len -= 4 + extra;
		}
		lwsl_ext("  TX rewritten with new effbuff len %d, ret %d\n",
			 eff_buf->token_len, !priv->tx.avail_out);

		return !priv->tx.avail_out; /* 1 == have more tx pending */

	case LWS_EXT_CB_PACKET_TX_PRESEND:
		if (!priv->compressed_out)
			break;
		priv->compressed_out = 0;

		if ((*(eff_buf->token) & 0x80) && priv->args[PMD_CLIENT_NO_CONTEXT_TAKEOVER]) {
			(void)deflateEnd(&priv->tx);
			priv->tx_init = 0;
		}

		n = *(eff_buf->token) & 15;
		/* set RSV1, but not on CONTINUATION */
		if (n == LWSWSOPC_TEXT_FRAME || n == LWSWSOPC_BINARY_FRAME)
			*eff_buf->token |= 0x40;
#if 0
		for (n = 0; n < eff_buf->token_len; n++) {
			printf("%02X ", (unsigned char)eff_buf->token[n]);
			if ((n & 15) == 15)
				puts("\n");
		}
		puts("\n");
#endif
		lwsl_ext("%s: tx opcode 0x%02X\n", __func__,
			 (unsigned char)*eff_buf->token);
		break;

	default:
		break;
	}

	return 0;
}

