/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include "private-lib-core.h"

/*
 * bitmap of control messages that are valid to receive for each http2 state
 */

static const uint16_t http2_rx_validity[] = {
	/* LWS_H2S_IDLE */
		(1 << LWS_H2_FRAME_TYPE_SETTINGS) |
		(1 << LWS_H2_FRAME_TYPE_PRIORITY) |
//		(1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE)| /* ignore */
		(1 << LWS_H2_FRAME_TYPE_HEADERS) |
		(1 << LWS_H2_FRAME_TYPE_CONTINUATION),
	/* LWS_H2S_RESERVED_LOCAL */
		(1 << LWS_H2_FRAME_TYPE_SETTINGS) |
		(1 << LWS_H2_FRAME_TYPE_RST_STREAM) |
		(1 << LWS_H2_FRAME_TYPE_PRIORITY) |
		(1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE),
	/* LWS_H2S_RESERVED_REMOTE */
		(1 << LWS_H2_FRAME_TYPE_SETTINGS) |
		(1 << LWS_H2_FRAME_TYPE_HEADERS) |
		(1 << LWS_H2_FRAME_TYPE_CONTINUATION) |
		(1 << LWS_H2_FRAME_TYPE_RST_STREAM) |
		(1 << LWS_H2_FRAME_TYPE_PRIORITY),
	/* LWS_H2S_OPEN */
		(1 << LWS_H2_FRAME_TYPE_DATA) |
		(1 << LWS_H2_FRAME_TYPE_HEADERS) |
		(1 << LWS_H2_FRAME_TYPE_PRIORITY) |
		(1 << LWS_H2_FRAME_TYPE_RST_STREAM) |
		(1 << LWS_H2_FRAME_TYPE_SETTINGS) |
		(1 << LWS_H2_FRAME_TYPE_PUSH_PROMISE) |
		(1 << LWS_H2_FRAME_TYPE_PING) |
		(1 << LWS_H2_FRAME_TYPE_GOAWAY) |
		(1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE) |
		(1 << LWS_H2_FRAME_TYPE_CONTINUATION),
	/* LWS_H2S_HALF_CLOSED_REMOTE */
		(1 << LWS_H2_FRAME_TYPE_SETTINGS) |
		(1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE) |
		(1 << LWS_H2_FRAME_TYPE_PRIORITY) |
		(1 << LWS_H2_FRAME_TYPE_RST_STREAM),
	/* LWS_H2S_HALF_CLOSED_LOCAL */
		(1 << LWS_H2_FRAME_TYPE_DATA) |
		(1 << LWS_H2_FRAME_TYPE_HEADERS) |
		(1 << LWS_H2_FRAME_TYPE_PRIORITY) |
		(1 << LWS_H2_FRAME_TYPE_RST_STREAM) |
		(1 << LWS_H2_FRAME_TYPE_SETTINGS) |
		(1 << LWS_H2_FRAME_TYPE_PUSH_PROMISE) |
		(1 << LWS_H2_FRAME_TYPE_PING) |
		(1 << LWS_H2_FRAME_TYPE_GOAWAY) |
		(1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE) |
		(1 << LWS_H2_FRAME_TYPE_CONTINUATION),
	/* LWS_H2S_CLOSED */
		(1 << LWS_H2_FRAME_TYPE_SETTINGS) |
		(1 << LWS_H2_FRAME_TYPE_PRIORITY) |
		(1 << LWS_H2_FRAME_TYPE_WINDOW_UPDATE) |
		(1 << LWS_H2_FRAME_TYPE_RST_STREAM),
};

static const char *preface = "PRI * HTTP/2.0\x0d\x0a\x0d\x0aSM\x0d\x0a\x0d\x0a";

static const char * const h2_state_names[] = {
	"LWS_H2S_IDLE",
	"LWS_H2S_RESERVED_LOCAL",
	"LWS_H2S_RESERVED_REMOTE",
	"LWS_H2S_OPEN",
	"LWS_H2S_HALF_CLOSED_REMOTE",
	"LWS_H2S_HALF_CLOSED_LOCAL",
	"LWS_H2S_CLOSED",
};

#if 0
static const char * const h2_setting_names[] = {
	"",
	"H2SET_HEADER_TABLE_SIZE",
	"H2SET_ENABLE_PUSH",
	"H2SET_MAX_CONCURRENT_STREAMS",
	"H2SET_INITIAL_WINDOW_SIZE",
	"H2SET_MAX_FRAME_SIZE",
	"H2SET_MAX_HEADER_LIST_SIZE",
	"reserved",
	"H2SET_ENABLE_CONNECT_PROTOCOL"
};

void
lws_h2_dump_settings(struct http2_settings *set)
{
	int n;

	for (n = 1; n < H2SET_COUNT; n++)
		lwsl_notice("   %30s: %10d\n", h2_setting_names[n], set->s[n]);
}
#else
void
lws_h2_dump_settings(struct http2_settings *set)
{
}
#endif

struct lws_h2_protocol_send *
lws_h2_new_pps(enum lws_h2_protocol_send_type type)
{
	struct lws_h2_protocol_send *pps = lws_malloc(sizeof(*pps), "pps");

	if (pps)
		pps->type = type;

	return pps;
}

void lws_h2_init(struct lws *wsi)
{
	wsi->h2.h2n->our_set = wsi->a.vhost->h2.set;
	wsi->h2.h2n->peer_set = lws_h2_defaults;
}

void
lws_h2_state(struct lws *wsi, enum lws_h2_states s)
{
	if (!wsi)
		return;
	lwsl_info("%s: %s: state %s -> %s\n", __func__, lws_wsi_tag(wsi),
			h2_state_names[wsi->h2.h2_state],
			h2_state_names[s]);
		
	(void)h2_state_names;
	wsi->h2.h2_state = (uint8_t)s;
}

int
lws_h2_update_peer_txcredit(struct lws *wsi, unsigned int sid, int bump)
{
	struct lws *nwsi = lws_get_network_wsi(wsi);
	struct lws_h2_protocol_send *pps;

	assert(wsi);

	if (!bump)
		return 0;

	if (sid == (unsigned int)-1)
		sid = wsi->mux.my_sid;

	lwsl_info("%s: sid %d: bump %d -> %d\n", __func__, sid, bump,
			(int)wsi->txc.peer_tx_cr_est + bump);

	pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW);
	if (!pps)
		return 1;

	pps->u.update_window.sid = (unsigned int)sid;
	pps->u.update_window.credit = (unsigned int)bump;
	wsi->txc.peer_tx_cr_est += bump;

	lws_wsi_txc_describe(&wsi->txc, __func__, wsi->mux.my_sid);

	lws_pps_schedule(wsi, pps);

	pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW);
	if (!pps)
		return 1;

	pps->u.update_window.sid = 0;
	pps->u.update_window.credit = (unsigned int)bump;
	nwsi->txc.peer_tx_cr_est += bump;

	lws_wsi_txc_describe(&nwsi->txc, __func__, nwsi->mux.my_sid);

	lws_pps_schedule(nwsi, pps);

	return 0;
}

int
lws_h2_get_peer_txcredit_estimate(struct lws *wsi)
{
	lws_wsi_txc_describe(&wsi->txc, __func__, wsi->mux.my_sid);
	return (int)wsi->txc.peer_tx_cr_est;
}

static int
lws_h2_update_peer_txcredit_thresh(struct lws *wsi, unsigned int sid, int threshold, int bump)
{
	if (wsi->txc.peer_tx_cr_est > threshold)
		return 0;

	return lws_h2_update_peer_txcredit(wsi, sid, bump);
}

/* cx + vh lock */

static struct lws *
__lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi,
		     unsigned int sid)
{
	struct lws *nwsi = lws_get_network_wsi(parent_wsi);
	struct lws_h2_netconn *h2n = nwsi->h2.h2n;
	char tmp[50], tmp1[50];
	unsigned int n, b = 0;
	struct lws *wsi;
	const char *p;

	lws_context_assert_lock_held(vh->context);
	lws_vhost_assert_lock_held(vh);

	/*
	 * The identifier of a newly established stream MUST be numerically
   	 * greater than all streams that the initiating endpoint has opened or
   	 * reserved.  This governs streams that are opened using a HEADERS frame
   	 * and streams that are reserved using PUSH_PROMISE.  An endpoint that
   	 * receives an unexpected stream identifier MUST respond with a
   	 * connection error (Section 5.4.1) of type PROTOCOL_ERROR.
	 */
	if (sid <= h2n->highest_sid_opened) {
		lwsl_info("%s: tried to open lower sid %d (%d)\n", __func__,
				sid, (int)h2n->highest_sid_opened);
		lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR, "Bad sid");
		return NULL;
	}

	/* no more children allowed by parent */
	if (parent_wsi->mux.child_count + 1 >
	    parent_wsi->h2.h2n->our_set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
		lwsl_notice("reached concurrent stream limit\n");
		return NULL;
	}

	n = 0;
	p = &parent_wsi->lc.gutag[1];
	do {
		if (*p == '|') {
			b++;
			if (b == 3)
				continue;
		}
		tmp1[n++] = *p++;
	} while (b < 3 && n < sizeof(tmp1) - 2);
	tmp1[n] = '\0';
	lws_snprintf(tmp, sizeof(tmp), "h2_sid%u_(%s)", sid, tmp1);
	wsi = lws_create_new_server_wsi(vh, parent_wsi->tsi, LWSLCG_WSI_MUX, tmp);
	if (!wsi) {
		lwsl_notice("new server wsi failed (%s)\n", lws_vh_tag(vh));
		return NULL;
	}

#if defined(LWS_WITH_SERVER)
	if (lwsi_role_server(parent_wsi)) {
		lws_metrics_caliper_bind(wsi->cal_conn, wsi->a.context->mth_srv);
	}
#endif

	h2n->highest_sid_opened = sid;

	lws_wsi_mux_insert(wsi, parent_wsi, sid);
	if (sid >= h2n->highest_sid)
		h2n->highest_sid = sid + 2;

	wsi->mux_substream = 1;
	wsi->seen_nonpseudoheader = 0;

	wsi->txc.tx_cr = (int32_t)nwsi->h2.h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE];
	wsi->txc.peer_tx_cr_est =
			(int32_t)nwsi->h2.h2n->our_set.s[H2SET_INITIAL_WINDOW_SIZE];

	lwsi_set_state(wsi, LRS_ESTABLISHED);
	lwsi_set_role(wsi, lwsi_role(parent_wsi));

	wsi->a.protocol = &vh->protocols[0];
	if (lws_ensure_user_space(wsi))
		goto bail1;

#if defined(LWS_WITH_SERVER) && defined(LWS_WITH_SECURE_STREAMS)
	if (lws_adopt_ss_server_accept(wsi))
		goto bail1;
#endif

	/* get the ball rolling */
	lws_validity_confirmed(wsi);

	lwsl_info("%s: %s new ch %s, sid %d, usersp=%p\n", __func__,
		  lws_wsi_tag(parent_wsi), lws_wsi_tag(wsi), sid, wsi->user_space);

	lws_wsi_txc_describe(&wsi->txc, __func__, wsi->mux.my_sid);
	lws_wsi_txc_describe(&nwsi->txc, __func__, 0);

	return wsi;

bail1:
	/* undo the insert */
	parent_wsi->mux.child_list = wsi->mux.sibling_list;
	parent_wsi->mux.child_count--;

	if (wsi->user_space)
		lws_free_set_NULL(wsi->user_space);
	vh->protocols[0].callback(wsi, LWS_CALLBACK_WSI_DESTROY, NULL, NULL, 0);
	__lws_vhost_unbind_wsi(wsi);
	lws_free(wsi);

	return NULL;
}

struct lws *
lws_wsi_h2_adopt(struct lws *parent_wsi, struct lws *wsi)
{
	struct lws *nwsi = lws_get_network_wsi(parent_wsi);

	/* no more children allowed by parent */
	if (parent_wsi->mux.child_count + 1 >
	    parent_wsi->h2.h2n->our_set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
		lwsl_notice("reached concurrent stream limit\n");
		return NULL;
	}

	/* sid is set just before issuing the headers, ensuring monoticity */

	wsi->seen_nonpseudoheader = 0;
#if defined(LWS_WITH_CLIENT)
	wsi->client_mux_substream = 1;
	/*
	 * A reused client stream is a mux substream just like the first one.
	 * Without this, HPACK skips capturing custom (non-indexed) response
	 * headers into the ah unknown-header list (that capture is gated on
	 * mux_substream), so a pooled client's 2nd+ request drops custom
	 * response headers and leaves a stale list that lws_hdr_custom_*
	 * subsequently walks out of bounds.
	 */
	wsi->mux_substream = 1;
#endif
	wsi->h2.initialized = 1;

#if 0
	/* only assign sid at header send time when we know it */
	if (!wsi->mux.my_sid) {
		wsi->mux.my_sid = nwsi->h2.h2n->highest_sid;
		nwsi->h2.h2n->highest_sid += 2;
	}
#endif

	lwsl_info("%s: binding wsi %s to sid %d (next %d)\n", __func__,
		lws_wsi_tag(wsi), (int)wsi->mux.my_sid, (int)nwsi->h2.h2n->highest_sid);

	lws_wsi_mux_insert(wsi, parent_wsi, wsi->mux.my_sid);

	wsi->txc.tx_cr = (int32_t)nwsi->h2.h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE];
	wsi->txc.peer_tx_cr_est = (int32_t)
			nwsi->h2.h2n->our_set.s[H2SET_INITIAL_WINDOW_SIZE];

	lws_wsi_txc_describe(&wsi->txc, __func__, wsi->mux.my_sid);

	if (lws_ensure_user_space(wsi))
		goto bail1;

	lws_role_transition(wsi, LWSIFR_CLIENT, LRS_H2_WAITING_TO_SEND_HEADERS,
			    &role_ops_h2);

	lws_callback_on_writable(wsi);

	return wsi;

bail1:
	/* undo the insert */
	parent_wsi->mux.child_list = wsi->mux.sibling_list;
	parent_wsi->mux.child_count--;

	if (wsi->user_space)
		lws_free_set_NULL(wsi->user_space);
	wsi->a.protocol->callback(wsi, LWS_CALLBACK_WSI_DESTROY, NULL, NULL, 0);
	lws_free(wsi);

	return NULL;
}


int
lws_h2_issue_preface(struct lws *wsi)
{
	struct lws_h2_netconn *h2n = wsi->h2.h2n;
	struct lws_h2_protocol_send *pps;

	if (!h2n) {
		lwsl_warn("%s: no valid h2n\n", __func__);
		return 1;
	}

	if (h2n->sent_preface)
		return 1;

	lwsl_debug("%s: %s: fd %d\n", __func__, lws_wsi_tag(wsi), (int)wsi->desc.sockfd);

	if (lws_issue_raw(wsi, (uint8_t *)preface, strlen(preface)) !=
		(int)strlen(preface))
		return 1;

	h2n->sent_preface = 1;

	lws_role_transition(wsi, LWSIFR_CLIENT, LRS_H2_WAITING_TO_SEND_HEADERS,
			    &role_ops_h2);

	h2n->count = 0;
	wsi->txc.tx_cr = 65535;

	/*
	 * we must send a settings frame
	 */
	pps = lws_h2_new_pps(LWS_H2_PPS_MY_SETTINGS);
	if (!pps)
		return 1;
	lws_pps_schedule(wsi, pps);
	lwsl_info("%s: h2 client sending settings\n", __func__);

	return 0;
}

void
lws_pps_schedule(struct lws *wsi, struct lws_h2_protocol_send *pps)
{
	struct lws *nwsi = lws_get_network_wsi(wsi);
	struct lws_h2_netconn *h2n = nwsi->h2.h2n;

	if (!h2n) {
		lwsl_warn("%s: null h2n\n", __func__);
		lws_free(pps);
		return;
	}

	if (pps->type != LWS_H2_PPS_GOAWAY && h2n->pps_count >= 50) {
		lwsl_warn("%s: too many pending protocol sends (%u), dropping conn\n",
			  __func__, (unsigned int)h2n->pps_count);
		lws_free(pps);
		lws_h2_goaway(nwsi, H2_ERR_ENHANCE_YOUR_CALM, "too many pending pps");
		return;
	}

	pps->next = h2n->pps;
	h2n->pps = pps;
	h2n->pps_count++;
	lws_rx_flow_control(wsi, LWS_RXFLOW_REASON_APPLIES_DISABLE |
				 LWS_RXFLOW_REASON_H2_PPS_PENDING);
	lws_callback_on_writable(wsi);
}

int
lws_h2_goaway(struct lws *wsi, uint32_t err, const char *reason)
{
	struct lws_h2_netconn *h2n = wsi->h2.h2n;
	struct lws_h2_protocol_send *pps;

	if (h2n->type == LWS_H2_FRAME_TYPE_COUNT)
		return 0;

	pps = lws_h2_new_pps(LWS_H2_PPS_GOAWAY);
	if (!pps)
		return 1;

	lwsl_info("%s: %s: ERR 0x%x, '%s'\n", __func__, lws_wsi_tag(wsi), (int)err, reason);

	pps->u.ga.err = err;
	pps->u.ga.highest_sid = h2n->highest_sid;
	lws_strncpy(pps->u.ga.str, reason, sizeof(pps->u.ga.str));
	lws_pps_schedule(wsi, pps);

	h2n->type = LWS_H2_FRAME_TYPE_COUNT; /* ie, IGNORE */

	return 0;
}

int
lws_h2_rst_stream(struct lws *wsi, uint32_t err, const char *reason)
{
	struct lws *nwsi = lws_get_network_wsi(wsi);
	struct lws_h2_netconn *h2n = nwsi->h2.h2n;
	struct lws_h2_protocol_send *pps;

	if (!h2n)
		return 0;

	if (!wsi->h23_stream_carries_ws && h2n->type == LWS_H2_FRAME_TYPE_COUNT)
		return 0;

	pps = lws_h2_new_pps(LWS_H2_PPS_RST_STREAM);
	if (!pps)
		return 1;

	lwsl_info("%s: RST_STREAM 0x%x, sid %d, REASON '%s'\n", __func__,
		  (int)err, wsi->mux.my_sid, reason);

	pps->u.rs.sid = wsi->mux.my_sid;
	pps->u.rs.err = err;

	lws_pps_schedule(wsi, pps);

	h2n->type = LWS_H2_FRAME_TYPE_COUNT; /* ie, IGNORE */
	lws_h2_state(wsi, LWS_H2_STATE_CLOSED);

	return 0;
}

int
lws_h2_settings(struct lws *wsi, struct http2_settings *settings,
		unsigned char *buf, int len)
{
	struct lws *nwsi = lws_get_network_wsi(wsi);
	unsigned int a, b;

	if (!len)
		return 0;

	if (len < LWS_H2_SETTINGS_LEN)
		return 1;

	while (len >= LWS_H2_SETTINGS_LEN) {
		a = (unsigned int)((buf[0] << 8) | buf[1]);
		if (!a || a >= H2SET_COUNT)
			goto skip;
		b = (unsigned int)(buf[2] << 24 | buf[3] << 16 | buf[4] << 8 | buf[5]);

		switch (a) {
		case H2SET_HEADER_TABLE_SIZE:
			break;
		case H2SET_ENABLE_PUSH:
			if (b > 1) {
				lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR,
					      "ENABLE_PUSH invalid arg");
				return 1;
			}
			break;
		case H2SET_MAX_CONCURRENT_STREAMS:
			break;
		case H2SET_INITIAL_WINDOW_SIZE:
			if (b > 0x7fffffff) {
				lws_h2_goaway(nwsi, H2_ERR_FLOW_CONTROL_ERROR,
					      "Initial Window beyond max");
				return 1;
			}

#if defined(LWS_WITH_CLIENT)
#if defined(LWS_AMAZON_RTOS) || defined(LWS_AMAZON_LINUX)
			if (
#else
			if (wsi->flags & LCCSCF_H2_QUIRK_OVERFLOWS_TXCR &&
#endif
			    b == 0x7fffffff) {
				b >>= 4;

				break;
			}
#endif

			/*
			 * In addition to changing the flow-control window for
			 * streams that are not yet active, a SETTINGS frame
			 * can alter the initial flow-control window size for
			 * streams with active flow-control windows (that is,
			 * streams in the "open" or "half-closed (remote)"
			 * state).  When the value of
			 * SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver
			 * MUST adjust the size of all stream flow-control
			 * windows that it maintains by the difference between
			 * the new value and the old value.
			 */

			lws_start_foreach_ll(struct lws *, w,
					     nwsi->mux.child_list) {
				lwsl_info("%s: adi child tc cr %d +%d -> %d",
					  __func__, (int)w->txc.tx_cr,
					  b - (unsigned int)settings->s[a],
					  (int)(w->txc.tx_cr + (int)b -
						  (int)settings->s[a]));
				w->txc.tx_cr += (int)b - (int)settings->s[a];
				if (w->txc.tx_cr > 0 &&
				    w->txc.tx_cr <=
						  (int32_t)(b - settings->s[a]))

					lws_callback_on_writable(w);
			} lws_end_foreach_ll(w, mux.sibling_list);

			break;
		case H2SET_MAX_FRAME_SIZE:
			if (b < wsi->a.vhost->h2.set.s[H2SET_MAX_FRAME_SIZE]) {
				lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR,
					      "Frame size < initial");
				return 1;
			}
			if (b > 0x00ffffff) {
				lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR,
					      "Settings Frame size above max");
				return 1;
			}
			break;
		case H2SET_MAX_HEADER_LIST_SIZE:
			break;
		}
		settings->s[a] = b;
		lwsl_info("http2 settings %d <- 0x%x\n", a, b);
skip:
		len -= LWS_H2_SETTINGS_LEN;
		buf += LWS_H2_SETTINGS_LEN;
	}

	if (len)
		return 1;

	lws_h2_dump_settings(settings);

	return 0;
}

/* RFC7640 Sect 6.9
 *
 * The WINDOW_UPDATE frame can be specific to a stream or to the entire
 * connection.  In the former case, the frame's stream identifier
 * indicates the affected stream; in the latter, the value "0" indicates
 * that the entire connection is the subject of the frame.
 *
 * ...
 *
 * Two flow-control windows are applicable: the stream flow-control
 * window and the connection flow-control window.  The sender MUST NOT
 * send a flow-controlled frame with a length that exceeds the space
 * available in either of the flow-control windows advertised by the
 * receiver.  Frames with zero length with the END_STREAM flag set (that
 * is, an empty DATA frame) MAY be sent if there is no available space
 * in either flow-control window.
 */

int
lws_h2_tx_cr_get(struct lws *wsi)
{
	int c = wsi->txc.tx_cr;
	struct lws *nwsi = lws_get_network_wsi(wsi);

	if (!wsi->mux_substream && !nwsi->upgraded_to_http2)
		return ~0x80000000;

	lwsl_info ("%s: %s: own tx credit %d: nwsi credit %d\n",
		     __func__, lws_wsi_tag(wsi), c, (int)nwsi->txc.tx_cr);

	if (nwsi->txc.tx_cr < c)
		c = nwsi->txc.tx_cr;

	if (c < 0)
		return 0;

	return c;
}

void
lws_h2_tx_cr_consume(struct lws *wsi, int consumed)
{
	struct lws *nwsi = lws_get_network_wsi(wsi);

	wsi->txc.tx_cr -= consumed;

	if (nwsi != wsi)
		nwsi->txc.tx_cr -= consumed;
}

int lws_h2_frame_write(struct lws *wsi, int type, int flags,
		       unsigned int sid, unsigned int len, unsigned char *buf)
{
	struct lws *nwsi = lws_get_network_wsi(wsi);
	unsigned char *p = &buf[-LWS_H2_FRAME_HEADER_LENGTH];
	int n;

	//if (wsi->h23_stream_carries_ws)
	// lwsl_hexdump_level(LLL_NOTICE, buf, len);

	*p++ = (uint8_t)(len >> 16);
	*p++ = (uint8_t)(len >> 8);
	*p++ = (uint8_t)len;
	*p++ = (uint8_t)type;
	*p++ = (uint8_t)flags;
	*p++ = (uint8_t)(sid >> 24);
	*p++ = (uint8_t)(sid >> 16);
	*p++ = (uint8_t)(sid >> 8);
	*p++ = (uint8_t)sid;

	lwsl_debug("%s: %s (eff %s). typ %d, fl 0x%x, sid=%d, len=%d, "
		   "txcr=%d, nwsi->txcr=%d\n", __func__, lws_wsi_tag(wsi),
		   lws_wsi_tag(nwsi), type, flags,
		   sid, len, (int)wsi->txc.tx_cr, (int)nwsi->txc.tx_cr);

	if (type == LWS_H2_FRAME_TYPE_DATA) {
#if defined(LWS_ROLE_WS)
		if (wsi->h23_stream_carries_ws &&
		    (wsi->buflist_out ||
		     lws_h2_tx_cr_get(wsi) < (int)len)) {
			/*
			 * ws-over-h2 (RFC 8441): not enough h2 flow-control
			 * credit for this DATA frame (or earlier frames are
			 * already parked and must keep their order).  Sending
			 * DATA past the peer's advertised window is a
			 * connection-fatal FLOW_CONTROL_ERROR, and unlike the
			 * http file-serving path the ws user API has no seat
			 * at the flow-control table -- so park the whole
			 * frame (header included) on the stream's buflist and
			 * send it from the POLLOUT servicing loop once
			 * WINDOW_UPDATE restores credit (which re-arms every
			 * mux child).  Credit is consumed at drain time.
			 */
			if (lws_buflist_append_segment(&wsi->buflist_out,
					&buf[-LWS_H2_FRAME_HEADER_LENGTH],
					len + LWS_H2_FRAME_HEADER_LENGTH) < 0)
				return -1;

			lws_callback_on_writable(wsi);

			return (int)len;
		}
#endif
		if (wsi->txc.tx_cr < (int)len)

			lwsl_info("%s: %s: sending payload len %d"
				 " but tx_cr only %d!\n", __func__,
				 lws_wsi_tag(wsi), len, (int)wsi->txc.tx_cr);
				lws_h2_tx_cr_consume(wsi, (int)len);
	}

	n = lws_issue_raw(nwsi, &buf[-LWS_H2_FRAME_HEADER_LENGTH],
			  len + LWS_H2_FRAME_HEADER_LENGTH);
	if (n < 0)
		return n;

	if (n >= LWS_H2_FRAME_HEADER_LENGTH)
		return n - LWS_H2_FRAME_HEADER_LENGTH;

	return n;
}

#if defined(LWS_ROLE_WS)
/*
 * Send as much parked ws-over-h2 DATA (see lws_h2_frame_write) as the
 * stream's tx credit allows, oldest first.  Each parked buflist segment is one
 * complete h2 frame: the 9-byte header (carrying the payload length) plus the
 * payload.  h2 DATA is a byte stream, so a frame larger than the available
 * credit is SPLIT: a chunk goes out under a fresh header and the stored
 * header is rewritten in place for the remainder -- the peer's stream window
 * (SETTINGS_INITIAL_WINDOW_SIZE) may be permanently smaller than a parked
 * frame, so waiting for whole-frame credit can deadlock.  Flags (e.g.
 * END_STREAM) are only sent with a frame's final chunk.  Returns <0 on a
 * fatal write error; leftover bytes stay parked until the next WINDOW_UPDATE.
 */
int
lws_h2_ws_drain_parked_tx(struct lws *nwsi, struct lws *wsi)
{
	uint8_t out[LWS_H2_FRAME_HEADER_LENGTH + 4096];

	/*
	 * Test the stream's own buflist directly: lws_has_buffered_out()
	 * additionally reports the nwsi's buffer, which fills up whenever
	 * lws_issue_raw() below takes a partial socket write and must not
	 * keep us looping after our own parked frames are gone.
	 */
	while (wsi->buflist_out) {
		uint8_t *seg = NULL;
		size_t sl = lws_buflist_next_segment_len(&wsi->buflist_out,
							 &seg);
		unsigned int plen, chunk, rem;
		uint8_t type, flags, sid[4];
		int cr;

		if (!sl || !seg || sl < LWS_H2_FRAME_HEADER_LENGTH)
			return -1;

		plen = ((unsigned int)seg[0] << 16) |
		       ((unsigned int)seg[1] << 8) | seg[2];
		type = seg[3];
		flags = seg[4];
		memcpy(sid, &seg[5], 4);

		if (sl < LWS_H2_FRAME_HEADER_LENGTH + plen)
			return -1;

		cr = lws_h2_tx_cr_get(wsi);
		if (plen && cr <= 0)
			/* still skint; WINDOW_UPDATE will re-arm us */
			return 0;

		chunk = plen;
		if (chunk > (unsigned int)cr)
			chunk = (unsigned int)cr;
		if (chunk > sizeof(out) - LWS_H2_FRAME_HEADER_LENGTH)
			chunk = sizeof(out) - LWS_H2_FRAME_HEADER_LENGTH;

		out[0] = (uint8_t)(chunk >> 16);
		out[1] = (uint8_t)(chunk >> 8);
		out[2] = (uint8_t)chunk;
		out[3] = type;
		out[4] = chunk == plen ? flags : 0;
		memcpy(&out[5], sid, 4);
		memcpy(&out[LWS_H2_FRAME_HEADER_LENGTH],
		       &seg[LWS_H2_FRAME_HEADER_LENGTH], chunk);

		lws_h2_tx_cr_consume(wsi, (int)chunk);

		/*
		 * lws_issue_raw() either sends the bytes or copies any
		 * remainder onto nwsi's own buflist, so the parked bytes can
		 * be released either way.
		 */
		if (lws_issue_raw(nwsi, out,
				  LWS_H2_FRAME_HEADER_LENGTH + chunk) < 0)
			return -1;

		if (chunk == plen) {
			lws_buflist_use_segment(&wsi->buflist_out,
					LWS_H2_FRAME_HEADER_LENGTH + plen);
			continue;
		}

		/*
		 * Partial send: stamp a header for the remainder over the
		 * last 9 already-consumed bytes (the sent payload ended at
		 * seg[chunk + 9], so seg[chunk..chunk+8] only covers spent
		 * header/payload bytes for any chunk >= 1), then advance the
		 * segment onto it.
		 */
		rem = plen - chunk;
		seg[chunk + 0] = (uint8_t)(rem >> 16);
		seg[chunk + 1] = (uint8_t)(rem >> 8);
		seg[chunk + 2] = (uint8_t)rem;
		seg[chunk + 3] = type;
		seg[chunk + 4] = flags;
		memcpy(&seg[chunk + 5], sid, 4);
		lws_buflist_use_segment(&wsi->buflist_out, chunk);
	}

	return 0;
}
#endif

static void lws_h2_set_bin(struct lws *wsi, int n, unsigned char *buf)
{
	*buf++ = (uint8_t)(n >> 8);
	*buf++ = (uint8_t)n;
	*buf++ = (uint8_t)(wsi->h2.h2n->our_set.s[n] >> 24);
	*buf++ = (uint8_t)(wsi->h2.h2n->our_set.s[n] >> 16);
	*buf++ = (uint8_t)(wsi->h2.h2n->our_set.s[n] >> 8);
	*buf = (uint8_t)wsi->h2.h2n->our_set.s[n];
}

/* we get called on the network connection */

int lws_h2_do_pps_send(struct lws *wsi)
{
	struct lws_h2_netconn *h2n = wsi->h2.h2n;
	struct lws_h2_protocol_send *pps = NULL;
	struct lws *cwsi;
	uint8_t set[LWS_PRE + 64], *p = &set[LWS_PRE], *q;
	int n, m = 0, flags = 0;

	if (!h2n)
		return 1;

	/* get the oldest pps */

	lws_start_foreach_llp(struct lws_h2_protocol_send **, pps1, h2n->pps) {
		if ((*pps1)->next == NULL) { /* we are the oldest in the list */
			pps = *pps1; /* remove us from the list */
			*pps1 = NULL;
			break;
		}
	} lws_end_foreach_llp(pps1, next);

	if (!pps)
		return 1;

	if (h2n->pps_count)
		h2n->pps_count--;

	lwsl_info("%s: %s: %d\n", __func__, lws_wsi_tag(wsi), pps->type);

	switch (pps->type) {

	case LWS_H2_PPS_MY_SETTINGS:

		/*
		 * if any of our settings varies from h2 "default defaults"
		 * then we must inform the peer
		 */
		for (n = 1; n < H2SET_COUNT; n++)
			if (h2n->our_set.s[n] != lws_h2_defaults.s[n]) {
				lwsl_debug("sending SETTING %d 0x%x\n", n,
					   (unsigned int)
						   wsi->h2.h2n->our_set.s[n]);

				lws_h2_set_bin(wsi, n, &set[LWS_PRE + m]);
				m += (int)sizeof(h2n->one_setting);
			}
		n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_SETTINGS,
				       flags, LWS_H2_STREAM_ID_MASTER, (unsigned int)m,
		     		       &set[LWS_PRE]);
		if (n != m) {
			lwsl_info("send %d %d\n", n, m);
			goto bail;
		}
		break;

	case LWS_H2_PPS_SETTINGS_INITIAL_UPDATE_WINDOW:
		q = &set[LWS_PRE];
		*q++ = (uint8_t)(H2SET_INITIAL_WINDOW_SIZE >> 8);
		*q++ = (uint8_t)(H2SET_INITIAL_WINDOW_SIZE);
		*q++ = (uint8_t)(pps->u.update_window.credit >> 24);
		*q++ = (uint8_t)(pps->u.update_window.credit >> 16);
		*q++ = (uint8_t)(pps->u.update_window.credit >> 8);
		*q = (uint8_t)(pps->u.update_window.credit);

		lwsl_debug("%s: resetting initial window to %d\n", __func__,
				(int)pps->u.update_window.credit);

		n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_SETTINGS,
				       flags, LWS_H2_STREAM_ID_MASTER, 6,
		     		       &set[LWS_PRE]);
		if (n != 6) {
			lwsl_info("send %d %d\n", n, m);
			goto bail;
		}
		break;

	case LWS_H2_PPS_ACK_SETTINGS:
		/* send ack ... always empty */
		n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_SETTINGS, 1,
				       LWS_H2_STREAM_ID_MASTER, 0,
				       &set[LWS_PRE]);
		if (n) {
			lwsl_err("%s: writing settings ack frame failed %d\n", __func__, n);
			goto bail;
		}
		wsi->h2_acked_settings = 0;
		/* this is the end of the preface dance then? */
		if (lwsi_state(wsi) == LRS_H2_AWAIT_SETTINGS) {
			lwsi_set_state(wsi, LRS_ESTABLISHED);
#if defined(LWS_WITH_FILE_OPS)
			wsi->http.fop_fd = NULL;
#endif
			if (lws_is_ssl(lws_get_network_wsi(wsi)))
				break;

			if (wsi->a.vhost->options &
				LWS_SERVER_OPTION_H2_PRIOR_KNOWLEDGE)
				break;

			/*
			 * we need to treat the headers from the upgrade as the
			 * first job.  So these need to get shifted to sid 1.
			 */

			lws_context_lock(wsi->a.context, "h2 mig");
			lws_vhost_lock(wsi->a.vhost);

			h2n->swsi = __lws_wsi_server_new(wsi->a.vhost, wsi, 1);

			lws_vhost_unlock(wsi->a.vhost);
			lws_context_unlock(wsi->a.context);

			if (!h2n->swsi)
				goto bail;

			/* pass on the initial headers to SID 1 */
			h2n->swsi->http.ah = wsi->http.ah;
			wsi->http.ah = NULL;

			lwsl_info("%s: inherited headers %p\n", __func__,
				  h2n->swsi->http.ah);
			h2n->swsi->txc.tx_cr = (int32_t)
				h2n->our_set.s[H2SET_INITIAL_WINDOW_SIZE];
			lwsl_info("initial tx credit on %s: %d\n",
				  lws_wsi_tag(h2n->swsi),
				  (int)h2n->swsi->txc.tx_cr);
			h2n->swsi->h2.initialized = 1;
			/* demanded by HTTP2 */
			h2n->swsi->h2.END_STREAM = 1;
			lwsl_info("servicing initial http request\n");

#if defined(LWS_WITH_SERVER)
			if (lws_http_action(h2n->swsi))
				goto bail;
#endif
			break;
		}
		break;

	/*
	 * h2 only has PING... ACK = 0 = ping, ACK = 1 = pong
	 */

	case LWS_H2_PPS_PING:
	case LWS_H2_PPS_PONG:
		if (pps->type == LWS_H2_PPS_PING)
			lwsl_info("sending PING\n");
		else {
			lwsl_info("sending PONG\n");
			flags = LWS_H2_FLAG_SETTINGS_ACK;
		}

		memcpy(&set[LWS_PRE], pps->u.ping.ping_payload, 8);
		n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_PING, flags,
				       LWS_H2_STREAM_ID_MASTER, 8,
				       &set[LWS_PRE]);
		if (n != 8)
			goto bail;

		break;

	case LWS_H2_PPS_GOAWAY:
		lwsl_info("LWS_H2_PPS_GOAWAY\n");
		*p++ = (uint8_t)(pps->u.ga.highest_sid >> 24);
		*p++ = (uint8_t)(pps->u.ga.highest_sid >> 16);
		*p++ = (uint8_t)(pps->u.ga.highest_sid >> 8);
		*p++ = (uint8_t)(pps->u.ga.highest_sid);
		*p++ = (uint8_t)(pps->u.ga.err >> 24);
		*p++ = (uint8_t)(pps->u.ga.err >> 16);
		*p++ = (uint8_t)(pps->u.ga.err >> 8);
		*p++ = (uint8_t)(pps->u.ga.err);
		q = (unsigned char *)pps->u.ga.str;
		n = 0;
		while (*q && n < (int)sizeof(pps->u.ga.str)) {
			n++;
			*p++ = *q++;
		}
		h2n->we_told_goaway = 1;
		n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_GOAWAY, 0,
				       LWS_H2_STREAM_ID_MASTER,
				       (unsigned int)lws_ptr_diff(p, &set[LWS_PRE]),
				       &set[LWS_PRE]);
		if (n != 4) {
			lwsl_info("send %d %d\n", n, m);
			goto bail;
		}
		goto bail;

	case LWS_H2_PPS_RST_STREAM:
		lwsl_info("LWS_H2_PPS_RST_STREAM\n");
		*p++ = (uint8_t)(pps->u.rs.err >> 24);
		*p++ = (uint8_t)(pps->u.rs.err >> 16);
		*p++ = (uint8_t)(pps->u.rs.err >> 8);
		*p++ = (uint8_t)(pps->u.rs.err);
		n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_RST_STREAM,
				       0, pps->u.rs.sid, 4, &set[LWS_PRE]);
		if (n != 4) {
			lwsl_info("send %d %d\n", n, m);
			goto bail;
		}
		cwsi = lws_wsi_mux_from_id(wsi, pps->u.rs.sid);
		if (cwsi) {
			lwsl_debug("%s: closing cwsi %s %s %s (wsi %s)\n",
				   __func__, lws_wsi_tag(cwsi),
				   cwsi->role_ops->name,
				   cwsi->a.protocol->name, lws_wsi_tag(wsi));
			lws_close_free_wsi(cwsi, 0, "reset stream");
		}
		break;

	case LWS_H2_PPS_UPDATE_WINDOW:
		lwsl_info("Issuing LWS_H2_PPS_UPDATE_WINDOW: sid %d: add %d\n",
			    (int)pps->u.update_window.sid,
			    (int)pps->u.update_window.credit);
		*p++ = (uint8_t)((pps->u.update_window.credit >> 24) & 0x7f); /* 31b */
		*p++ = (uint8_t)(pps->u.update_window.credit >> 16);
		*p++ = (uint8_t)(pps->u.update_window.credit >> 8);
		*p++ = (uint8_t)(pps->u.update_window.credit);
		n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_WINDOW_UPDATE,
				       0, pps->u.update_window.sid, 4,
				       &set[LWS_PRE]);
		if (n != 4) {
			lwsl_info("send %d %d\n", n, m);
			goto bail;
		}
		break;

	default:
		break;
	}

	lws_free(pps);

	return 0;

bail:
	lws_free(pps);

	return 1;
}

static int
lws_h2_parse_end_of_frame(struct lws *wsi);

/*
 * The frame header part has just completely arrived.
 * Perform actions for header completion.
 */
static int
lws_h2_parse_frame_header(struct lws *wsi)
{
	struct lws_h2_netconn *h2n = wsi->h2.h2n;
	struct lws_h2_protocol_send *pps;
	int n;

	/*
	 * We just got the frame header
	 */
	h2n->count = 0;
	h2n->swsi = wsi;
	/* b31 is a reserved bit */
	h2n->sid = h2n->sid & 0x7fffffff;

	if (h2n->sid && !(h2n->sid & 1)) {
		char pes[32];
		lws_snprintf(pes, sizeof(pes), "Even Stream ID 0x%x", (unsigned int)h2n->sid);
		lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, pes);

		return 0;
	}

	/* let the network wsi live a bit longer if subs are active */

	if (!wsi->immortal_substream_count) {
		int ds = lws_wsi_keepalive_timeout_eff(wsi);

		/*
		 * A short (5s) timeout here affects the reverse proxy if
		 * the onward box takes a long time to respond, eg to a POST.
		 * The mount can override the keepalive timeout, eg, to give
		 * the right behaviour depending on reverse proxy for a particular
		 * server.
		 */
		if (lwsi_role_client(wsi))
			lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT,
					(int)wsi->a.context->timeout_secs);
		else {
			
			lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, ds);
		}
	}
	if (h2n->sid)
		h2n->swsi = lws_wsi_mux_from_id(wsi, h2n->sid);

	lwsl_debug("%s (%s): fr hdr: typ 0x%x, fla 0x%x, sid 0x%x, len 0x%x\n",
		  lws_wsi_tag(wsi), lws_wsi_tag(h2n->swsi), h2n->type,
		  h2n->flags, (unsigned int)h2n->sid, (unsigned int)h2n->length);

	if (h2n->we_told_goaway && h2n->sid > h2n->highest_sid)
		h2n->type = LWS_H2_FRAME_TYPE_COUNT; /* ie, IGNORE */

	if (h2n->type >= LWS_H2_FRAME_TYPE_COUNT) {
		lwsl_info("%s: ignoring unknown frame type %d (len %d)\n", __func__, h2n->type, (unsigned int)h2n->length);
		/* we MUST ignore frames we don't understand */
		h2n->type = LWS_H2_FRAME_TYPE_COUNT;
	}

	/*
	 * Even if we have decided to logically ignore this frame, we must
	 * consume the correct "frame length" amount of data to retain sync
	 */

	if (h2n->length > h2n->our_set.s[H2SET_MAX_FRAME_SIZE]) {
		/*
		 * peer sent us something bigger than we told
		 * it we would allow
		 */
		lwsl_info("%s: received oversize frame %d\n", __func__,
			  (unsigned int)h2n->length);
		lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
			      "Peer ignored our frame size setting");
		return 1;
	}

	if (h2n->swsi)
		lwsl_info("%s: %s, State: %s, received cmd %d\n",
		  __func__, lws_wsi_tag(h2n->swsi),
		  h2_state_names[h2n->swsi->h2.h2_state], h2n->type);
	else {
		/* if it's data, either way no swsi means CLOSED state */
		if (h2n->type == LWS_H2_FRAME_TYPE_DATA) {
			if (h2n->sid <= h2n->highest_sid_opened
#if defined(LWS_WITH_CLIENT)
					&& wsi->client_h2_alpn
#endif
			) {
				if (h2n->flags & LWS_H2_FLAG_END_STREAM)
					lwsl_notice("%s: stragging EOS\n", __func__);
				else {
					lwsl_wsi_notice(wsi, "ignoring straggling "
						"DATA (flags 0x%x, length %d)",
							h2n->flags, (int)h2n->length);
					/* ie, IGNORE */
					h2n->type = LWS_H2_FRAME_TYPE_COUNT;
				}
			} else {
				lwsl_info("%s: received %d bytes data for unknown sid %d, highest known %d\n",
						__func__, (int)h2n->length, (int)h2n->sid, (int)h2n->highest_sid_opened);

//				if (h2n->sid > h2n->highest_sid_opened) {
				lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED,
				      "Data for nonexistent sid");
				return 0;
//				}
			}
		}
		/* if the sid is credible, treat as wsi for it closed */
		if (h2n->sid > h2n->highest_sid_opened &&
		    h2n->type != LWS_H2_FRAME_TYPE_HEADERS &&
		    h2n->type != LWS_H2_FRAME_TYPE_PRIORITY) {
			/* if not credible, reject it */
			lwsl_info("%s: %s, No child for sid %d, rxcmd %d\n",
			  __func__, lws_wsi_tag(h2n->swsi), (unsigned int)h2n->sid, h2n->type);
			lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED,
				     "Data for nonexistent sid");
			return 0;
		}
	}

	if (h2n->swsi && h2n->sid && h2n->type != LWS_H2_FRAME_TYPE_COUNT &&
	    !(http2_rx_validity[h2n->swsi->h2.h2_state] & (1 << h2n->type))) {
		lwsl_info("%s: %s, State: %s, ILLEGAL cmdrx %d (OK 0x%x)\n",
			  __func__, lws_wsi_tag(h2n->swsi),
			  h2_state_names[h2n->swsi->h2.h2_state], h2n->type,
			  http2_rx_validity[h2n->swsi->h2.h2_state]);

		if (h2n->swsi->h2.h2_state == LWS_H2_STATE_CLOSED ||
		    h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_REMOTE)
			n = H2_ERR_STREAM_CLOSED;
		else
			n = H2_ERR_PROTOCOL_ERROR;
		lws_h2_goaway(wsi, (unsigned int)n, "invalid rx for state");

		return 0;
	}

	if (h2n->cont_exp && h2n->type != LWS_H2_FRAME_TYPE_COUNT &&
	    (h2n->cont_exp_sid != h2n->sid ||
			      h2n->type != LWS_H2_FRAME_TYPE_CONTINUATION)) {
		lwsl_info("%s: expected cont on sid %u (got %d on sid %u)\n",
			  __func__, (unsigned int)h2n->cont_exp_sid, h2n->type,
			  (unsigned int)h2n->sid);
		h2n->cont_exp = 0;
		if (h2n->cont_exp_headers)
			n = H2_ERR_COMPRESSION_ERROR;
		else
			n = H2_ERR_PROTOCOL_ERROR;
		lws_h2_goaway(wsi, (unsigned int)n, "Continuation hdrs State");

		return 0;
	}

	switch (h2n->type) {
	case LWS_H2_FRAME_TYPE_DATA:
		lwsl_info("seen incoming LWS_H2_FRAME_TYPE_DATA start\n");
		if (!h2n->sid) {
			lwsl_info("DATA: 0 sid\n");
			lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "DATA 0 sid");
			break;
		}
		lwsl_info("Frame header DATA: sid %u, flags 0x%x, len %u\n",
				(unsigned int)h2n->sid, h2n->flags,
				(unsigned int)h2n->length);

		if (!h2n->swsi) {
			lwsl_notice("DATA: NULL swsi\n");
			break;
		}

		lwsl_info("DATA rx on state %d\n", h2n->swsi->h2.h2_state);

		if (
		    h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_REMOTE ||
		    h2n->swsi->h2.h2_state == LWS_H2_STATE_CLOSED) {
			lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED, "conn closed");
			break;
		}

		if (h2n->length == 0)
			lws_h2_parse_end_of_frame(wsi);

		break;

	case LWS_H2_FRAME_TYPE_PRIORITY:
		lwsl_info("LWS_H2_FRAME_TYPE_PRIORITY complete frame\n");
		if (!h2n->sid) {
			lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
				      "Priority has 0 sid");
			break;
		}
		if (h2n->length != 5) {
			lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
				      "Priority has length other than 5");
			break;
		}
		break;
	case LWS_H2_FRAME_TYPE_PUSH_PROMISE:
                h2n->cont_count = 0;
                lwsl_info("LWS_H2_FRAME_TYPE_PUSH_PROMISE complete frame\n");
		lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "Server only");
		break;

	case LWS_H2_FRAME_TYPE_GOAWAY:
		lwsl_debug("LWS_H2_FRAME_TYPE_GOAWAY received\n");
		break;

	case LWS_H2_FRAME_TYPE_RST_STREAM:
		if (!h2n->sid)
			return 1;
		if (!h2n->swsi) {
			if (h2n->sid <= h2n->highest_sid_opened)
				break;
			lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
				      "crazy sid on RST_STREAM");
			return 1;
		}
		if (h2n->length != 4) {
			lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
				      "RST_STREAM can only be length 4");
			break;
		}
		lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);
		break;

	case LWS_H2_FRAME_TYPE_SETTINGS:
		lwsl_info("LWS_H2_FRAME_TYPE_SETTINGS complete frame\n");
		/* nonzero sid on settings is illegal */
		if (h2n->sid) {
			lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
					 "Settings has nonzero sid");
			break;
		}

		if (!(h2n->flags & LWS_H2_FLAG_SETTINGS_ACK)) {
			if (h2n->length % 6) {
				lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
						 "Settings length error");
				break;
			}

			if (h2n->type == LWS_H2_FRAME_TYPE_COUNT)
				return 0;

			if (wsi->upgraded_to_http2 &&
#if defined(LWS_WITH_CLIENT)
			    (!(wsi->flags & LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM) ||
#else
			    (
#endif
					    !wsi->h2_acked_settings)) {

				pps = lws_h2_new_pps(LWS_H2_PPS_ACK_SETTINGS);
				if (!pps)
					return 1;
				lws_pps_schedule(wsi, pps);
				wsi->h2_acked_settings = 1;
			}
			break;
		}
		/* came to us with ACK set... not allowed to have payload */

		if (h2n->length) {
			lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
				      "Settings with ACK not allowed payload");
			break;
		}
		break;
	case LWS_H2_FRAME_TYPE_PING:
		if (h2n->sid) {
			lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
				      "Ping has nonzero sid");
			break;
		}
		if (h2n->length != 8) {
			lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
				      "Ping payload can only be 8");
			break;
		}
		break;
	case LWS_H2_FRAME_TYPE_CONTINUATION:
		lwsl_info("LWS_H2_FRAME_TYPE_CONTINUATION: sid = %u %d %d\n",
			  (unsigned int)h2n->sid, (int)h2n->cont_exp,
			  (int)h2n->cont_exp_sid);

		if (++h2n->cont_count > 64) {
			lws_h2_goaway(wsi, H2_ERR_ENHANCE_YOUR_CALM,
				      "CONTINUATION flood");
			return 1;
		}

		if (!h2n->cont_exp ||
		     h2n->cont_exp_sid != h2n->sid ||
		     !h2n->sid ||
		     !h2n->swsi) {
			lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
				      "unexpected CONTINUATION");
			break;
		}

		if (h2n->swsi->h2.END_HEADERS) {
			lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
				      "END_HEADERS already seen");
			break;
		}
		/* END_STREAM is in HEADERS, skip resetting it */
		goto update_end_headers;

	case LWS_H2_FRAME_TYPE_HEADERS:
		h2n->hpack_total_hdr_len = 0;
		h2n->cont_count = 0;
		lwsl_info("HEADERS: frame header: sid = %u\n",
				(unsigned int)h2n->sid);
		if (!h2n->sid) {
			lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "sid 0");
			return 1;
		}

		if (h2n->swsi && !h2n->swsi->h2.END_STREAM &&
		    h2n->swsi->h2.END_HEADERS &&
		    !(h2n->flags & LWS_H2_FLAG_END_STREAM)) {
			lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
				      "extra HEADERS together");
			return 1;
		}

#if defined(LWS_WITH_CLIENT)
		if (wsi->client_h2_alpn) {
			if (h2n->sid) {
				h2n->swsi = lws_wsi_mux_from_id(wsi, h2n->sid);
				lwsl_info("HEADERS: nwsi %s: sid %u mapped "
					  "to wsi %s\n", lws_wsi_tag(wsi),
					  (unsigned int)h2n->sid,
					  lws_wsi_tag(h2n->swsi));
				if (!h2n->swsi)
					break;
			}
			goto update_end_headers;
		}
#endif

		if (!h2n->swsi) {
			/* no more children allowed by parent */
			if (wsi->mux.child_count + 1 >
			    wsi->h2.h2n->our_set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
				lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
				"Another stream not allowed");

				return 1;
			}

			/*
			 * The peer has sent us a HEADERS implying the creation
			 * of a new stream
			 */

			lws_context_lock(wsi->a.context, "h2 new str");
			lws_vhost_lock(wsi->a.vhost);

			h2n->swsi = __lws_wsi_server_new(wsi->a.vhost, wsi,
						         h2n->sid);

			lws_vhost_unlock(wsi->a.vhost);
			lws_context_unlock(wsi->a.context);

			if (!h2n->swsi) {
				lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
					      "OOM");

				return 1;
			}

			if (h2n->sid >= h2n->highest_sid)
				h2n->highest_sid = h2n->sid + 2;

			h2n->swsi->h2.initialized = 1;

			if (lws_h2_update_peer_txcredit(h2n->swsi,
					h2n->swsi->mux.my_sid, 4 * 65536))
				goto cleanup_wsi_l;
		}

		/*
		 * ah needs attaching to child wsi, even though
		 * we only fill it from network wsi
		 */
		if (!h2n->swsi->http.ah)
			if (lws_header_table_attach(h2n->swsi, 0)) {
				lwsl_err("%s: Failed to get ah\n", __func__);
				return 1;
			}

		/*
		 * The first use of a new stream identifier implicitly closes
		 * all streams in the "idle" state that might have been
		 * initiated by that peer with a lower-valued stream identifier.
		 *
		 * For example, if a client sends a HEADERS frame on stream 7
		 * without ever sending a frame on stream 5, then stream 5
		 * transitions to the "closed" state when the first frame for
		 * stream 7 is sent or received.
		 */
		lws_start_foreach_ll_safe(struct lws *, w, wsi->mux.child_list,
					  mux.sibling_list) {
			if (w->mux.my_sid < h2n->sid &&
			    w->h2.h2_state == LWS_H2_STATE_IDLE)
				lws_close_free_wsi(w, 0, "h2 sid close");
		} lws_end_foreach_ll_safe(w);

		h2n->cont_exp = !(h2n->flags & LWS_H2_FLAG_END_HEADERS);
		h2n->cont_exp_sid = h2n->sid;
		h2n->cont_exp_headers = 1;
	//	lws_header_table_reset(h2n->swsi, 0);

update_end_headers:
		if (lws_check_opt(h2n->swsi->a.vhost->options,
			       LWS_SERVER_OPTION_VH_H2_HALF_CLOSED_LONG_POLL)) {

			/*
			 * We don't directly timeout streams that enter the
			 * half-closed remote state, allowing immortal long
			 * poll
			 */
			lws_mux_mark_immortal(h2n->swsi);
			lwsl_info("%s: %s: h2 stream entering long poll\n",
					__func__, lws_wsi_tag(h2n->swsi));

		} else {
			h2n->swsi->h2.END_STREAM =
					!!(h2n->flags & LWS_H2_FLAG_END_STREAM);
			lwsl_debug("%s: hdr END_STREAM = %d\n",__func__,
			  h2n->swsi->h2.END_STREAM);
		}

		/* no END_HEADERS means CONTINUATION must come */
		h2n->swsi->h2.END_HEADERS =
				!!(h2n->flags & LWS_H2_FLAG_END_HEADERS);
		lwsl_info("%s: %s: END_HEADERS %d\n", __func__, lws_wsi_tag(h2n->swsi),
			  h2n->swsi->h2.END_HEADERS);
		if (h2n->swsi->h2.END_HEADERS)
			h2n->cont_exp = 0;
		lwsl_debug("END_HEADERS %d\n", h2n->swsi->h2.END_HEADERS);
		break;

cleanup_wsi_l:

		return 1;

	case LWS_H2_FRAME_TYPE_WINDOW_UPDATE:
		if (h2n->length != 4) {
			lws_h2_goaway(wsi, H2_ERR_FRAME_SIZE_ERROR,
				      "window update frame not 4");
			break;
		}
		lwsl_info("LWS_H2_FRAME_TYPE_WINDOW_UPDATE\n");
		break;
	case LWS_H2_FRAME_TYPE_COUNT:
		if (h2n->length == 0)
			lws_h2_parse_end_of_frame(wsi);
		else
			lwsl_debug("%s: going on to deal with unknown frame remaining len %d\n", __func__, (unsigned int)h2n->length);
		break;
	default:
		lwsl_info("%s: ILLEGAL FRAME TYPE %d\n", __func__, h2n->type);
		h2n->type = LWS_H2_FRAME_TYPE_COUNT; /* ie, IGNORE */
		break;
	}
	if (h2n->length == 0)
		h2n->frame_state = 0;

	return 0;
}

static const char * const method_names[] = {
	"GET", "POST",
#if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS)
	"OPTIONS", "PUT", "PATCH", "DELETE",
#endif
	"CONNECT", "HEAD"
};
static unsigned char method_index[] = {
	WSI_TOKEN_GET_URI,
	WSI_TOKEN_POST_URI,
#if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS)
	WSI_TOKEN_OPTIONS_URI,
	WSI_TOKEN_PUT_URI,
	WSI_TOKEN_PATCH_URI,
	WSI_TOKEN_DELETE_URI,
#endif
	WSI_TOKEN_CONNECT,
	WSI_TOKEN_HEAD_URI,
};

/*
 * The last byte of the whole frame has been handled.
 * Perform actions for frame completion.
 *
 * This is the crunch time for parsing that may have occured on a network
 * wsi with a pending partial send... we may call lws_http_action() to send
 * a response, conflicting with the partial.
 *
 * So in that case we change the wsi state and do the lws_http_action() in the
 * WRITABLE handler as a priority.
 */
static int
lws_h2_parse_end_of_frame(struct lws *wsi)
{
	struct lws_h2_netconn *h2n = wsi->h2.h2n;
	struct lws *eff_wsi = wsi;
	const char *p;
	int n;

	h2n->frame_state = 0;
	h2n->count = 0;

	if (h2n->sid)
		h2n->swsi = lws_wsi_mux_from_id(wsi, h2n->sid);

	if (h2n->sid > h2n->highest_sid)
		h2n->highest_sid = h2n->sid;

	if (h2n->collected_priority && (h2n->dep & ~(1u << 31)) == h2n->sid) {
		lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "depends on own sid");
		return 0;
	}

	switch (h2n->type) {

	case LWS_H2_FRAME_TYPE_SETTINGS:

#if defined(LWS_WITH_CLIENT)
		if (wsi->client_h2_alpn && !wsi->client_mux_migrated &&
		    !(h2n->flags & LWS_H2_FLAG_SETTINGS_ACK)) {
			struct lws_h2_protocol_send *pps;

			/* migrate original client ask on to substream 1 */
#if defined(LWS_WITH_FILE_OPS)
			wsi->http.fop_fd = NULL;
#endif
			lwsl_info("%s: migrating\n", __func__);
			wsi->client_mux_migrated = 1;
			/*
			 * we need to treat the headers from the upgrade as the
			 * first job.  So these need to get shifted to sid 1.
			 */
			lws_context_lock(wsi->a.context, "h2 mig");
			lws_vhost_lock(wsi->a.vhost);

			h2n->swsi = __lws_wsi_server_new(wsi->a.vhost, wsi, 1);

			lws_vhost_unlock(wsi->a.vhost);
			lws_context_unlock(wsi->a.context);

			if (!h2n->swsi)
				return 1;
			h2n->sid = 1;

			assert(lws_wsi_mux_from_id(wsi, 1) == h2n->swsi);

		//	lws_role_transition(wsi, LWSIFR_CLIENT,
		//			    LRS_H2_WAITING_TO_SEND_HEADERS,
		//			    &role_ops_h2);

			lws_role_transition(h2n->swsi, LWSIFR_CLIENT,
					    LRS_H2_WAITING_TO_SEND_HEADERS,
					    &role_ops_h2);

			/* pass on the initial headers to SID 1 */
			h2n->swsi->http.ah = wsi->http.ah;
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
			lws_fi_import(&h2n->swsi->fic, &wsi->fic);
#endif
			h2n->swsi->client_mux_substream = 1;
			h2n->swsi->client_h2_alpn = 1;
#if defined(LWS_ROLE_WS)
			/*
			 * If the original client ask was a ws connection, the
			 * migrated stream carries it (RFC 8441 extended
			 * CONNECT); without this the h2 client handshake
			 * issues a plain GET and the ws ask is lost.
			 */
			if (wsi->ws) {
				h2n->swsi->ws = wsi->ws;
				wsi->ws = NULL;
				h2n->swsi->do_ws = 1;
			}
#endif
#if defined(LWS_WITH_CLIENT)
			h2n->swsi->flags = wsi->flags;
#if defined(LWS_WITH_CONMON)
			/* sid1 needs to represent the connection experience
			 * ... we take over responsibility for the DNS list
			 * copy as well
			 */
			h2n->swsi->conmon = wsi->conmon;
			h2n->swsi->conmon_datum = wsi->conmon_datum;
			h2n->swsi->sa46_peer = wsi->sa46_peer;
			wsi->conmon.dns_results_copy = NULL;
#endif
#endif /* CLIENT */

#if defined(LWS_WITH_SECURE_STREAMS)
			if (wsi->for_ss) {
				lws_ss_handle_t *h = (lws_ss_handle_t *)lws_get_opaque_user_data(wsi);

				if (!h)
					return 1;
				h2n->swsi->for_ss = 1;
				wsi->for_ss = 0;

				if (h->wsi == wsi)
					h->wsi = h2n->swsi;
			}
#endif

			h2n->swsi->a.protocol = wsi->a.protocol;
			if (h2n->swsi->user_space &&
			    !h2n->swsi->user_space_externally_allocated)
				lws_free(h2n->swsi->user_space);
			h2n->swsi->user_space = wsi->user_space;
			h2n->swsi->user_space_externally_allocated =
					wsi->user_space_externally_allocated;
			h2n->swsi->a.opaque_user_data = wsi->a.opaque_user_data;
			wsi->a.opaque_user_data = NULL;
			h2n->swsi->txc.manual_initial_tx_credit =
					wsi->txc.manual_initial_tx_credit;

#if defined(LWS_WITH_TLS)
			lws_strncpy(h2n->swsi->alpn, wsi->alpn,
					sizeof(wsi->alpn));
#endif

			wsi->user_space = NULL;

			if (h2n->swsi->http.ah)
				h2n->swsi->http.ah->wsi = h2n->swsi;
			wsi->http.ah = NULL;

			lwsl_info("%s: MIGRATING nwsi %s -> swsi %s\n", __func__,
				  lws_wsi_tag(wsi), lws_wsi_tag(h2n->swsi));
			h2n->swsi->txc.tx_cr = (int32_t)
				h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE];
			lwsl_info("%s: initial tx credit on %s: %d\n",
				  __func__, lws_wsi_tag(h2n->swsi),
				  (int)h2n->swsi->txc.tx_cr);
			h2n->swsi->h2.initialized = 1;

			/* set our initial window size */
			if (!wsi->h2.initialized) {
				wsi->txc.tx_cr = (int32_t)
				     h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE];

				lwsl_info("%s: initial tx credit for us to "
					  "write on nwsi %s: %d\n", __func__,
					  lws_wsi_tag(wsi), (int)wsi->txc.tx_cr);
				wsi->h2.initialized = 1;
			}

			lws_callback_on_writable(h2n->swsi);

			if (!wsi->h2_acked_settings ||
			    !(wsi->flags & LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM)
			) {
				pps = lws_h2_new_pps(LWS_H2_PPS_ACK_SETTINGS);
				if (!pps)
					return 1;
				lws_pps_schedule(wsi, pps);
				lwsl_info("%s: SETTINGS ack PPS\n", __func__);
				wsi->h2_acked_settings = 1;
			}

			/* also attach any queued guys */

			lws_wsi_mux_apply_queue(wsi);
		}
#endif
		break;

	case LWS_H2_FRAME_TYPE_CONTINUATION:
	case LWS_H2_FRAME_TYPE_HEADERS:

		if (!h2n->swsi)
			break;

		/* service the http request itself */

		if (h2n->last_action_dyntable_resize) {
			lws_h2_goaway(wsi, H2_ERR_COMPRESSION_ERROR,
				"dyntable resize last in headers");
			break;
		}

		if (!h2n->swsi->h2.END_HEADERS) {
			/* we are not finished yet */
			lwsl_info("withholding http action for continuation\n");
			h2n->cont_exp_sid = h2n->sid;
			h2n->cont_exp = 1;
			break;
		}

		/* confirm the hpack stream state is reasonable for finishing */

		if (h2n->hpack != HPKS_TYPE) {
			/* hpack incomplete */
			lwsl_info("hpack incomplete %d (type %d, len %u)\n",
				  h2n->hpack, h2n->type,
				  (unsigned int)h2n->hpack_len);
			lws_h2_goaway(wsi, H2_ERR_COMPRESSION_ERROR,
				      "hpack incomplete");
			break;
		}

		/* this is the last part of HEADERS */
		switch (h2n->swsi->h2.h2_state) {
		case LWS_H2_STATE_IDLE:
			lws_h2_state(h2n->swsi, LWS_H2_STATE_OPEN);
			break;
		case LWS_H2_STATE_RESERVED_REMOTE:
			lws_h2_state(h2n->swsi, LWS_H2_STATE_HALF_CLOSED_LOCAL);
			break;
		}

		lwsl_info("http req, %s, h2n->swsi=%s\n", lws_wsi_tag(wsi),
				lws_wsi_tag(h2n->swsi));
		h2n->swsi->hdr_parsing_completed = 1;

#if defined(LWS_WITH_CLIENT)
		if (h2n->swsi->client_mux_substream &&
		    lws_client_interpret_server_handshake(h2n->swsi)) {
			/*
			 * This is more complicated than it looks, one exit from
			 * interpret_server_handshake() is to do a close that
			 * turns into a redirect.
			 *
			 * In that case, the wsi survives having being reset
			 * and detached from any h2 identity.  We need to get
			 * our parents out from touching it any more
			 */
			lwsl_info("%s: cli int serv hs closed, or redir\n", __func__);
			return 2;
		}
#endif

		if (lws_hdr_extant(h2n->swsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
			const char *simp = lws_hdr_simple_ptr(h2n->swsi,
					      WSI_TOKEN_HTTP_CONTENT_LENGTH);

			if (!simp) /* coverity */
				return 1;
			{
				long long cl_val = atoll(simp);
				if (cl_val < 0)
					return 1;
				h2n->swsi->http.rx_content_length = (unsigned long long)cl_val;
			}
			h2n->swsi->http.rx_content_remain =
					h2n->swsi->http.rx_content_length;
			h2n->swsi->http.content_length_given = 1;
			lwsl_info("setting rx_content_length %lld\n",
				  (long long)h2n->swsi->http.rx_content_length);
		}

		{
			int n = 0, len;
			char buf[256];
			const unsigned char *c;

			do {
				c = lws_token_to_string((enum lws_token_indexes)n);
				if (!c) {
					n++;
					continue;
				}

				len = lws_hdr_total_length(h2n->swsi, (enum lws_token_indexes)n);
				if (!len || len > (int)sizeof(buf) - 1) {
					n++;
					continue;
				}

				if (lws_hdr_copy(h2n->swsi, buf, sizeof buf,
						(enum lws_token_indexes)n) < 0) {
					lwsl_info("    %s !oversize!\n",
						  (char *)c);
				} else {
					buf[sizeof(buf) - 1] = '\0';

					lwsl_info("    %s = %s\n",
						  (char *)c, buf);
				}
				n++;
			} while (c);
		}

		if (h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_REMOTE ||
		    h2n->swsi->h2.h2_state == LWS_H2_STATE_CLOSED) {
			lws_h2_goaway(wsi, H2_ERR_STREAM_CLOSED,
				      "Banning service on CLOSED_REMOTE");
			break;
		}

		switch (h2n->swsi->h2.h2_state) {
		case LWS_H2_STATE_IDLE:
			lws_h2_state(h2n->swsi, LWS_H2_STATE_OPEN);
			break;
		case LWS_H2_STATE_OPEN:
			if (h2n->swsi->h2.END_STREAM)
				lws_h2_state(h2n->swsi,
					     LWS_H2_STATE_HALF_CLOSED_REMOTE);
			break;
		case LWS_H2_STATE_HALF_CLOSED_LOCAL:
			if (h2n->swsi->h2.END_STREAM)
				/*
				 * action the END_STREAM
				 */
				lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);
			break;
		}

#if defined(LWS_WITH_CLIENT)

		/*
		 * If we already had the END_STREAM along with the END_HEADERS,
		 * we have already transitioned to STATE_CLOSED and we are not
		 * going to be doing anything further on this stream.
		 *
		 * In that case handle the transaction completion and
		 * finalize the stream for the peer
		 */

		if (h2n->swsi->h2.h2_state == LWS_H2_STATE_CLOSED &&
			h2n->swsi->client_mux_substream) {

			lws_h2_rst_stream(h2n->swsi, H2_ERR_NO_ERROR,
				"client done");

			if (lws_http_transaction_completed_client(h2n->swsi))
				lwsl_debug("tx completed returned close\n");
			break;
		}

		if (h2n->swsi->client_mux_substream) {
			lwsl_info("%s: %s: headers: client path (h2 state %s)\n",
				  __func__, lws_wsi_tag(wsi),
				  h2_state_names[h2n->swsi->h2.h2_state]);
			break;
		}
#endif

		if (!lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_PATH) ||
		    !lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_METHOD) ||
		    !lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_SCHEME) ||
		     lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_STATUS) ||
		     lws_hdr_extant(h2n->swsi, WSI_TOKEN_CONNECTION)) {
			lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
				      "Pseudoheader checks");
			break;
		}

		if (lws_hdr_extant(h2n->swsi, WSI_TOKEN_TE)) {
			n = lws_hdr_total_length(h2n->swsi, WSI_TOKEN_TE);

			if (n != 8 ||
			    !lws_hdr_simple_ptr(h2n->swsi, WSI_TOKEN_TE) ||
			    strncmp(lws_hdr_simple_ptr(h2n->swsi, WSI_TOKEN_TE),
				  "trailers", (unsigned int)n)) {
				lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
					      "Illegal transfer-encoding");
				break;
			}
		}

#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
		lws_http_compression_validate(h2n->swsi);
#endif

		p = lws_hdr_simple_ptr(h2n->swsi, WSI_TOKEN_HTTP_COLON_METHOD);
		/*
		 * duplicate :path into the individual method uri header
		 * index, so that it looks the same as h1 in the ah
		 */
		for (n = 0; n < (int)LWS_ARRAY_SIZE(method_names); n++)
			if (p && !strcasecmp(p, method_names[n])) {
				h2n->swsi->http.ah->frag_index[method_index[n]] =
						h2n->swsi->http.ah->frag_index[
				                     WSI_TOKEN_HTTP_COLON_PATH];
				break;
			}

		{
			lwsl_debug("%s: setting DEF_ACT from 0x%x\n", __func__,
				   (unsigned int)h2n->swsi->wsistate);
			lwsi_set_state(h2n->swsi, LRS_DEFERRING_ACTION);
			lws_callback_on_writable(h2n->swsi);
		}
		break;

	case LWS_H2_FRAME_TYPE_DATA:
		lwsl_info("%s: DATA flags 0x%x\n", __func__, h2n->flags);
		if (!h2n->swsi)
			break;

		if (lws_hdr_total_length(h2n->swsi,
					 WSI_TOKEN_HTTP_CONTENT_LENGTH) &&
		    h2n->swsi->h2.END_STREAM &&
		    h2n->swsi->http.rx_content_length &&
		    h2n->swsi->http.rx_content_remain) {
			lws_h2_rst_stream(h2n->swsi, H2_ERR_PROTOCOL_ERROR,
					  "Not enough rx content");
			break;
		}

		if (h2n->swsi->h2.END_STREAM &&
		    h2n->swsi->h2.h2_state == LWS_H2_STATE_OPEN)
			lws_h2_state(h2n->swsi,
				     LWS_H2_STATE_HALF_CLOSED_REMOTE);

		if (h2n->swsi->h2.END_STREAM &&
		    h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_LOCAL)
			lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);

#if defined(LWS_WITH_CLIENT)
		/*
		 * client... remote END_STREAM implies we weren't going to
		 * send anything else anyway.
		 */

		if (h2n->swsi->client_mux_substream &&
		    (h2n->flags & LWS_H2_FLAG_END_STREAM)) {
			lwsl_info("%s: %s: DATA: end stream\n",
				  __func__, lws_wsi_tag(h2n->swsi));

			if (h2n->swsi->h2.h2_state == LWS_H2_STATE_OPEN) {
				lws_h2_state(h2n->swsi,
					     LWS_H2_STATE_HALF_CLOSED_REMOTE);
		//		lws_h2_rst_stream(h2n->swsi, H2_ERR_NO_ERROR,
		//				  "client done");

		//		if (lws_http_transaction_completed_client(h2n->swsi))
		//			lwsl_debug("tx completed returned close\n");
			}

			//if (h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_LOCAL)
			{
				lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);

				lws_h2_rst_stream(h2n->swsi, H2_ERR_NO_ERROR,
						  "client done");

				if (lws_http_transaction_completed_client(h2n->swsi))
					lwsl_debug("tx completed returned close\n");
			}
		}
#endif
		break;

	case LWS_H2_FRAME_TYPE_PING:
		if (h2n->flags & LWS_H2_FLAG_SETTINGS_ACK)
			lws_validity_confirmed(wsi);
		else {
			/* they're sending us a ping request */
			struct lws_h2_protocol_send *pps =
					lws_h2_new_pps(LWS_H2_PPS_PONG);
			if (!pps)
				return 1;

			lwsl_info("rx ping, preparing pong\n");

			memcpy(pps->u.ping.ping_payload, h2n->ping_payload, 8);
			lws_pps_schedule(wsi, pps);
		}

		break;

	case LWS_H2_FRAME_TYPE_WINDOW_UPDATE:
		/*
		 * We only have an unsigned 31-bit (positive) increment possible
		 */
		h2n->hpack_e_dep &= ~(1u << 31);
		lwsl_info("WINDOW_UPDATE: sid %u %u (0x%x)\n",
			  (unsigned int)h2n->sid,
			  (unsigned int)h2n->hpack_e_dep,
			  (unsigned int)h2n->hpack_e_dep);

		if (h2n->sid)
			eff_wsi = h2n->swsi;

		if (!eff_wsi) {
			if (h2n->sid > h2n->highest_sid_opened)
				lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
					      "alien sid");
			break; /* ignore */
		}

		if (eff_wsi->a.vhost->options &
		        LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW &&
		    (uint64_t)eff_wsi->txc.tx_cr + (uint64_t)h2n->hpack_e_dep >
		    (uint64_t)0x7fffffff)
			h2n->hpack_e_dep = (uint32_t)(0x7fffffff - eff_wsi->txc.tx_cr);

		if ((uint64_t)eff_wsi->txc.tx_cr + (uint64_t)h2n->hpack_e_dep >
		    (uint64_t)0x7fffffff) {
			lwsl_warn("%s: WINDOW_UPDATE 0x%llx + 0x%llx = 0x%llx, too high\n",
					__func__, (unsigned long long)eff_wsi->txc.tx_cr,
					(unsigned long long)h2n->hpack_e_dep,
					(unsigned long long)eff_wsi->txc.tx_cr + (unsigned long long)h2n->hpack_e_dep);
			if (h2n->sid)
				lws_h2_rst_stream(h2n->swsi,
						  H2_ERR_FLOW_CONTROL_ERROR,
						  "Flow control exceeded max");
			else
				lws_h2_goaway(wsi, H2_ERR_FLOW_CONTROL_ERROR,
					      "Flow control exceeded max");
			break;
		}

		if (!h2n->hpack_e_dep) {
			lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
				      "Zero length window update");
			break;
		}
		n = eff_wsi->txc.tx_cr;
		eff_wsi->txc.tx_cr += (int32_t)h2n->hpack_e_dep;

		lws_wsi_txc_report_manual_txcr_in(eff_wsi,
						  (int32_t)h2n->hpack_e_dep);

		lws_wsi_txc_describe(&eff_wsi->txc, "WINDOW_UPDATE in",
				     eff_wsi->mux.my_sid);

		if (n <= 0 && eff_wsi->txc.tx_cr <= 0)
			/* it helps, but won't change sendability for anyone */
			break;

		/*
		 * It may have changed sendability (depends on SID 0 tx credit
		 * too)... for us and any children waiting on us... reassess
		 * blockage for all children first
		 */
		lws_start_foreach_ll(struct lws *, w, wsi->mux.child_list) {
			lws_callback_on_writable(w);
		} lws_end_foreach_ll(w, mux.sibling_list);

		if (eff_wsi->txc.skint &&
		    !lws_wsi_txc_check_skint(&eff_wsi->txc,
					     lws_h2_tx_cr_get(eff_wsi)))
			/*
			 * This one became un-skint, schedule a writeable
			 * callback
			 */
			lws_callback_on_writable(eff_wsi);

		break;

	case LWS_H2_FRAME_TYPE_GOAWAY:
		lwsl_wsi_info(wsi, "RX GOAWAY: last sid %u, error 0x%08X, string '%s'\n",
			  (unsigned int)h2n->goaway_last_sid,
			  (unsigned int)h2n->goaway_err, h2n->goaway_str);

		return 1;

	case LWS_H2_FRAME_TYPE_RST_STREAM:
		lwsl_info("LWS_H2_FRAME_TYPE_RST_STREAM: sid %u: reason 0x%x\n",
			  (unsigned int)h2n->sid,
			  (unsigned int)h2n->hpack_e_dep);
		if (h2n->swsi) {
			lws_close_free_wsi(h2n->swsi, LWS_CLOSE_STATUS_NOSTATUS,
					   "peer RST_STREAM");
			h2n->swsi = NULL;
		}
		break;

	case LWS_H2_FRAME_TYPE_COUNT: /* IGNORING FRAME */
		break;
	}

	return 0;
}

/*
 * This may want to send something on the network wsi, which may be in the
 * middle of a partial send.  PPS sends are OK because they are queued to
 * go through the WRITABLE handler already.
 *
 * The read parser for the network wsi has no choice but to parse its stream
 * anyway, because otherwise it will not be able to get tx credit window
 * messages.
 *
 * Therefore if we will send non-PPS, ie, lws_http_action() for a stream
 * wsi, we must change its state and handle it as a priority in the
 * POLLOUT handler instead of writing it here.
 *
 * About closing... for the main network wsi, it should return nonzero to
 * close it all.  If it needs to close an swsi, it can do it here.
 */
int
lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t _inlen,
	      lws_filepos_t *inused)
{
	struct lws_h2_netconn *h2n = wsi->h2.h2n;
	struct lws_h2_protocol_send *pps;
	unsigned char c, *oldin = in, *iend = in + (size_t)_inlen;
	int n, m;

	if (!h2n)
		goto fail;

	while (in < iend) {

		c = *in++;

		switch (lwsi_state(wsi)) {
		case LRS_H2_AWAIT_PREFACE:
			if (preface[h2n->count++] != c)
				goto fail;

			if (preface[h2n->count])
				break;

			lwsl_info("http2: %s: established\n", lws_wsi_tag(wsi));
			lwsi_set_state(wsi, LRS_H2_AWAIT_SETTINGS);
			lws_validity_confirmed(wsi);
			h2n->count = 0;
			wsi->txc.tx_cr = 65535;

			/*
			 * we must send a settings frame -- empty one is OK...
			 * that must be the first thing sent by server
			 * and the peer must send a SETTINGS with ACK flag...
			 */
			pps = lws_h2_new_pps(LWS_H2_PPS_MY_SETTINGS);
			if (!pps)
				goto fail;
			lws_pps_schedule(wsi, pps);
			break;

		case LRS_H2_WAITING_TO_SEND_HEADERS:
		case LRS_ESTABLISHED:
		case LRS_H2_AWAIT_SETTINGS:

			if (h2n->frame_state != LWS_H2_FRAME_HEADER_LENGTH)
				goto try_frame_start;

			/*
			 * post-header, preamble / payload / padding part
			 */
			h2n->count++;

			if (h2n->type == LWS_H2_FRAME_TYPE_COUNT) { /* IGNORING FRAME */
				//lwsl_debug("%s: consuming for ignored %u %u\n", __func__, (unsigned int)h2n->count, (unsigned int)h2n->length);
				goto frame_end;
			}


			if (h2n->flags & LWS_H2_FLAG_PADDED &&
			    !h2n->pad_length) {
				/*
				 * Get the padding count... actual padding is
				 * at the end of the frame.
				 */
				h2n->padding = c;
				h2n->pad_length = 1;
				h2n->preamble++;

				if (h2n->padding > h2n->length - 1)
					lws_h2_goaway(wsi,
						      H2_ERR_PROTOCOL_ERROR,
						      "execssive padding");
				break; /* we consumed this */
			}

			if (h2n->flags & LWS_H2_FLAG_PRIORITY &&
			    !h2n->collected_priority) {
				/* going to be 5 preamble bytes */

				lwsl_debug("PRIORITY FLAG:  0x%x\n", c);

				if (h2n->preamble++ - h2n->pad_length < 4) {
					h2n->dep = ((h2n->dep) << 8) | c;
					break; /* we consumed this */
				}
				h2n->weight_temp = c;
				h2n->collected_priority = 1;
				lwsl_debug("PRI FL: dep 0x%x, weight 0x%02X\n",
					   (unsigned int)h2n->dep,
					   h2n->weight_temp);
				break; /* we consumed this */
			}
			if (h2n->padding && h2n->count >
			    (h2n->length - h2n->padding)) {
				if (c) {
					lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
						      "nonzero padding");
					break;
				}
				goto frame_end;
			}

			/* applies to wsi->h2.swsi which may be wsi */
			switch(h2n->type) {

			case LWS_H2_FRAME_TYPE_SETTINGS:
				n = (int)(h2n->count - 1u - h2n->preamble) %
				     LWS_H2_SETTINGS_LEN;
				h2n->one_setting[n] = c;
				if (n != LWS_H2_SETTINGS_LEN - 1)
					break;
				lws_h2_settings(wsi, &h2n->peer_set,
						h2n->one_setting,
						LWS_H2_SETTINGS_LEN);
				break;

			case LWS_H2_FRAME_TYPE_CONTINUATION:
			case LWS_H2_FRAME_TYPE_HEADERS:
				if (!h2n->swsi)
					break;
				if (lws_hpack_interpret(h2n->swsi, c)) {
					lwsl_info("%s: hpack failed\n",
						  __func__);
					goto fail;
				}
				break;

			case LWS_H2_FRAME_TYPE_GOAWAY:
				switch (h2n->inside++) {
				case 0:
				case 1:
				case 2:
				case 3:
					h2n->goaway_last_sid <<= 8;
					h2n->goaway_last_sid |= c;
					h2n->goaway_str[0] = '\0';
					break;

				case 4:
				case 5:
				case 6:
				case 7:
					h2n->goaway_err <<= 8;
					h2n->goaway_err |= c;
					break;

				default:
					if (h2n->inside - 9 <
					    sizeof(h2n->goaway_str) - 1)
						h2n->goaway_str[
						           h2n->inside - 9] = (char)c;
					h2n->goaway_str[
					    sizeof(h2n->goaway_str) - 1] = '\0';
					break;
				}
				break;

			case LWS_H2_FRAME_TYPE_DATA:

			//	lwsl_info("%s: LWS_H2_FRAME_TYPE_DATA: fl 0x%x\n",
			//		  __func__, h2n->flags);

				/*
				 * let the network wsi live a bit longer if
				 * subs are active... our frame may take a long
				 * time to chew through
				 */
				if (!wsi->immortal_substream_count) {
					
					lws_set_timeout(wsi,
						PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE,
						lws_wsi_keepalive_timeout_eff(wsi));
				}

				if (!h2n->swsi || h2n->swsi->socket_is_permanently_unusable)
					break;

				if (lws_buflist_next_segment_len(
						&h2n->swsi->buflist, NULL))
					lwsl_info("%s: substream has pending\n",
						  __func__);

				if (lwsi_role_http(h2n->swsi) &&
				    lwsi_state(h2n->swsi) == LRS_ESTABLISHED) {
					lwsi_set_state(h2n->swsi, LRS_BODY);
					lwsl_info("%s: %s to LRS_BODY\n",
							__func__, lws_wsi_tag(h2n->swsi));
				}

				/*
				 * in + length may cover multiple frames, we
				 * can only consider the length of the DATA
				 * in front of us
				 */

				if (lws_hdr_total_length(h2n->swsi,
					     WSI_TOKEN_HTTP_CONTENT_LENGTH) &&
				    h2n->swsi->http.rx_content_length &&
				    h2n->swsi->http.rx_content_remain <
						     h2n->length - h2n->inside && /* last */
				    h2n->inside < h2n->length) {

					lwsl_warn("%s: rx.cl: %lu, rx.content_remain: %lu, buf left: %lu, "
						  "h2->inside: %lu, h2->length: %lu\n", __func__,
						  (unsigned long)h2n->swsi->http.rx_content_length,
						  (unsigned long)h2n->swsi->http.rx_content_remain,
						  (unsigned long)(lws_ptr_diff_size_t(iend, in) + 1),
						  (unsigned long)h2n->inside, (unsigned long)h2n->length);

					/* unread data in frame */
					lws_h2_goaway(wsi,
						      H2_ERR_PROTOCOL_ERROR,
					    "More rx than content_length told");
					break;
				}

				/*
				 * We operate on a frame.  The RX we have at
				 * hand may exceed the current frame.
				 */

				n = (int)lws_ptr_diff_size_t(iend, in)  + 1;
				if (n > (int)(h2n->length - h2n->count + 1)) {
					if (h2n->count > h2n->length)
						goto close_swsi_and_return;
					n = (int)(h2n->length - h2n->count) + 1;
					lwsl_debug("---- restricting len to %d "
						   "\n", n);
				}
#if defined(LWS_WITH_CLIENT)
				/*
				 * A client stream carrying ws (RFC 8441) is
				 * NOT http body: its DATA is ws framing that
				 * must go through the ws parser via the
				 * lws_read_h1() path below, like the server
				 * side of the same situation.
				 */
				if (h2n->swsi->client_mux_substream &&
				    !h2n->swsi->h23_stream_carries_ws) {
					if (!h2n->swsi->a.protocol) {
						lwsl_err("%s: %p doesn't have protocol\n",
							 __func__, lws_wsi_tag(h2n->swsi));
						m = 1;
					} else {
						h2n->swsi->txc.peer_tx_cr_est -= n;
						wsi->txc.peer_tx_cr_est -= n;
						lws_wsi_txc_describe(&h2n->swsi->txc,
							__func__,
							h2n->swsi->mux.my_sid);
					m = user_callback_handle_rxflow(
						h2n->swsi->a.protocol->callback,
						h2n->swsi,
					  LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,
						h2n->swsi->user_space,
						in - 1, (unsigned int)n);
					}

					in += n - 1;
					h2n->inside += (unsigned int)n;
					h2n->count += (unsigned int)n - 1;

					if (m) {
						lwsl_info("RECEIVE_CLIENT_HTTP "
							  "closed it\n");
						goto close_swsi_and_return;
					}

					goto do_windows;
				}
#endif
				if (lwsi_state(h2n->swsi) == LRS_DEFERRING_ACTION) {
					m = lws_buflist_append_segment(
						&h2n->swsi->buflist, in - 1, (unsigned int)n);
					if (m < 0)
						return -1;

					/*
					 * Since we're in an open-ended
					 * DEFERRING_ACTION, don't add this swsi
					 * to the pt list of wsi holding buflist
					 * content yet, we are not in a position
					 * to consume it until we get out of
					 * DEFERRING_ACTION.
					 */

					in += n - 1;
					h2n->inside += (unsigned int)n;
					h2n->count += (unsigned int)n - 1;

					lwsl_debug("%s: deferred %d\n", __func__, n);
					goto do_windows;
				}

				h2n->swsi->outer_will_close = 1;
				/*
				 * choose the length for this go so that we end at
				 * the frame boundary, in the case there is already
				 * more waiting leave it for next time around
				 */

				n = lws_read_h1(h2n->swsi, in - 1, (unsigned int)n);
				// lwsl_notice("%s: lws_read_h1 %d\n", __func__, n);
				h2n->swsi->outer_will_close = 0;
				/*
				 * can return 0 in POST body with
				 * content len exhausted somehow.
				 */
				if (n < 0 ||
				    (!n && h2n->swsi->http.content_length_given && !lws_buflist_next_segment_len(
						    &wsi->buflist, NULL))) {
					lwsl_info("%s: lws_read_h1 told %d %u / %u\n",
						__func__, n,
						(unsigned int)h2n->count,
						(unsigned int)h2n->length);
					in += h2n->length - h2n->count;
					h2n->inside = h2n->length;
					h2n->count = h2n->length - 1;

					//if (n < 0)
					//	goto already_closed_swsi;
					goto close_swsi_and_return;
				}

				lwsl_info("%s: lws_read_h1 telling %d %u / %u\n",
						__func__, n,
						(unsigned int)h2n->count,
						(unsigned int)h2n->length);

				if (n) {
					in += (unsigned int)n - 1;
					h2n->inside += (unsigned int)n;
					h2n->count += (unsigned int)n - 1;

					h2n->swsi->txc.peer_tx_cr_est -= n;
					wsi->txc.peer_tx_cr_est -= n;
				}

do_windows:

#if defined(LWS_WITH_CLIENT)
				if (!(h2n->swsi->flags & LCCSCF_H2_MANUAL_RXFLOW))
#endif
				{
					/*
					 * The default behaviour is we just keep
					 * cranking the other side's tx credit
					 * back up, for simple bulk transfer as
					 * fast as we can take it
					 */

					m = n  + 65536;

					/* update both the stream and nwsi */

					lws_h2_update_peer_txcredit_thresh(h2n->swsi,
								    h2n->sid, m, m);
				}
#if defined(LWS_WITH_CLIENT)
				else {
					/*
					 * If he's handling it himself, only
					 * repair the nwsi credit but allow the
					 * stream credit to run down until the
					 * user code deals with it
					 */
					lws_h2_update_peer_txcredit(wsi, (unsigned int)0, n);
					h2n->swsi->txc.manual = 1;
				}
#endif
				break;

			case LWS_H2_FRAME_TYPE_PRIORITY:
				if (h2n->count <= 4) {
					h2n->dep <<= 8;
					h2n->dep |= c;
					break;
				}
				h2n->weight_temp = c;
				lwsl_info("PRIORITY: dep 0x%x, weight 0x%02X\n",
					  (unsigned int)h2n->dep, h2n->weight_temp);

				if ((h2n->dep & ~(1u << 31)) == h2n->sid) {
					lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
						      "cant depend on own sid");
					break;
				}
				break;

			case LWS_H2_FRAME_TYPE_RST_STREAM:
				h2n->hpack_e_dep <<= 8;
				h2n->hpack_e_dep |= c;
				break;

			case LWS_H2_FRAME_TYPE_PUSH_PROMISE:
				break;

			case LWS_H2_FRAME_TYPE_PING:
				if (h2n->flags & LWS_H2_FLAG_SETTINGS_ACK) { // ack
				} else { /* they're sending us a ping request */
					if (h2n->count > 8)
						return 1;
					h2n->ping_payload[h2n->count - 1] = c;
				}
				break;

			case LWS_H2_FRAME_TYPE_WINDOW_UPDATE:
				h2n->hpack_e_dep <<= 8;
				h2n->hpack_e_dep |= c;
				break;

			case LWS_H2_FRAME_TYPE_COUNT: /* IGNORING FRAME */
				//lwsl_debug("%s: consuming for ignored %u %u\n", __func__, (unsigned int)h2n->count, (unsigned int)h2n->length);
				h2n->count++;
				break;

			default:
				lwsl_notice("%s: unhandled frame type %d\n",
					    __func__, h2n->type);

				goto fail;
			}

frame_end:
			if (h2n->count > h2n->length) {
				lwsl_notice("%s: count > length %u %u (type %d)\n",
					    __func__, (unsigned int)h2n->count,
					    (unsigned int)h2n->length, h2n->type);

			} else
				if (h2n->count != h2n->length)
					break;

			/*
			 * end of frame just happened
			 */
			n = lws_h2_parse_end_of_frame(wsi);
			if (n == 2) {
				*inused = (lws_filepos_t)lws_ptr_diff_size_t(in, oldin);

				return 2;
			}
			if (n)
				goto fail;

			break;

try_frame_start:
			if (h2n->frame_state <= 8) {

				switch (h2n->frame_state++) {
				case 0:
					h2n->pad_length = 0;
					h2n->collected_priority = 0;
					h2n->padding = 0;
					h2n->preamble = 0;
					h2n->length = c;
					h2n->inside = 0;
					break;
				case 1:
				case 2:
					h2n->length <<= 8;
					h2n->length |= c;
					break;
				case 3:
					h2n->type = c;
					break;
				case 4:
					h2n->flags = c;
					break;

				case 5:
				case 6:
				case 7:
				case 8:
					h2n->sid <<= 8;
					h2n->sid |= c;
					break;
				}
			}

			if (h2n->frame_state == LWS_H2_FRAME_HEADER_LENGTH &&
			    lws_h2_parse_frame_header(wsi))
				goto fail;
			break;

		default:
			if (h2n->type == LWS_H2_FRAME_TYPE_COUNT) { /* IGNORING FRAME */
				//lwsl_debug("%s: consuming for ignored %u %u\n", __func__, (unsigned int)h2n->count, (unsigned int)h2n->length);
				h2n->count++;
			}
			break;
		}
	}

	*inused = (lws_filepos_t)lws_ptr_diff_size_t(in, oldin);

	return 0;

close_swsi_and_return:

	lws_close_free_wsi(h2n->swsi, 0, "close_swsi_and_return");
	h2n->swsi = NULL;
	h2n->frame_state = 0;
	h2n->count = 0;

// already_closed_swsi:
	*inused = (lws_filepos_t)lws_ptr_diff_size_t(in, oldin);

	return 2;

fail:
	*inused = (lws_filepos_t)lws_ptr_diff_size_t(in, oldin);

	return 1;
}

#if defined(LWS_WITH_CLIENT)
int
lws_h2_client_handshake(struct lws *wsi)
{
	struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];
	uint8_t *buf, *start, *p, *p1, *end;
	char *meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD),
	     *uri = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI), *simp;
	struct lws *nwsi = lws_get_network_wsi(wsi);
	const char *path = "/";
	int n, m;
	/*
	 * The identifier of a newly established stream MUST be numerically
	 * greater than all streams that the initiating endpoint has opened or
	 * reserved.  This governs streams that are opened using a HEADERS frame
	 * and streams that are reserved using PUSH_PROMISE.  An endpoint that
	 * receives an unexpected stream identifier MUST respond with a
	 * connection error (Section 5.4.1) of type PROTOCOL_ERROR.
	 */
	unsigned int sid = wsi->mux.my_sid;

	/*
	 * If this stream was already bound to a sid (the first client stream is
	 * bound to sid 1 at the h2 upgrade), keep it.  Only streams that don't
	 * yet have a sid get one allocated here, at header-send time, so that
	 * concurrently-opening streams still get monotonically-increasing sids.
	 * Without this guard the first stream's sid 1 was overwritten with
	 * highest_sid_opened + 2 (= 3), which strict servers (e.g. Google's GFE)
	 * reject with PROTOCOL_ERROR since the first client stream must be 1.
	 */
	if (!sid)
		sid = nwsi->h2.h2n->highest_sid_opened + 2;

	lwsl_debug("%s\n", __func__);

	/*
	 * We MUST allocate our sid here at the point we're about to send the
	 * stream open.  It's because we don't know the order in which multiple
	 * open streams will send their headers... in h2, sending the headers
	 * is the point the stream is opened.  The peer requires that we only
	 * open streams in ascending sid order
	 */

	wsi->mux.my_sid = sid;
	if (sid > nwsi->h2.h2n->highest_sid_opened)
		nwsi->h2.h2n->highest_sid_opened = sid;
	lwsl_info("%s: %s: assigning SID %d at header send\n", __func__,
			lws_wsi_tag(wsi), sid);


	lwsl_info("%s: CLIENT_WAITING_TO_SEND_HEADERS: pollout (sid %d)\n",
			__func__, wsi->mux.my_sid);

	p = start = buf = pt->serv_buf + LWS_PRE;
	end = start + (wsi->a.context->pt_serv_buf_size / 2) - LWS_PRE - 1;

	/* it's time for us to send our client stream headers */

	if (wsi->do_ws)
		meth = "CONNECT";
	else if (!meth)
		meth = "GET";

	/* h2 pseudoheaders must be in a bunch at the start */

	if (lws_add_http_header_by_token(wsi,
				WSI_TOKEN_HTTP_COLON_METHOD,
				(unsigned char *)meth,
				(int)strlen(meth), &p, end))
		goto fail_length;

	if (wsi->do_ws) {
		if (lws_add_http_header_by_token(wsi,
					WSI_TOKEN_COLON_PROTOCOL,
					(unsigned char *)"websocket", 9,
					&p, end))
			goto fail_length;
	}

	if (lws_add_http_header_by_token(wsi,
				WSI_TOKEN_HTTP_COLON_SCHEME,
				(unsigned char *)"https", 5,
				&p, end))
		goto fail_length;


	n = lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_URI);
	if (n)
		path = uri;
	else
		if (wsi->stash && wsi->stash->cis[CIS_PATH]) {
			path = wsi->stash->cis[CIS_PATH];
			n = (int)strlen(path);
		} else
			n = 1;

	if (n > 1 && path[0] == '/' && path[1] == '/') {
		path++;
		n--;
	}

	// lwsl_hexdump_notice(path, (size_t)n);

	if (n && lws_add_http_header_by_token(wsi,
				WSI_TOKEN_HTTP_COLON_PATH,
				(unsigned char *)path, n, &p, end))
		goto fail_length;

	n = lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_HOST);
	simp = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST);
	if (!n && wsi->stash && wsi->stash->cis[CIS_ADDRESS]) {
		n = (int)strlen(wsi->stash->cis[CIS_ADDRESS]);
		simp = wsi->stash->cis[CIS_ADDRESS];
	}

//	n = lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_ORIGIN);
//	simp = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN);
	/*
	 * h2 carries the request authority in the :authority pseudo-header,
	 * not a Host header (RFC 7540 8.1.2.3).  Sending only Host (as this
	 * path used to) makes strict servers such as Google's GFE reject the
	 * stream with PROTOCOL_ERROR; :authority is accepted by all h2 servers.
	 */
	if (n && simp && lws_add_http_header_by_token(wsi,
				WSI_TOKEN_HTTP_COLON_AUTHORITY,
				(unsigned char *)simp, n, &p, end))
		goto fail_length;

#if defined(LWS_ROLE_WS)
	if (wsi->do_ws) {
		/*
		 * These are regular headers, so they must come after every
		 * pseudo-header (:authority is the last one above), or strict
		 * peers fail the stream with "pseudoheader after normal hdrs".
		 * The requested subprotocol list rides in the same header as
		 * for h1 upgrades (RFC 8441 Sect 5).
		 */
		const char *prot = lws_hdr_simple_ptr(wsi,
					_WSI_TOKEN_CLIENT_SENT_PROTOCOLS);

		if (lws_add_http_header_by_token(wsi, WSI_TOKEN_VERSION,
					(unsigned char *)"13", 2, &p, end))
			goto fail_length;

		if (!prot && wsi->stash && wsi->stash->cis[CIS_PROTOCOL])
			prot = wsi->stash->cis[CIS_PROTOCOL];

		if (prot) {
			if (lws_add_http_header_by_token(wsi, WSI_TOKEN_PROTOCOL,
						(unsigned char *)prot, (int)strlen(prot), &p, end))
				goto fail_length;
		}

#if !defined(LWS_WITHOUT_EXTENSIONS)
		{
			/*
			 * Offer the vhost's extensions the same way the h1
			 * upgrade does (RFC 8441 Sect 5 carries the ws
			 * headers unchanged): without this the client can
			 * never negotiate eg, permessage-deflate over h2.
			 */
			const struct lws_extension *ext =
						wsi->a.vhost->ws.extensions;
			char eb[256];
			int el = 0;

			while (ext && ext->callback) {
				if (wsi->a.vhost->protocols[0].callback(wsi,
				    LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED,
				    wsi->user_space, (char *)ext->name, 0)) {
					ext++;
					continue;
				}
				el += lws_snprintf(eb + (size_t)el,
						   sizeof(eb) - (size_t)el,
						   "%s%s", el ? "," : "",
						   ext->client_offer);
				ext++;
			}
			if (el &&
			    lws_add_http_header_by_token(wsi,
					WSI_TOKEN_EXTENSIONS,
					(unsigned char *)eb, el, &p, end))
				goto fail_length;
		}
#endif

		wsi->h23_stream_carries_ws = 1;
	}
#endif

	if (wsi->flags & LCCSCF_HTTP_MULTIPART_MIME) {
		p1 = lws_http_multipart_headers(wsi, p);
		if (!p1)
			goto fail_length;
		p = p1;
	}

	if (wsi->flags & LCCSCF_HTTP_X_WWW_FORM_URLENCODED) {
		if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
			   (unsigned char *)"application/x-www-form-urlencoded",
			   33, &p, end))
			goto fail_length;
		lws_client_http_body_pending(wsi, 1);
	}

	/* give userland a chance to append, eg, cookies */

#if defined(LWS_WITH_CACHE_NSCOOKIEJAR) && defined(LWS_WITH_CLIENT)
	if (wsi->flags & LCCSCF_CACHE_COOKIES)
		lws_cookie_send_cookies(wsi, (char **)&p, (char *)end);
#endif

	if (wsi->a.protocol->callback(wsi,
				LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER,
				wsi->user_space, &p, lws_ptr_diff_size_t(end, p) - 12))
		goto fail_length;

	if (lws_finalize_http_header(wsi, &p, end))
		goto fail_length;

	m = LWS_WRITE_HTTP_HEADERS;
#if defined(LWS_WITH_CLIENT)
	/* below is not needed in spec, indeed it destroys the long poll
	 * feature, but required by nghttp2 */
	if ((wsi->flags & LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM) &&
	    !(wsi->client_http_body_pending  || lws_has_buffered_out(wsi)))
		m |= LWS_WRITE_H2_STREAM_END;
#endif

	// lwsl_hexdump_notice(start, p - start);

	n = lws_write(wsi, start, lws_ptr_diff_size_t(p, start), (enum lws_write_protocol)m);

	if (n != lws_ptr_diff(p, start)) {
		lwsl_err("_write returned %d from %ld\n", n,
			 (long)(p - start));
		return -1;
	}

	/*
	 * Normally let's charge up the peer tx credit a bit.  But if
	 * MANUAL_REFLOW is set, just set it to the initial credit given in
	 * the client create info
	 */

	n = 4 * 65536;
	if (wsi->flags & LCCSCF_H2_MANUAL_RXFLOW) {
		n = wsi->txc.manual_initial_tx_credit;
		wsi->txc.manual = 1;
	}

	if (lws_h2_update_peer_txcredit(wsi, wsi->mux.my_sid, n))
		return 1;

	lws_h2_state(wsi, LWS_H2_STATE_OPEN);
	lwsi_set_state(wsi, LRS_ESTABLISHED);

	if (wsi->mux.my_sid == 1) {
		lws_start_foreach_ll(struct lws *, w1, nwsi->mux.child_list) {
			if (w1 != wsi && lwsi_state(w1) == LRS_H2_WAITING_TO_SEND_HEADERS)
				lws_callback_on_writable(w1);
		} lws_end_foreach_ll(w1, mux.sibling_list);
	}

	if (wsi->flags & LCCSCF_HTTP_MULTIPART_MIME)
		lws_callback_on_writable(wsi);

	return 0;

fail_length:
	lwsl_err("Client hdrs too long: incr context info.pt_serv_buf_size\n");

	return -1;
}
#endif

#if defined(LWS_ROLE_WS) && defined(LWS_WITH_SERVER)
int
lws_h2_ws_handshake(struct lws *wsi)
{
	uint8_t buf[LWS_PRE + 2048], *p = buf + LWS_PRE, *start = p,
		*end = &buf[sizeof(buf) - 1];
	const struct lws_http_mount *hit;
	const char * uri_ptr;
	size_t m;
	int n;

	if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
		return -1;

	if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) > 64)
		return -1;

	if (wsi->proxied_ws_parent && wsi->child_list) {
		if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL)) {
			if (lws_add_http_header_by_token(wsi, WSI_TOKEN_PROTOCOL,
				(uint8_t *)lws_hdr_simple_ptr(wsi,
							   WSI_TOKEN_PROTOCOL),
				(int)strlen(lws_hdr_simple_ptr(wsi,
							   WSI_TOKEN_PROTOCOL)),
						 &p, end))
			return -1;
		}
	} else {

		/* we can only return the protocol header if:
		 *  - one came in, and ... */
		if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) &&
		    /*  - it is not an empty string */
		    wsi->a.protocol->name && wsi->a.protocol->name[0]) {

#if defined(LWS_WITH_SECURE_STREAMS) && defined(LWS_WITH_SERVER)

		/*
		 * This is the h2 version of server-ws.c understanding that it
		 * did the ws upgrade on a ss server object, therefore it needs
		 * to pass back to the peer the policy ws-protocol name, not
		 * the generic ss-ws.c protocol name
		 */

		if (wsi->a.vhost && wsi->a.vhost->ss_handle &&
		    wsi->a.vhost->ss_handle->policy->u.http.u.ws.subprotocol) {
			lws_ss_handle_t *h =
				(lws_ss_handle_t *)wsi->a.opaque_user_data;

			lwsl_notice("%s: Server SS %s .wsi %s switching to ws protocol\n",
					__func__, lws_ss_tag(h), lws_wsi_tag(h->wsi));

			wsi->a.protocol = &protocol_secstream_ws;

			/*
			 * inform the SS user code that this has done a one-way
			 * upgrade to some other protocol... it will likely
			 * want to treat subsequent payloads differently
			 */

			lws_ss_event_helper(h, LWSSSCS_SERVER_UPGRADE);

			lws_mux_mark_immortal(wsi);

			if (lws_add_http_header_by_token(wsi, WSI_TOKEN_PROTOCOL,
				(unsigned char *)wsi->a.vhost->ss_handle->policy->
						u.http.u.ws.subprotocol,
				(int)strlen(wsi->a.vhost->ss_handle->policy->
						u.http.u.ws.subprotocol), &p, end))
					return -1;
		} else
#endif

			if (lws_add_http_header_by_token(wsi, WSI_TOKEN_PROTOCOL,
				(unsigned char *)wsi->a.protocol->name,
				(int)strlen(wsi->a.protocol->name), &p, end))
					return -1;
		}
	}

#if !defined(LWS_WITHOUT_EXTENSIONS)
        /*
         * Figure out which extensions the client has that we want to
         * enable on this connection, and give him back the list.
         *
         * Give him a limited write bugdet
         */
        if (lws_extension_server_handshake(wsi, (char **)&p, 192))
               return -1;
#endif

	{
		struct lws_process_html_args args;

		args.p = (char *)p;
		args.max_len = lws_ptr_diff(end, p);
		if (user_callback_handle_rxflow(wsi->a.protocol->callback, wsi,
						LWS_CALLBACK_ADD_HEADERS,
						wsi->user_space, &args, 0))
			return -1;

		p = (uint8_t *)args.p;
	}

	if (lws_finalize_http_header(wsi, &p, end))
		return -1;

	m = lws_ptr_diff_size_t(p, start);
	// lwsl_hexdump_notice(start, m);
	n = lws_write(wsi, start, m, LWS_WRITE_HTTP_HEADERS);
	if (n != (int)m) {
		lwsl_err("_write returned %d from %d\n", n, (int)m);

		return -1;
	}

	/*
	 * alright clean up, set our state to generic ws established, the
	 * mode / state of the nwsi will get the h2 processing done.
	 */

	lwsi_set_state(wsi, LRS_ESTABLISHED);
	wsi->lws_rx_parse_state = 0; // ==LWS_RXPS_NEW;

	uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_PATH);
	n = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH);
	hit = lws_find_mount(wsi, uri_ptr, n);

	if (hit && hit->cgienv &&
	    wsi->a.protocol->callback(wsi, LWS_CALLBACK_HTTP_PMO, wsi->user_space,
				    (void *)hit->cgienv, 0))
		return 1;

	lws_validity_confirmed(wsi);

	return 0;
}
#endif

int
lws_read_h2(struct lws *wsi, unsigned char *buf, lws_filepos_t len)
{
	unsigned char *oldbuf = buf;

	// lwsl_notice("%s: h2 path: wsistate 0x%x len %d\n", __func__,
	//		wsi->wsistate, (int)len);

	/*
	 * wsi here is always the network connection wsi, not a stream
	 * wsi.  Once we unpicked the framing we will find the right
	 * swsi and make it the target of the frame.
	 *
	 * If it's ws over h2, the nwsi will get us here to do the h2
	 * processing, and that will call us back with the swsi +
	 * ESTABLISHED state for the inner payload, handled in a later
	 * case.
	 */
	while (len) {
		lws_filepos_t body_chunk_len = 0;
		int m;

		/*
		 * we were accepting input but now we stopped doing so
		 */
		if (lws_is_flowcontrolled(wsi)) {
			lws_rxflow_cache(wsi, buf, 0, (size_t)len);
			buf += len;
			break;
		}

		/*
		 * lws_h2_parser() may send something; when it gets the
		 * whole frame, it will want to perform some action
		 * involving a reply.  But we may be in a partial send
		 * situation on the network wsi...
		 *
		 * Even though we may be in a partial send and unable to
		 * send anything new, we still have to parse the network
		 * wsi in order to gain tx credit to send, which is
		 * potentially necessary to clear the old partial send.
		 *
		 * ALL network wsi-specific frames are sent by PPS
		 * already, these are sent as a priority on the writable
		 * handler, and so respect partial sends.  The only
		 * problem is when a stream wsi wants to send an, eg,
		 * reply headers frame in response to the parsing
		 * we will do now... the *stream wsi* must stall in a
		 * different state until it is able to do so from a
		 * priority on the WRITABLE callback, same way that
		 * file transfers operate.
		 */

		m = lws_h2_parser(wsi, buf, len, &body_chunk_len);

               body_chunk_len &= 0xffffffff; /* attempt workaround for finding len=7167 --> len = 0xffffffff00001bff on lws.org */

		if (m && m != 2) {
			lwsl_debug("%s: http2_parser bail: %d\n", __func__, m);
			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
					   "lws_read_h2 bail");

			return -1;
		}
		if (m == 2) {
			/* swsi has been closed */
			buf += body_chunk_len;
			break;
		}

		buf += body_chunk_len;
		len -= body_chunk_len;
	}

	return lws_ptr_diff(buf, oldbuf);
}

int
lws_h2_client_stream_long_poll_rxonly(struct lws *wsi)
{

	if (!wsi->mux_substream)
		return 1;

	/*
	 * Elect to send an empty DATA with END_STREAM, to force the stream
	 * into HALF_CLOSED LOCAL
	 */
	wsi->h2.long_poll = 1;
	wsi->h2.send_END_STREAM = 1;

	// lws_header_table_detach(wsi, 0);

	lws_callback_on_writable(wsi);

	return 0;
}
